Ответ 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);
}