#c# #memory #bitmap #parallel-processing #copy
#c# #память #растровое изображение #параллельная обработка #Копировать
Вопрос:
Мне нужно скопировать строки развертки из одного байтового * буфера в другой байтовый * буфер с разными шагами.
Для этого я использую RtlMoveMemory :
[DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]
private static unsafe extern void MoveMemory(void* dest, void* src, int size);
Обычный способ был бы чем-то вроде
for (int y = 0; y < height; y )
{
MoveMemory(dst_ptr, src_ptr, stride);
src_ptr = src_stride;
dst_ptr = dst_stride;
}
Мой вопрос — будет ли это быстрее с использованием параллельного класса?
Parallel.For(0, height, (y) =>
{
byte* src_ptr = src_base y * src_stride;
byte* dst_ptr = dst_base y * dst_stride;
MoveMemory(dst_ptr, src_ptr, line_width);
});
Или это просто негативно скажется на производительности?
Комментарии:
1. Что произошло, когда вы попробовали каждый из них?
2. Я со стыдом должен признать, что не смог попробовать, я нахожусь в процессе написания своего кодека ZMBV на C # (что может занять еще вечер или два, чтобы привести его в полезное состояние :)) и мне просто теоретически интересно, поможет или повредит ли использование параллельного класса в этомслучай.
3. Я действительно сомневаюсь в этом. Обработка не требуется; Я бы ожидал улучшения производительности, если бы вы что-то распараллеливали. Здесь нечего распараллеливать, кроме копирования, которое, вероятно, уже происходит так быстро, как только возможно. Параллельный класс может фактически замедлить его. Но единственный способ узнать наверняка — это протестировать и измерить.
4. Это тоже была моя догадка, но я недостаточно знаком с параллельными задачами / обработкой памяти, чтобы действительно знать. Я полагаю, что для начала я выберу «стандартный» способ, если только кто-нибудь более осведомленный не скажет мне, что здесь было бы полезно распараллеливание, или профилирование не скажет мне, что это работает 🙂
Ответ №1:
На самом деле, мое тестирование показывает, что построчное копирование выполняется быстрее. Например, я создал массив, представляющий растровое изображение размером 1024 x 768. Параллельная версия была на 45% быстрее.
Параллельная версия намного быстрее, когда строки сканирования длиннее. Однопоточная версия объемом менее 1 килобайта работает быстрее.
Протестировано с .NET 4.5, Visual Studio 2013. 64-разрядный режим. Компилируется с выпуском и запускается без подключенного отладчика.
private const int NumLines = 1024;
private const int LineLength = 768*3;
private const int ArraySize = NumLines*LineLength;
[DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]
private static unsafe extern void MoveMemory(void* dest, void* src, int size);
unsafe public void Test()
{
// initialize a big array to test the copy
var source = Enumerable.Range(0, ArraySize).Select(x => (byte)x).ToArray();
var dest = new byte[ArraySize];
fixed (byte* pSource = source, pDest = dest)
{
// test single threaded
// do it once for the JIT
Console.WriteLine("Testing single threaded...");
MoveSingleThread(pSource, pDest, NumLines, LineLength);
// Okay, time it.
var sw = Stopwatch.StartNew();
MoveSingleThread(pSource, pDest, NumLines, LineLength);
sw.Stop();
Console.WriteLine("Single threaded: {0:N0} ticks", sw.ElapsedTicks);
var singleTicks = sw.ElapsedTicks;
Console.WriteLine("Testing parallel");
// do it once for JIT
MoveParallel(pSource, pDest, NumLines, LineLength);
sw = Stopwatch.StartNew();
MoveParallel(pSource, pDest, NumLines, LineLength);
sw.Stop();
Console.WriteLine("Parallel: {0:N0} ticks", sw.ElapsedTicks);
var diff = sw.ElapsedTicks - singleTicks;
var pct = (double) sw.ElapsedTicks/singleTicks;
Console.WriteLine("Difference: {0:N0} ticks {1:P2}", diff, pct);
}
}
private unsafe void MoveSingleThread(byte* source, byte* dest, int nLines, int lineLength)
{
var srcPtr = source;
var dstPtr = dest;
for (int y = 0; y < nLines; y)
{
MoveMemory(dstPtr, srcPtr, lineLength);
srcPtr = lineLength;
dstPtr = lineLength;
}
}
unsafe void MoveParallel(byte* source, byte* dest, int nLines, int lineLength)
{
Parallel.For(0, nLines, (y) =>
{
byte* srcPtr = source y * lineLength;
byte* dstPtr = dest y * lineLength;
MoveMemory(dstPtr, srcPtr, lineLength);
});
}
Ответ №2:
Я делал это раньше и, по моему опыту, параллельно.For был медленнее при использовании для копирования каждой строки сканирования. Вместо этого попробуйте параллельный.Для для разделов изображения (несколько десятков или сотен строк развертки на поток). «Магическое» количество строк сканирования зависит от компьютера / памяти / процессора, поэтому вам нужно настроить его, чтобы получить оптимальную скорость передачи.
Конечно, как было предложено выше, вы должны попробовать каждый подход, чтобы увидеть, как он работает в вашем конкретном случае.
Комментарии:
1. Спасибо. Сейчас я пойду «стандартным» способом, а позже попробую параллельный вариант с более крупными фрагментами, чтобы посмотреть, как он работает.