Быстрое рисование множества прямоугольников по одному в WPF - PullRequest
12 голосов
/ 15 декабря 2010

Мое приложение получает данные с внешнего устройства.После каждой точки данных существует короткое электронное время задержки (около 10 мкс), в течение которого никакая другая точка данных не может быть получена, и мое приложение должно использовать ее для обработки и отображения данных на экране в виде точечной диаграммы.Моя самая важная цель - не превышать это электронное мертвое время.Как можно было бы подойти к этой проблеме в приложении на основе WPF и как можно было бы сравнить различные методы?

Я попробовал следующее:

  • Создание Rectangleв Canvas для каждой прибывающей точки данных.Это слишком медленно с коэффициентом 10.
  • Тот же подход, но рисование DrawingVisuals в пользовательском элементе управления.Лучше, но все еще слишком медленно.Добавление визуальных / логических дочерних элементов в дерево может иметь слишком много служебных данных.
  • A UserControl, где все точки данных хранятся в массиве и отображаются в методе OnRender.Здесь я должен снова рисовать каждую точку при каждом вызове OnRender.Поэтому этот метод замедляется с течением времени, что нежелательно.Есть ли способ сказать OnRender не очищать экран при каждом проходе, чтобы я мог рисовать постепенно?
  • Отображение каждой точки в виде пикселя в WriteableBitmap.Кажется, это работает, но я не нашел способа определить, не делает ли недействительной часть растрового изображения несколько очень долгих периодов ожидания (когда изображение фактически обновляется на экране).Любые идеи для измерения этого?

Редактировать:

В комментариях была повышена точка буферизации данных и их отображения с более медленной скоростью.Проблема с этим подходом заключается в том, что в какой-то момент мне приходится обрабатывать буфер.Выполнение этого во время измерения вводит длительное время, в течение которого моя система занята, и новые события будут отбрасываться.Поэтому иметь дело с каждым пунктом индивидуально, но навсегда, было бы более желательным.Использование 10 мкс для запуска отображения для каждого события намного лучше, чем мгновенное сохранение его в буфере, и использование 100 мкс каждые 50 мс или около того для обработки накопленных событий.

I old (то есть не-WPF)) дней, вы могли бы, например, поместить необходимые данные в графическую память и сделать так, чтобы видеокарта работала с ними в удобное для вас время.Конечно, на самом деле он не будет отображаться с частотой, превышающей 60 Гц, но вам не нужно было снова трогать эти данные.

Надеюсь, я ясно дал понять, каковы мои требования.Черт мой английский =)

Ответы [ 4 ]

11 голосов
/ 15 декабря 2010

Использование WriteableBitmap будет самым быстрым подходом.Для тестирования вы можете предварительно выделить массив и использовать секундомер для выборки таймингов при рендеринге, затем вы можете проанализировать тайминги, чтобы получить представление о производительности.

Одна из ваших основных проблем связана со сборкой мусора.,К сожалению, это создаст потенциальные возможности для проблем с производительностью, которые вы описываете, например, случайное зависание во время проведения GC.Вы можете поэкспериментировать с GC с низкой задержкой, чтобы смягчить это.

Update

Вот пример использования GC с низкой задержкой:

http://blogs.microsoft.co.il/blogs/sasha/archive/2008/08/10/low-latency-gc-in-net-3-5.aspx

Вы можете использовать это, чтобы гарантировать, что во время «мертвого времени», то есть времени рендеринга, не будет никаких сборок мусора.

Обновление 2

Как я уже говорилв моем комментарии некоторое время назад - вы пакетируете обновления для вашего WritableBitmap?

Частота обновления вашего устройства слишком высока, чтобы выдерживать запись в растровое изображение для каждого обновления устройства - я думаю, что в каждом обновлении находится от 10 до 100 000 обновлений.второй.Попробуйте обновить свое растровое изображение на более заметной частоте (например, 60 или 25 раз в секунду), поскольку накладные расходы на принудительное отображение растрового изображения будут доминировать при производительности при 10–100 тыс. Обновлений в секунду.Выполняйте запись в буфер при получении обновлений устройства, а затем периодически переносите этот буфер в WritableBitmap.Вы можете использовать таймер для этого или делать это каждые n обновлений устройства.Таким образом, вы будете пакетировать свои обновления и значительно сократить накладные расходы при рендеринге WritableBitmap.

