[.net] Array.Copy 대 Buffer.BlockCopy
Array.Copy 및 Buffer.BlockCopy는 모두 동일한 작업을 수행하지만 BlockCopy
빠른 바이트 수준의 기본 배열 복사를 목표로하는 반면 Copy
범용 구현입니다. 내 질문은-어떤 상황에서 사용해야 BlockCopy
합니까? 기본 유형 배열을 복사 할 때 언제라도 사용해야합니까, 아니면 성능을 위해 코딩하는 경우에만 사용해야합니까? Buffer.BlockCopy
over 사용 에 대해 본질적으로 위험한 것이 Array.Copy
있습니까?
답변
의 매개 변수 Buffer.BlockCopy
는 인덱스 기반이 아니라 바이트 기반이므로을 사용하는 것보다 코드를 망칠 가능성이 더 높으므로 코드 의 성능이 중요한 섹션 Array.Copy
에서만 사용 Buffer.BlockCopy
합니다.
답변
전주곡
나는 늦게 파티에 참여하고 있지만, 조회수가 3 만 2 천명으로이 문제를 바로 잡을 가치가 있습니다. 지금까지 게시 된 답변의 대부분의 마이크로 벤치마킹 코드는 테스트 루프에서 메모리 할당을 이동하지 않고 (심각한 GC 아티팩트를 유발 함), 변수 대 결정적 실행 흐름, JIT 워밍업을 테스트하지 않는 등 하나 이상의 심각한 기술적 결함이 있습니다. 테스트 내 변동성을 추적하지 않습니다. 또한 대부분의 답변은 다양한 버퍼 크기와 다양한 기본 유형의 영향을 테스트하지 않았습니다 (32 비트 또는 64 비트 시스템과 관련하여). 이 질문을 좀 더 포괄적으로 다루기 위해, 저는 가능한 한 대부분의 일반적인 “고장난”을 줄이는 사용자 지정 마이크로 벤치마킹 프레임 워크에 연결했습니다. 테스트는 32 비트 컴퓨터와 64 비트 컴퓨터 모두에서 .NET 4.0 릴리스 모드로 실행되었습니다. 결과는 평균 20 회 테스트 실행으로, 각 실행에는 방법 당 1 백만 번의 시행이있었습니다. 테스트 된 기본 유형은byte
(1 바이트), int
(4 바이트) 및 double
(8 바이트). 세 가지 방법은 테스트되었습니다 : Array.Copy()
, Buffer.BlockCopy()
루프, 그리고 간단한 당 인덱스 할당. 여기에 게시하기에는 데이터가 너무 방대하므로 중요한 사항을 요약하겠습니다.
테이크 아웃
- 당신의 버퍼 길이가 75 ~ 100 이하에 대한 경우, 명시 적 루프 복사 루틴은 더 빨리 (약 5 % 정도) 중 하나 이상의 보통
Array.Copy()
또는Buffer.BlockCopy()
32 비트 및 64 비트 시스템에서 테스트 3 개 원시 유형. 또한 명시 적 루프 복사 루틴은 두 가지 대안에 비해 성능의 변동성이 눈에 띄게 낮습니다. 좋은 성능은 거의 확실하게 메서드 호출 오버 헤드가없는 CPU L1 / L2 / L3 메모리 캐싱에 의해 악용되는 참조 지역성 때문 입니다.- 들어
double
버퍼 32 비트 시스템에서만 : 명시 적 루프 복사 루틴은 모든 버퍼 모두 대안이 100,000까지 시험 지정 크기보다 낫다. 개선은 다른 방법보다 3-5 % 우수합니다. 의 성능 때문입니다Array.Copy()
과는Buffer.BlockCopy()
완전히 네이티브 32 비트 폭을 통과에 따라 저하된다. 따라서 동일한 효과가long
버퍼 에도 적용될 것이라고 가정합니다 .
- 들어
- 버퍼 크기가 ~ 100을 초과하는 경우 명시 적 루프 복사는 다른 두 가지 방법보다 훨씬 느려집니다 (방금 언급 한 특정 예외가 있음). 차이점은
byte[]
큰 버퍼 크기에서 명시 적 루프 복사가 7 배 이상 느려질 수있는에서 가장 두드러 집니다. - 일반적으로, 3 종류의 프리미티브에 대해 시험 및 모든 버퍼 크기에 걸쳐,
Array.Copy()
와Buffer.BlockCopy()
거의 동일하게 수행 하였다. 평균적Array.Copy()
으로 약 2 % 이하의 시간이 소요되는 매우 경미한 가장자리가있는 것 같습니다 (그러나 0.2 %-0.5 % 더 나은 것이 일반적입니다)Buffer.BlockCopy()
. 알 수없는 이유로Buffer.BlockCopy()
는Array.Copy()
. 이 효과는 여러 완화를 시도하고 그 이유에 대한 작동 가능한 이론이 없음에도 불구하고 제거 할 수 없습니다. Array.Copy()
“스마트”하고 더 일반적이며 훨씬 안전한 방법 이기 때문에 매우 약간 더 빠르고 평균적으로 변동성이 적기 때문에Buffer.BlockCopy()
거의 모든 일반적인 경우에 선호되어야합니다 .Buffer.BlockCopy()
훨씬 더 나은 유일한 사용 사례 는 소스 및 대상 배열 값 유형이 다른 경우입니다 (Ken Smith의 답변에서 지적했듯이). 이 시나리오는 흔하지 않지만Array.Copy()
.NET의 직접 캐스팅에 비해 지속적인 “안전한”값 유형 캐스팅으로 인해 여기에서 성능이 매우 떨어질 수 있습니다Buffer.BlockCopy()
.- 동일한 유형의 배열 복사
Array.Copy()
보다 빠른 StackOverflow 외부의 추가 증거는 여기Buffer.BlockCopy()
에서 찾을 수 있습니다 .
답변
사용하는 것이 합리적 일 때의 또 다른 예는 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);
이것은 안전한 C # (void *)
에서 C 및 C ++에서 흔히 볼 수있는 일종의 메모리 관리에 가깝습니다 .
답변
내 테스트에 따르면 성능은 Array.Copy보다 Buffer.BlockCopy를 선호하는 이유 가 아닙니다 . 내 테스트에서 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
답변
ArrayCopy는 BlockCopy보다 똑똑합니다. 소스와 대상이 동일한 배열 인 경우 요소를 복사하는 방법을 알아냅니다.
int 배열을 0,1,2,3,4로 채우고 적용하면 :
Array.Copy (배열, 0, 배열, 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에는이 기능이 없습니다.
답변
이 주장을 고려하기 위해이 벤치 마크를 작성하는 방법을주의하지 않으면 쉽게 오도 될 수 있습니다. 나는 이것을 설명하기 위해 매우 간단한 테스트를 작성했습니다. 아래의 테스트에서 Buffer.BlockCopy를 먼저 시작하거나 Array.Copy를 시작하는 것 사이에서 테스트 순서를 바꾸면 가장 먼저 진행되는 것이 거의 항상 가장 느립니다 (가장 가까운 것임에도 불구하고). 이것은 내가 단순히 테스트를 여러 번 실행하지 않는 여러 가지 이유 때문에 다른 하나가 정확한 결과를 제공하지 못한다는 것을 의미합니다.
나는 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;
}
}
답변
BlockCopy가 Array.Copy보다 ‘성능’이점이 없음을 다시 보여주는 테스트 사례를 추가하고 싶습니다. 내 컴퓨터의 릴리스 모드에서 동일한 성능을 보이는 것 같습니다 (둘 다 5 천만 개의 정수를 복사하는 데 약 66ms가 걸립니다). 디버그 모드에서 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);
}