İşte bizim için Microsoft tarafından terk edilen Framework kullanıcıları için başka bir sürüm. Panos Theof'un çözümü ile Eric J ve Petar Petrov'un paralel olanından 4 kat daha hızlı Array.Clear
ve daha hızlı - büyük diziler için iki kata kadar daha hızlıdır.
Öncelikle size işlevin atalarını sunmak istiyorum, çünkü bu kodu anlamayı kolaylaştırıyor. Performans açısından bu Panos Theof'un koduna eşittir ve zaten yeterli olabilecek bazı şeyler için:
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);
}
Gördüğünüz gibi, bu zaten başlatılmış olan parçanın tekrarlanan iki katına dayanmaktadır. Bu basit ve verimlidir, ancak modern bellek mimarileri ile doludur. Bu nedenle, yalnızca önbellek dostu bir tohum bloğu oluşturmak için iki katına çıkaran ve daha sonra hedef alan üzerinde tekrarlanan şekilde patlatılan bir versiyon doğdu:
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);
}
Not: (count + 1) >> 1
son kopya işleminin kalan tüm öğeleri kapsayacak kadar yem içerdiğinden emin olmak için iki kat döngü için sınır olarak gerekli olan önceki kod . Bunun count >> 1
yerine tek sayı sayılırsa durum böyle olmaz . Mevcut sürüm için bu önemli değildir, çünkü doğrusal kopya döngüsü herhangi bir boşluğu alır.
Dizi hücresinin boyutu parametre olarak iletilmelidir, çünkü - zihin boggles - jeneriklerin gelecekte kullanılabilir olabilecek veya olmayabilecek sizeof
bir kısıt ( unmanaged
) kullanmadıkları sürece kullanılmasına izin verilmez . Yanlış tahminler önemli değildir, ancak değer doğruysa performans aşağıdaki nedenlerle en iyisidir:
Eleman boyutunu küçümsemek, L1 önbelleğinin yarısından daha büyük blok boyutlarına yol açabilir, böylece kopya kaynak verilerinin L1'den çıkarılma ve daha yavaş önbellek düzeylerinden yeniden getirilmesi olasılığı artar.
Eleman boyutunun fazla tahmin edilmesi, CPU'nun L1 önbelleğinin gereğinden az kullanılmasıyla sonuçlanır, yani doğrusal blok kopya döngüsü en uygun kullanımdan daha sık yürütülür. Böylece, sabit döngü / çağrı ek yükü kesinlikle gerekenden daha fazla gerçekleşir.
Kodumu karşılayan bir kıyaslama Array.Clear
ve daha önce bahsedilen diğer üç çözüm. Zamanlamalar Int32[]
, verilen boyutlardaki tamsayı dizilerini ( ) doldurmak içindir . Önbellek değişkenlerinin vb. Neden olduğu varyasyonu azaltmak için her test arka arkaya iki kez yürütüldü ve ikinci uygulama için zamanlamalar yapıldı.
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
Bu kodun performansı yeterli değilse, ümit verici bir yol doğrusal kopya döngüsünü (aynı kaynak bloğunu kullanan tüm iş parçacıklarıyla) veya eski dostumuz P / Invoke'u paralelleştirecektir.
Not: blokların temizlenmesi ve doldurulması normalde MMX / SSE talimatları ve ne olursa olsun, son derece özel bir koda dallanan çalışma zamanı rutinleri tarafından yapılır, bu nedenle herhangi bir iyi ortamda, sadece ilgili ahlaki eşdeğerini çağırır ve std::memset
profesyonel performans seviyelerinden emin olabilirsiniz. IOW, haklar gereği kütüphane fonksiyonu Array.Clear
tüm elle haddelenmiş versiyonlarımızı toz içinde bırakmalıdır. Bunun tam tersi olması, bu tür şeylerin ne kadar uzak olduğunu gerçekten gösterir. Aynı şey Fill<>
ilk etapta kendi başına haddeleme yapmak için de geçerlidir , çünkü hala sadece Çekirdek ve Standarttadır, ancak Çerçevede değildir. .NET neredeyse yirmi yıldır var ve hala en temel şeyler için sol ve sağ P / Invoke veya kendi rulo ...