Son zamanlarda şirketimde farklı veri yapıları üzerinde bir kıyaslama yaptım, bu yüzden bir kelime bırakmam gerektiğini hissediyorum. Bir şeyi doğru bir şekilde kıyaslamak çok karmaşıktır.
Kıyaslama
Web'de nadiren iyi tasarlanmış bir kıyaslama buluyoruz (eğer varsa). Bugüne kadar sadece gazetecilerin yöntemiyle yapılan ölçütler buldum (oldukça hızlı ve düzinelerce değişkeni halının altına süpüren).
1) Önbellek ısıtmayı düşünmeniz gerekir
Kriterleri çalıştıran çoğu insan, zamanlayıcı tutarsızlığından korkar, bu nedenle işlerini binlerce kez çalıştırırlar ve tüm zamanı alırlar, sadece her işlem için aynı bin kez almaya özen gösterirler ve sonra bunu karşılaştırılabilir olarak değerlendirirler.
Gerçek şu ki, gerçek dünyada bu pek mantıklı değil çünkü önbelleğiniz ısınmayacak ve operasyonunuz muhtemelen sadece bir kez çağrılacak. Bu nedenle, RDTSC kullanarak kıyaslama yapmanız ve onları yalnızca bir kez çağırarak zaman yapmanız gerekir. Intel, RDTSC'nin nasıl kullanılacağını açıklayan bir makale hazırladı (boru hattını temizlemek için bir cpuid talimatı kullanarak ve onu stabilize etmek için programın başında en az 3 kez çağırarak).
2) RDTSC doğruluk ölçüsü
Bunu da yapmanızı tavsiye ederim:
u64 g_correctionFactor; // number of clocks to offset after each measurement to remove the overhead of the measurer itself.
u64 g_accuracy;
static u64 const errormeasure = ~((u64)0);
#ifdef _MSC_VER
#pragma intrinsic(__rdtsc)
inline u64 GetRDTSC()
{
int a[4];
__cpuid(a, 0x80000000); // flush OOO instruction pipeline
return __rdtsc();
}
inline void WarmupRDTSC()
{
int a[4];
__cpuid(a, 0x80000000); // warmup cpuid.
__cpuid(a, 0x80000000);
__cpuid(a, 0x80000000);
// measure the measurer overhead with the measurer (crazy he..)
u64 minDiff = LLONG_MAX;
u64 maxDiff = 0; // this is going to help calculate our PRECISION ERROR MARGIN
for (int i = 0; i < 80; ++i)
{
u64 tick1 = GetRDTSC();
u64 tick2 = GetRDTSC();
minDiff = std::min(minDiff, tick2 - tick1); // make many takes, take the smallest that ever come.
maxDiff = std::max(maxDiff, tick2 - tick1);
}
g_correctionFactor = minDiff;
printf("Correction factor %llu clocks\n", g_correctionFactor);
g_accuracy = maxDiff - minDiff;
printf("Measurement Accuracy (in clocks) : %llu\n", g_accuracy);
}
#endif
Bu bir tutarsızlık ölçücüsüdür ve zaman zaman -10 ** 18 (64 bit ilk negatif değerler) elde etmekten kaçınmak için ölçülen tüm değerlerin minimumunu alacaktır.
Satır içi montaj yerine içsel kullanımına dikkat edin. İlk satır içi montaj, günümüzde derleyiciler tarafından nadiren destekleniyor, ancak hepsinden daha kötüsü, derleyici, iç kısmı statik olarak analiz edemediği için satır içi montaj etrafında tam bir sipariş engeli oluşturuyor, bu nedenle bu, özellikle yalnızca öğeleri çağırırken gerçek dünya materyallerini karşılaştırmak için bir sorundur. bir Zamanlar. Dolayısıyla, burada bir içsellik uygundur, çünkü derleyicinin talimatları ücretsiz olarak yeniden sırasını bozmaz.
3) parametreler
Son sorun, insanların genellikle senaryonun çok az varyasyonunu test etmeleridir. Bir kapsayıcı performansı şunlardan etkilenir:
- Ayırıcı
- içerilen tipin boyutu
- kopyalama işleminin uygulama maliyeti, atama işlemi, taşıma işlemi, inşaat işlemi, içerilen tipte.
- kaptaki elemanların sayısı (problemin boyutu)
- tür önemsiz 3. işlemlere sahiptir
- tip POD'dur
Nokta 1 önemlidir, çünkü kapsayıcılar zaman zaman tahsis eder ve CRT "yeni" veya havuz tahsisi veya serbest liste veya diğer gibi kullanıcı tanımlı bir işlemi kullanarak ayırmaları çok önemlidir ...
( pt 1 ile ilgilenen kişiler için , gamedev'de sistem ayırıcı performans etkisi hakkındaki gizemli konuya katılın )
Nokta 2, bazı kapların (örneğin A) etrafındaki şeyleri kopyalarken zaman kaybedeceğidir ve tür ne kadar büyükse, ek yük o kadar büyük olur. Sorun şu ki, başka bir konteyner B ile karşılaştırıldığında, A, küçük tipler için B'yi kazanabilir ve daha büyük tipler için kaybedebilir.
3. nokta, 2. nokta ile aynıdır, ancak maliyeti bazı ağırlık faktörleriyle çarpmasıdır.
Nokta 4, önbellek sorunlarıyla karışık büyük bir sorudur. Bazı kötü karmaşık kapsayıcılar, az sayıdaki türler için düşük karmaşıklık düzeyine sahip kaplardan büyük ölçüde daha iyi performans gösterebilir ( önbellek konumları iyi olduğu, ancak belleği parçalara ayırdığı için map
vs. gibi ). Ve sonra bir kesişme noktasında kaybedecekler, çünkü içerilen toplam boyut ana belleğe "sızmaya" başlıyor ve önbellek ıskalarına neden oluyor, buna ek olarak asimptotik karmaşıklık hissedilmeye başlayabiliyor.vector
map
Nokta 5, derleyicilerin derleme zamanında boş veya önemsiz olan şeyleri elden çıkarabilmeleriyle ilgilidir. Bu, bazı işlemleri büyük ölçüde optimize edebilir, çünkü kapsayıcılar şablonludur, bu nedenle her türün kendi performans profili olacaktır.
Nokta 6, nokta 5 ile aynı, POD'lar, kopya yapısının sadece bir memcpy olduğu gerçeğinden faydalanabilir ve bazı kapsayıcılar, T'nin özelliklerine göre algoritmaları seçmek için kısmi şablon uzmanlıkları veya SFINAE kullanarak bu durumlar için özel bir uygulamaya sahip olabilir.
Düz harita hakkında
Görünüşe göre düz harita, Loki AssocVector gibi sıralı bir vektör sarmalayıcıdır, ancak C ++ 11 ile gelen bazı ek modernizasyonlarla, tek öğelerin eklenmesini ve silinmesini hızlandırmak için hareket semantiğinden yararlanır.
Bu hala düzenli bir konteynerdir. Çoğu insan genellikle sipariş kısmına, dolayısıyla varlığına ihtiyaç duymaz unordered..
.
Belki bir ihtiyacınız olduğunu düşündünüz mü flat_unorderedmap
? hangi bir şey google::sparse_map
veya bunun gibi bir şey olabilir - açık bir adres karma haritası.
Açık adres hash haritalarının sorunu rehash
, tahsis edilen veriler olduğu yerde kalırken, standart bir sırasız haritanın sadece hash indeksini yeniden oluşturması gerekiyorken, her şeyi yeni genişletilmiş düz araziye kopyalamak zorunda olmalarıdır. Elbette dezavantajı hafızanın cehennem gibi parçalanmış olmasıdır.
Bir açık adres karma haritasında yeniden çalışmanın kriteri, kapasitenin kova vektörünün boyutunun yük faktörü ile çarpımını aşmasıdır.
Tipik bir yük faktörü 0.8
; bu nedenle, buna dikkat etmeniz gerekir, eğer karma haritanızı doldurmadan önce önceden boyutlandırabilirseniz, her zaman önceden boyutlandırabilirseniz: intended_filling * (1/0.8) + epsilon
bu, doldurma sırasında her şeyi gizlice yeniden düzenlemeniz ve yeniden kopyalamanız gerekmeyeceğinin garantisini verecektir.
Kapalı adres haritalarının ( std::unordered..
) avantajı, bu parametrelerle ilgilenmenize gerek olmamasıdır.
Ancak, boost::flat_map
sıralı bir vektördür; bu nedenle, her zaman bir log (N) asimptotik karmaşıklığa sahip olacaktır, bu da açık adres karma haritasından (amortize edilmiş sabit zaman) daha az iyidir. Bunu da düşünmelisiniz.
Karşılaştırma sonuçları
Bu, farklı haritaları ( int
anahtarlı ve __int64
/ somestruct
değerli) ve std::vector
.
test edilen tür bilgileri:
typeid=__int64 . sizeof=8 . ispod=yes
typeid=struct MediumTypePod . sizeof=184 . ispod=yes
Yerleştirme
DÜZENLE:
Önceki sonuçlarımda bir hata vardı: Düz haritalar için çok hızlı bir davranış sergileyen sıralı eklemeyi gerçekten test ettiler.
Bu sonuçları daha sonra bu sayfada bıraktım çünkü ilginçler.
Bu doğru test:
Uygulamayı kontrol ettim, burada düz haritalarda uygulanan ertelenmiş sıralama diye bir şey yok. Her ekleme anında sıralama yapar, bu nedenle bu kıyaslama asimptotik eğilimleri sergiler:
map: O (N * log (N))
hashmaps: O (N)
vector and flatmaps: O (N * N)
Uyarı : bundan sonra s için 2 test std::map
ve her ikisi flat_map
de hatalı ve aslında sıralı yerleştirmeyi test ediyor (diğer kapsayıcılar için rastgele yerleştirmeye kıyasla. Evet kafa karıştırıcı üzgünüm):
Sıralı yerleştirmenin geri itmeye neden olduğunu ve son derece hızlı olduğunu görebiliriz. Bununla birlikte, kıyaslamamın çizelgesiz sonuçlarından, bunun bir geri ekleme için mutlak optimalliğe yakın olmadığını da söyleyebilirim. 10k elemanlarda, önceden ayrılmış bir vektör üzerinde mükemmel bir geri ekleme optimizasyonu elde edilir. Bu da bize 3 Milyon döngü verir; Buraya sıralı ekleme için 4,8 milyonu gözlemliyoruz flat_map
(bu nedenle optimalin% 160'ı).
Analiz: Bunun vektör için 'rastgele ekleme' olduğunu unutmayın, bu nedenle devasa 1 milyar döngü, her eklemede verilerin yarısını (ortalama olarak) yukarı doğru (bir öğeye bir öğe) kaydırmaktan kaynaklanır.
3 öğenin rastgele aranması (1'e yeniden normalleştirilmiş saatler)
boyutta = 100
boyutta = 10000
Yineleme
100'den büyük (yalnızca MediumPod tipi)
10000'den büyük (sadece MediumPod tipi)
Son tuz tanesi
Sonunda "Benchmarking §3 Pt1" (sistem ayırıcı) üzerine geri dönmek istedim. Yakın zamanda yaptığım bir deneyde , geliştirdiğim bir açık adres hash haritasının performansıyla ilgili olarak yapıyorum , bazı std::unordered_map
kullanım durumlarında Windows 7 ve Windows 8 arasında% 3000'den fazla bir performans farkı ölçtüm ( burada tartışılmıştır ).
Bu da okuyucuyu yukarıdaki sonuçlar hakkında uyarmak istememe neden oluyor (bunlar Win7'de yapıldı): kilometreniz değişebilir.
Saygılarımla