Подтвердить что ты не робот

Быстрое рисование прямоугольников по одному в WPF

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

Вещи, которые я пробовал:

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

Edit:

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

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

Надеюсь, я разъяснил, каковы мои требования. Dang my English =)

4b9b3361

Ответ 1

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

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

Обновление

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

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

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

Обновление 2

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

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

Обновление 3

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

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

Обновление 4

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

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

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

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

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

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

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

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

Ответ 3

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

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

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

// 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

Ответ 4

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

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

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

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

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

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