Обновление 3

Хорошо, похоже, что вы обновляете WritableBitmap 10–100 тыс. Раз завторое - это нереально.Пожалуйста, попробуйте механизм, основанный на кадрах и пакетах, как описано ранее.Кроме того, ваш дисплей может обновляться с частотой 60 кадров в секунду.

Если вас беспокоит блокировка обновлений вашего устройства, рассмотрите возможность использования двух чередующихся обратных буферов и многопоточности.Таким образом, вы периодически переключаетесь в какой буфер буферизует ваше устройство и используете второй поток для рендеринга замененного буфера в WritableBitmap.Пока вы можете поменять буфер в <10 мкс, вы можете делать это в мертвое время, не блокируя обновления вашего устройства. </p>

Обновление 4

В дополнение к ответуна мой вопрос, может показаться, что в настоящее время вызывается «блокировка \ разблокировка» для каждого из 100 000 обновлений в секунду.Это то, что, вероятно, убивает производительность.На моей (мощной) системе я измерил 100k "блокировка \ разблокировка" на ~ 275мс.Это довольно тяжело и будет намного хуже в системах с низким энергопотреблением.

Вот почему я думаю, что 100 тыс. Обновлений в секунду недостижимо, т.е. блокировка -> обновление -> разблокировка.Блокировка слишком дорогая.

Вам нужно найти способ снизить количество блокирующих вызовов, не блокируя их вообще, блокируя каждые n операций или, возможно, пакетные запросы и затем применяя пакетное обновление взамок.Здесь есть несколько вариантов.

Если вы хотите выполнить пакетное обновление, оно может составить всего 10 циклов, что снизит частоту обновления до 10 000 обновлений в секунду.Это сократит ваши накладные расходы на блокировку в 10 раз.

Пример кода эталонного теста для блокировки накладных расходов на вызовах 100k:

lock/unlock - Interval:1 - :289.47ms
lock/unlock - Interval:1 - :287.43ms
lock/unlock - Interval:1 - :288.74ms
lock/unlock - Interval:1 - :286.48ms
lock/unlock - Interval:1 - :286.36ms
lock/unlock - Interval:10 - :29.12ms
lock/unlock - Interval:10 - :29.01ms
lock/unlock - Interval:10 - :28.80ms
lock/unlock - Interval:10 - :29.35ms
lock/unlock - Interval:10 - :29.00ms

Код:

public void MeasureLockUnlockOverhead()
{
    const int TestIterations = 5;

    Action<string, Func<double>> test = (name, action) =>
    {
        for (int i = 0; i < TestIterations; i++)
        {
            Console.WriteLine("{0}:{1:F2}ms", name, action());
        }
    };

    Action<int> lockUnlock = interval =>
    {
        WriteableBitmap bitmap =
           new WriteableBitmap(100, 100, 96d, 96d, PixelFormats.Bgr32, null);

        int counter = 0;

        Action t1 = () =>
        {
            if (++counter % interval == 0)
            {
                bitmap.Lock();
                bitmap.Unlock();
            }
        };

        string title = string.Format("lock/unlock - Interval:{0} -", interval);

        test(title, () => TimeTest(t1));
    };

    lockUnlock(1);
    lockUnlock(10);
}

[SuppressMessage("Microsoft.Reliability",
    "CA2001:AvoidCallingProblematicMethods", MessageId = "System.GC.Collect")]
