Array.Copy и Buffer.BlockCopy обе делают то же самое, но BlockCopy
нацелено на быстрое копирование примитивного массива на уровне байта, тогда как Copy
является универсальной реализацией. Мой вопрос: при каких обстоятельствах вы должны использовать BlockCopy
? Следует ли использовать его в любое время при копировании массивов примитивных типов или использовать его только в том случае, если вы кодируете производительность? Есть ли что-то опасное в использовании Buffer.BlockCopy
над Array.Copy
?
Array.Copy vs Buffer.BlockCopy
Ответ 1
Поскольку параметры до Buffer.BlockCopy
основаны на байтах, а не на основе индексов, вы, скорее всего, испортите свой код, чем если бы вы использовали Array.Copy
, поэтому я бы использовал только Buffer.BlockCopy
критический раздел моего кода.
Ответ 2
Прелюдия
Я присоединяюсь к партии поздно, но с просмотрами 32 тыс., это стоит того, чтобы понять это правильно. Большая часть кода микрообнаружения в опубликованных ответах до сих пор страдает одним или несколькими серьезными техническими недостатками, в том числе не перенося выделения памяти из тестовых циклов (что вводит серьезные артефакты GC), не тестируя переменные и детерминированные потоки выполнения, разминку JIT, и не отслеживая изменчивость внутри теста. Кроме того, большинство ответов не проверяли влияние различных размеров буфера и различных примитивных типов (в отношении 32-разрядных или 64-разрядных систем). Чтобы более подробно рассмотреть этот вопрос, я подключил его к настраиваемой инфраструктуре микрообнаружения, которую я разработал, что максимально уменьшает большинство распространенных "gotchas". Тесты выполнялись в режиме выпуска .NET 4.0 на 32-разрядной машине и 64-битной машине. Результаты были усреднены в течение 20 испытаний, в которых каждый прогон имел 1 миллион испытаний на один метод. Примитивные типы были byte
(1 байт), int
(4 байта) и double
(8 байтов). Были протестированы три метода: Array.Copy()
, Buffer.BlockCopy()
и простое назначение каждого индекса в цикле. Эти данные слишком объемны для публикации здесь, поэтому я подведу основные моменты.
The Takeaways
- Если длина вашего буфера составляет около 75-100 или менее, то явная процедура копирования цикла обычно выполняется быстрее (примерно на 5%), чем
Array.Copy()
илиBuffer.BlockCopy()
для всех трех примитивных типов, протестированных как на 32-битных, так и на 64-битные машины. Кроме того, явная процедура копирования цикла имеет заметно меньшую изменчивость в производительности по сравнению с двумя альтернативами. Хорошая производительность почти наверняка обусловлена локалью ссылки, используемой кэшированием ЦП L1/L2/L3 в сочетании с отсутствием служебных вызовов метода.- Для
double
буферов только на 32-разрядных машинах. Явная процедура копирования циклов лучше, чем обе альтернативы для всех размеров буферов, проверенных до 100 КБ. Улучшение на 3-5% лучше, чем другие методы. Это связано с тем, что производительностьArray.Copy()
иBuffer.BlockCopy()
полностью ухудшается при передаче собственной 32-битной ширины. Таким образом, я предполагаю, что тот же эффект применим и к буферамlong
.
- Для
- Для размеров буфера, превышающих ~ 100, явное циклическое копирование быстро становится намного медленнее, чем другие 2 метода (с единственным исключением, которое только что было отмечено). Разница наиболее заметна с помощью
byte[]
, где явное циклическое копирование может стать 7x или более медленным при больших размерах буфера. - В общем, для всех трех примитивных типов, проверенных и во всех размерах буфера,
Array.Copy()
иBuffer.BlockCopy()
выполняются почти одинаково. В среднем,Array.Copy()
, кажется, имеет очень небольшой край примерно на 2% или меньше времени (но 0,2% - на 0,5% лучше), хотяBuffer.BlockCopy()
иногда избивали его. По неизвестным причинамBuffer.BlockCopy()
имеет заметно более высокую вариабельность внутри теста, чемArray.Copy()
. Этот эффект не может быть устранен, несмотря на то, что я пытаюсь несколько смягчить и не имею оперативной теории о том, почему. - Поскольку
Array.Copy()
является "более умным", более общим и более безопасным методом, в дополнение к тому, чтобы быть немного более быстрым и иметь меньшую изменчивость в среднем, он должен быть предпочтительнееBuffer.BlockCopy()
почти во всех распространенных случаях. Единственный прецедент, когдаBuffer.BlockCopy()
будет значительно лучше, когда типы значений источника и целевого массива отличаются (как указано в ответе Кена Смита). Хотя этот сценарий не является обычным явлением,Array.Copy()
может работать очень плохо из-за непрерывного "безопасного" типа, отличного от прямого литьяBuffer.BlockCopy()
. - Дополнительные доказательства из внешнего StackOverflow, которые
Array.Copy()
быстрее, чемBuffer.BlockCopy()
для копирования массива одного типа, можно найти здесь .
Ответ 3
Другим примером того, когда имеет смысл использовать Buffer.BlockCopy()
, является то, когда вам предоставляется массив примитивов (например, shorts) и нужно преобразовать его в массив байтов (скажем, для передачи по сети). Я часто использую этот метод при работе с аудио из Silverlight AudioSink. Он предоставляет образец как массив short[]
, но вам нужно преобразовать его в массив byte[]
, когда вы создаете пакет, который вы отправляете в Socket.SendAsync()
. Вы можете использовать BitConverter
и повторять по массиву один за другим, но это намного быстрее (около 20 раз в моем тестировании), чтобы сделать это:
Buffer.BlockCopy(shortSamples, 0, packetBytes, 0, shortSamples.Length * sizeof(short)).
И тот же трюк работает и в обратном направлении:
Buffer.BlockCopy(packetBytes, readPosition, shortSamples, 0, payloadLength);
Это примерно так же близко, как и вы, в безопасном С#, к управлению памятью (void *)
, которое так распространено в C и С++.
Ответ 4
Основываясь на моем тестировании, производительность не является основанием для предпочтения Buffer.BlockCopy над Array.Copy. Из моего теста Array.Copy на самом деле быстрее Buffer.BlockCopy.
var buffer = File.ReadAllBytes(...);
var length = buffer.Length;
var copy = new byte[length];
var stopwatch = new Stopwatch();
TimeSpan blockCopyTotal = TimeSpan.Zero, arrayCopyTotal = TimeSpan.Zero;
const int times = 20;
for (int i = 0; i < times; ++i)
{
stopwatch.Start();
Buffer.BlockCopy(buffer, 0, copy, 0, length);
stopwatch.Stop();
blockCopyTotal += stopwatch.Elapsed;
stopwatch.Reset();
stopwatch.Start();
Array.Copy(buffer, 0, copy, 0, length);
stopwatch.Stop();
arrayCopyTotal += stopwatch.Elapsed;
stopwatch.Reset();
}
Console.WriteLine("bufferLength: {0}", length);
Console.WriteLine("BlockCopy: {0}", blockCopyTotal);
Console.WriteLine("ArrayCopy: {0}", arrayCopyTotal);
Console.WriteLine("BlockCopy (average): {0}", TimeSpan.FromMilliseconds(blockCopyTotal.TotalMilliseconds / times));
Console.WriteLine("ArrayCopy (average): {0}", TimeSpan.FromMilliseconds(arrayCopyTotal.TotalMilliseconds / times));
Результат:
bufferLength: 396011520
BlockCopy: 00:00:02.0441855
ArrayCopy: 00:00:01.8876299
BlockCopy (average): 00:00:00.1020000
ArrayCopy (average): 00:00:00.0940000
Ответ 5
ArrayCopy умнее BlockCopy. Он определяет, как копировать элементы, если источник и место назначения являются одним и тем же массивом.
Если мы заполняем массив int 0,1,2,3,4 и применяем:
Array.Copy(array, 0, array, 1, array.Length - 1);
мы получим 0,0,1,2,3, как ожидалось.
Попробуйте это с помощью BlockCopy, и получим: 0,0,2,3,4. Если после этого я присвою array[0]=-1
, он станет равным -1,0,2,3,4, как и ожидалось, но если длина массива четная, например 6, мы получим -1,256,2,3,4,5. Опасные вещи. Не используйте BlockCopy, кроме как для копирования одного байтового массива в другой.
Есть еще один случай, когда вы можете использовать Array.Copy: если размер массива длиннее 2 ^ 31. Array.Copy имеет перегруз с параметром размера long
. BlockCopy этого не делает.
Ответ 6
Чтобы взвесить этот аргумент, если вы не будете осторожны, как они автор этого теста, они могут быть легко введены в заблуждение. Я написал очень простой тест, чтобы проиллюстрировать это. В моем тесте ниже, если я поменяю порядок моих тестов между запуском Buffer.BlockCopy сначала или Array.Copy тот, который идет первым, почти всегда является самым медленным (хотя и близким). Это означает, что по целому ряду причин, по которым я не буду просто запускать тесты несколько раз, esp один за другим не даст точных результатов.
Я прибегал к поддержке теста, как и 1000000 попыток для массива из 1000000 последовательных удвоений. Однако в то же время я игнорирую первые 900000 циклов и усредняю остаток. В этом случае буфер лучше.
private static void BenchmarkArrayCopies()
{
long[] bufferRes = new long[1000000];
long[] arrayCopyRes = new long[1000000];
long[] manualCopyRes = new long[1000000];
double[] src = Enumerable.Range(0, 1000000).Select(x => (double)x).ToArray();
for (int i = 0; i < 1000000; i++)
{
bufferRes[i] = ArrayCopyTests.ArrayBufferBlockCopy(src).Ticks;
}
for (int i = 0; i < 1000000; i++)
{
arrayCopyRes[i] = ArrayCopyTests.ArrayCopy(src).Ticks;
}
for (int i = 0; i < 1000000; i++)
{
manualCopyRes[i] = ArrayCopyTests.ArrayManualCopy(src).Ticks;
}
Console.WriteLine("Loop Copy: {0}", manualCopyRes.Average());
Console.WriteLine("Array.Copy Copy: {0}", arrayCopyRes.Average());
Console.WriteLine("Buffer.BlockCopy Copy: {0}", bufferRes.Average());
//more accurate results - average last 1000
Console.WriteLine();
Console.WriteLine("----More accurate comparisons----");
Console.WriteLine("Loop Copy: {0}", manualCopyRes.Where((l, i) => i > 900000).ToList().Average());
Console.WriteLine("Array.Copy Copy: {0}", arrayCopyRes.Where((l, i) => i > 900000).ToList().Average());
Console.WriteLine("Buffer.BlockCopy Copy: {0}", bufferRes.Where((l, i) => i > 900000).ToList().Average());
Console.ReadLine();
}
public class ArrayCopyTests
{
private const int byteSize = sizeof(double);
public static TimeSpan ArrayBufferBlockCopy(double[] original)
{
Stopwatch watch = new Stopwatch();
double[] copy = new double[original.Length];
watch.Start();
Buffer.BlockCopy(original, 0 * byteSize, copy, 0 * byteSize, original.Length * byteSize);
watch.Stop();
return watch.Elapsed;
}
public static TimeSpan ArrayCopy(double[] original)
{
Stopwatch watch = new Stopwatch();
double[] copy = new double[original.Length];
watch.Start();
Array.Copy(original, 0, copy, 0, original.Length);
watch.Stop();
return watch.Elapsed;
}
public static TimeSpan ArrayManualCopy(double[] original)
{
Stopwatch watch = new Stopwatch();
double[] copy = new double[original.Length];
watch.Start();
for (int i = 0; i < original.Length; i++)
{
copy[i] = original[i];
}
watch.Stop();
return watch.Elapsed;
}
}
Ответ 7
Просто хочу добавить свой тестовый пример, который снова показывает, что BlockCopy не имеет преимущества "PERFORMANCE" над Array.Copy. Кажется, что они имеют такую же производительность в режиме выпуска на моей машине (оба берут около 66 мс для копирования 50 миллионов целых чисел). В режиме отладки BlockCopy работает чуть быстрее.
private static T[] CopyArray<T>(T[] a) where T:struct
{
T[] res = new T[a.Length];
int size = Marshal.SizeOf(typeof(T));
DateTime time1 = DateTime.Now;
Buffer.BlockCopy(a,0,res,0, size*a.Length);
Console.WriteLine("Using Buffer blockcopy: {0}", (DateTime.Now - time1).Milliseconds);
return res;
}
static void Main(string[] args)
{
int simulation_number = 50000000;
int[] testarray1 = new int[simulation_number];
int begin = 0;
Random r = new Random();
while (begin != simulation_number)
{
testarray1[begin++] = r.Next(0, 10000);
}
var copiedarray = CopyArray(testarray1);
var testarray2 = new int[testarray1.Length];
DateTime time2 = DateTime.Now;
Array.Copy(testarray1, testarray2, testarray1.Length);
Console.WriteLine("Using Array.Copy(): {0}", (DateTime.Now - time2).Milliseconds);
}