2010-12-15 2 views
11

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

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

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

Edit:

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

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

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

+0

Можете ли вы быть немного более конкретно: что такое «много»? И пиксель действительно подходящий «прямоугольник» для вас? спасибо –

+0

@Simon: Конечно! Я ожидаю, возможно, от 10.000 до 100.000 точек данных в секунду. Одно измерение не превышает нескольких секунд.Пикселы достаточно прямоугольны для меня =) – Jens

+0

@Jens Что касается вашего обновления, я предложил использовать второй поток для переноса данных буфера в WritableBitmap, чтобы точно решить проблему, которую вы описываете. –

ответ

10

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

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

Update

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

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

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

Update 2

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

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

Update 3

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

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

Update 4

Далее в ответ на мой вопрос, казалось бы, что в настоящее время «блокировка \ разблокировка» вызывается для каждого из 100k обновлений в секунду. Это то, что, вероятно, приводит к гибели. На моей (мощной) системе я измерил 100k «lock \ unlock» на ~ 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); 
} 
+0

Спасибо! Я обязательно посмотрю на GC с низкой задержкой! – Jens

+0

@Jens Дайте мне знать, как вы поживаете, мне интересно узнать, как вы справляетесь с требованиями в реальном времени. Возможно, вам придется использовать другие методы оптимизации, такие как запись в задний буфер, а затем периодическое перенос на битмап, чтобы получить необходимый вам перфект - 10 мкс довольно минимальны! –

+0

Смешно, установка GC на LowLatency несколько ухудшает измеренную производительность в некоторых настройках и не помогает другим. WriteableBitmap кажется (едва) достаточно быстрым. Я начну небольшую щедрость, чтобы увидеть, есть ли еще идеи. – Jens

2

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

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

1

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

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

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

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

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

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

2

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

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

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