private static double TimeTest(Action action)
{
    const int Iterations = 100 * 1000;

    Action gc = () =>
    {
        GC.Collect();
        GC.WaitForFullGCComplete();
    };

    Action empty = () => { };

    Stopwatch stopwatch1 = Stopwatch.StartNew();

    for (int j = 0; j < Iterations; j++)
    {
        empty();
    }

    double loopElapsed = stopwatch1.Elapsed.TotalMilliseconds;

    gc();

    action(); //JIT
    action(); //Optimize

    Stopwatch stopwatch2 = Stopwatch.StartNew();

    for (int j = 0; j < Iterations; j++)
    {
        action();
    }

    gc();

    double testElapsed = stopwatch2.Elapsed.TotalMilliseconds;

    return (testElapsed - loopElapsed);
}
2 голосов
/ 23 декабря 2011

Полное раскрытие: я участвовал в проекте с открытым исходным кодом WriteableBitmapEx, однако это не моя библиотека и я не связан с ее владельцем

Чтобы добавить к отличному ответу chibacity , я бы посоветовал взглянуть на библиотеку WriteableBitmapEx . Это отличная библиотека WPF, Silverlight и Windows Phone, которая добавляет GDI-подобные методы расширения рисования (блики, линии, фигуры, преобразования, а также пакетные операции) в класс WriteableBitmap.

Последняя версия WBEx содержит рефакторинг, который я выполнил, чтобы разрешить пакетные операции. Библиотека WriteableBitmapEx теперь имеет метод расширения, называемый GetBitmapContext(), для возврата структуры IDisposable, которая обертывает один блок блокировки / разблокировки / аннулирования. С помощью следующего синтаксиса вы можете легко группировать вызовы для рисования и выполнять только одну блокировку / разблокировку / отмену в конце

// Constructor of BitmapContext locks the bmp and gets a pointer to bitmap
using (var bitmapContext = writeableBitmap.GetBitmapContext())
{
     // Perform multiple drawing calls (pseudocode)
     writebleBitmap.DrawLine(...)
     writebleBitmap.DrawRectangle(...) 
     // etc ...
} // On dispose of bitmapcontext, it unlocks and invalidates the bmp
2 голосов
/ 20 декабря 2010

WPF опирается на сохраненный движок композиции, который клевый, но, похоже, вам нужно больше после «простого» и необработанного отображения растрового изображения.

Я думаю, у вас есть хороший пример того, что вы хотите сделать здесь: https://web.archive.org/web/20140519134127/http://khason.net/blog/how-to-high-performance-graphics-in-wpf/

1 голос
/ 21 декабря 2010

Если я правильно понял, у вас есть сценарий, в котором вы хотите получить данные от вашего датчика в течение нескольких секунд - и показать это.У вас есть требование в реальном времени - или вы сохраняете данные со своей специальной «камеры» в виде изображения, а графики в реальном времени предназначены только для шоу?

Если это так, вы могли бы подождать несколько секунд и затем показать результат?

Похоже, WritableBitmap может стать способом решения вашей проблемы.Я бы предположил, что каждый раз, когда у вас есть блокировка / разблокировка, возникают накладные расходы, так как это связано с тем, что происходит, - поэтому я не думаю, что это хорошая идея для каждой точки.Чтобы рассчитать время, вы можете использовать профилировщик для тестового проекта / тестовых данных - dotTrace из jetbrains в порядке - я думаю, что у них есть пробная версия.Вы также можете использовать счетчик производительности - который может быть полезен и для других вещей.

Я бы сделал его многопоточным и имел бы высокоприоритетный поток для обработки точек ввода - или вы получаете прерывания с вашего устройства?Как я понимаю, более важно получить всю точку, чем правильно нарисовать все точки.

Вы пишете, что WritableBitmap едва ли достаточно быстр - поэтому с вашим текущим решением я бы попытался сохранить вызовы в AddDirtyRect, так что это только вкаждые n точек / миллисекунд - передача во фронтбуфер должна быть быстрой, даже если это большой блок.Вы должны быть в состоянии получить его так же быстро с wpf, как и с формами - это просто лучше.

С некоторым кодом и дополнительной информацией в вашей системе было бы легче ответить:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...