Microsoft에서 포기한 Framework 사용자를위한 또 다른 버전이 있습니다. Panos Theof의 솔루션 과 Eric J 와 Petar Petrov의 병렬 솔루션Array.Clear
보다 4 배 빠릅니다. 최대 2 배 빠른 대형 배열로 -.
먼저 코드를 이해하기 쉽기 때문에 함수의 조상을 보여 드리고자합니다. 성능 측면에서 이것은 Panos Theof의 코드와 거의 비슷하며 이미 충분할 수도 있습니다.
public static void Fill<T> (T[] array, int count, T value, int threshold = 32)
{
if (threshold <= 0)
throw new ArgumentException("threshold");
int current_size = 0, keep_looping_up_to = Math.Min(count, threshold);
while (current_size < keep_looping_up_to)
array[current_size++] = value;
for (int at_least_half = (count + 1) >> 1; current_size < at_least_half; current_size <<= 1)
Array.Copy(array, 0, array, current_size, current_size);
Array.Copy(array, 0, array, current_size, count - current_size);
}
보시다시피, 이것은 이미 초기화 된 부분의 반복되는 배가에 기초합니다. 이것은 간단하고 효율적이지만 현대 메모리 아키텍처를 무시합니다. 따라서 캐시 친화적 인 시드 블록을 만들기 위해 두 배만 사용하는 버전이 탄생 한 후 대상 영역에 반복적으로 분사됩니다.
const int ARRAY_COPY_THRESHOLD = 32; // 16 ... 64 work equally well for all tested constellations
const int L1_CACHE_SIZE = 1 << 15;
public static void Fill<T> (T[] array, int count, T value, int element_size)
{
int current_size = 0, keep_looping_up_to = Math.Min(count, ARRAY_COPY_THRESHOLD);
while (current_size < keep_looping_up_to)
array[current_size++] = value;
int block_size = L1_CACHE_SIZE / element_size / 2;
int keep_doubling_up_to = Math.Min(block_size, count >> 1);
for ( ; current_size < keep_doubling_up_to; current_size <<= 1)
Array.Copy(array, 0, array, current_size, current_size);
for (int enough = count - block_size; current_size < enough; current_size += block_size)
Array.Copy(array, 0, array, current_size, block_size);
Array.Copy(array, 0, array, current_size, count - current_size);
}
참고 : 이전 코드 (count + 1) >> 1
는 최종 복사 작업에 남은 모든 항목을 포괄 할 수있는 충분한 사료를 확보하기 위해 배가 루프의 제한으로 필요 했습니다. count >> 1
대신 에 홀수 카운트 를 사용 하는 경우에는 그렇지 않습니다 . 현재 버전의 경우 선형 복사 루프가 느슨해지지 않기 때문에 이것은 중요하지 않습니다.
배열 셀의 크기는 매개 변수로 전달되어야합니다. 마인드 보글-제네릭은 앞으로 사용할 수 있거나 없을 수 sizeof
있는 제약 조건 ( unmanaged
) 을 사용 하지 않으면 사용할 수 없기 때문입니다. 잘못된 추정은 큰 문제는 아니지만 다음과 같은 이유로 값이 정확한 경우 성능이 가장 좋습니다.
요소 크기를 과소 평가하면 L1 캐시의 절반보다 큰 블록 크기로 이어질 수 있으므로 L1에서 복사 소스 데이터가 제거되고 느린 캐시 레벨에서 다시 가져와야 할 가능성이 높아집니다.
요소 크기를 과대 평가하면 CPU의 L1 캐시가 충분히 활용되지 않으므로 선형 블록 복사 루프가 최적의 활용보다 자주 실행됩니다. 따라서, 고정 루프 / 호출 오버 헤드의 많은 부분이 반드시 필요한 것보다 많이 발생합니다.
여기 내 코드를 훔치는 벤치 마크가 있습니다. Array.Clear
와 앞에서 언급 한 다른 세 가지 솔루션이 있습니다. 타이밍은 Int32[]
주어진 크기의 정수 배열 ( ) 을 채우기위한 것 입니다. 캐시 차고 등으로 인한 변동을 줄이기 위해 각 테스트는 두 번 연속으로 실행되었으며 두 번째 실행에 대한 타이밍이 사용되었습니다.
array size Array.Clear Eric J. Panos Theof Petar Petrov Darth Gizka
-------------------------------------------------------------------------------
1000: 0,7 µs 0,2 µs 0,2 µs 6,8 µs 0,2 µs
10000: 8,0 µs 1,4 µs 1,2 µs 7,8 µs 0,9 µs
100000: 72,4 µs 12,4 µs 8,2 µs 33,6 µs 7,5 µs
1000000: 652,9 µs 135,8 µs 101,6 µs 197,7 µs 71,6 µs
10000000: 7182,6 µs 4174,9 µs 5193,3 µs 3691,5 µs 1658,1 µs
100000000: 67142,3 µs 44853,3 µs 51372,5 µs 35195,5 µs 16585,1 µs
이 코드의 성능이 충분하지 않으면 유망한 길은 선형 복사 루프 (모든 스레드가 동일한 소스 블록을 사용함) 또는 우리의 좋은 친구 P / Invoke를 병렬화하는 것입니다.
참고 : 블록 지우기 및 채우기는 일반적으로 MMX / SSE 명령어를 사용하여 고도로 특수화 된 코드로 분기되는 런타임 루틴에 의해 수행되므로 적절한 환경에서는 해당하는 각각의 도덕적 수준을 호출하고 std::memset
전문적인 성능 수준을 보장합니다. IOW는 권리에 따라 라이브러리 기능 Array.Clear
은 모든 수동 버전을 먼지에 남겨 두어야합니다. 그것이 다른 방법이라는 사실은 실제로 얼마나 많은 것들이 있는지를 보여줍니다. Fill<>
그것은 여전히 핵심과 표준에만 있지만 프레임 워크에는 없기 때문에 처음부터 자신을 굴려야 합니다. .NET은 거의 20 년 동안 존재 해 왔으며 여전히 가장 기본적인 것들을 위해 좌우로 P / Invoke를해야합니다.