Вот простая казалось бы вещь: скопировать кусок памяти с одного IntPtr в другой. И вдруг обнаруживается, что ничего такого в C# нет.
Придётся делать своё.
Сначала посмотрим, что вообще в C# имеется:
Копирование массивов по элементам:
source.CopyTo(destination, 0);
где source и destination - некоторые массивы. Будем считать его эталонным, с которым сравнивать всё остальное.
Далее, есть копирование массивов как байтовый блок - тот самый memcpy
Buffer.BlockCopy(source, 0, destination, 0, size);
Работает аж в 4 раза быстрее, чем предыдущий вариант. Но, увы, ничего не понимает в указателях.
Есть копирование в маршалинге между управляемым и неуправляемым кодом:
Marshal.Copy(sourcePointer, destination, 0, size);
Marshal.Copy(source, 0, destinationPointer, size);
Работает почему-то с переменным успехом: скорость "скачет" от максимума - как у Buffer до в два раза медленнее. Т.е. в 2-4 раза быстрее Array.CopyTo. Увы, понимает только один указатель.
Побайтовое копирование вручную:
byte* srcPtr = (byte*)sourcePointer.ToPointer(), dstPtr = (byte*)destinationPointer.ToPointer();
for(int i = 0; i < bigSize; ++i, ++srcPtr, ++dstPtr)
{
*dstPtr = *srcPtr;
}
Работает в 2 раза медленнее, чем Array.CopyTo и в 7 раз медленнее, чем Buffer.BlockCopy. Этим путём мы не пойдём.
Сделать обёртку типа Array над указателем никак - класс не наследуется. Можно только наоборот: получить указатель на массив.
Ничего не остаётся, как комбинировать цикл по указателям с блочными операциями маршалинга.
public unsafe static void memcpy(void* dst, void* src, int count)
{
const int blockSize = 4096;
byte[] block = new byte[blockSize];
byte* d = (byte*)dst, s = (byte*)src;
for(int i = 0, step; i < count; i += step, d += step, s += step)
{
step = count - i;
if(step > blockSize)
{
step = blockSize;
}
Marshal.Copy(new IntPtr(s), block, 0, step);
Marshal.Copy(block, 0, new IntPtr(d), step);
}
}
Работает в 2 раза быстрее Array.CopyTo, в 2 раза медленнее Buffer.BlockCopy, и стабильно как Marshal.Copy в худшем случае.
Опыты проводились на объёмах в 500 Мб.