strstr,std::string::findgibi API'ler tek seferlik alt dize aramasını varsayar, ancak modern CPU'larda kısa dizeleri ve vektörleri karşılaştırmanın maliyeti düşük olduğundan SIMD yaklaşımı avantajlı olabilir- Temel fikir, Karp-Rabin'in zayıf hash koşulunu bir vektör yüklemine dönüştürmek ve yalnızca aday konumlarda kesin alt dize karşılaştırması yapmaktır
- Genel amaçlı SIMD algoritması, adayları azaltmak için needle'ın ilk karakteri ile son karakterini paralel olarak karşılaştırır ve yalnızca eşleşme olasılığı olan konumları
memcmpile doğrular - SSE4.1
MPSADBWve SSE4.2PCMPESTRMyaklaşımları da karşılaştırılır, ancak ölçüm sonuçlarına göre genel amaçlı SIMD daha tutarlı biçimde daha hızlıdır vePCMPESTRM,MPSADBW'dan da yavaştır - Genel amaçlı SIMD, tüm platformlarda C
strstr'den daha hızlıydı; ancak girdi dizisinin dışından okuma yapabildiği için güvenli değildir ve uzunluk bilgisini önceden aldığı için karşılaştırma koşulları da tamamen aynı değildir
Dize aramada değişen maliyet varsayımları
- C'deki
strstr, C++std::string::find, Python dizelerindekipos,indexgibi API'ler, verilen dizide bir alt diziyi bulmaya yönelik tek seferlik arama için tasarlanmıştır - Mevcut algoritmalar genel olarak iki büyük gruba ayrılır
- Knuth-Morris-Pratt, Boyer Moore gibi deterministik sonlu otomat tabanlı yöntemler
- Karp-Rabin gibi basit karşılaştırma tabanlı yöntemler
- Standart algoritmalar, bir karakter çifti karşılaştırmanın, yardımcı tabloya bakmanın ve koşullu dallanmanın ucuz; iki alt diziyi karşılaştırmanın ise pahalı olduğu varsayımına dayanır
- Modern masaüstü CPU'larda bu varsayım iyi çalışmayabilir
- 64 bit CPU'larda 1, 2, 4 ve 8 bayt karşılaştırmanın maliyeti aynıdır
- SIMD komutları destekleniyorsa 16, 32, 64 baytlık vektör karşılaştırmaları da tek bayt karşılaştırması kadar ucuz olabilir
- Tablo erişimi, L1 önbellek gidiş-dönüşü düzeyinde bellek erişim maliyeti yaratır; karakter bazlı okuma da benzer maliyet taşır
- Yanlış tahmin edilen dallanmalar yaklaşık 10~20 çevrim ceza yaratabilir
- Karakter okuma, karşılaştırma ve koşullu dallanmadan oluşan kısa bağımlılık zinciri, CPU'nun out-of-order yürütmeden yararlanmasını zorlaştırabilir
Yaklaşım: zayıf hash yerine vektör yüklemi kullanmak
- Karp-Rabin, yalnızca hedef alt dizinin zayıf hash'i ile mevcut dize parçasının hash'i eşit olduğunda kesin karşılaştırma yapar
- SIMD yaklaşımı bu hash koşulunu bir vektör yüklemi ile değiştirir
- Yüklem paralel hesaplanır
- Yüklem vektöründe doğru olan her konum için kesin alt dize karşılaştırması yapılır
- Uzunluğa göre özelleştirme de performans iyileştirmesi için kullanılır
- Genel uygulama alt dize karşılaştırması için
memcmpgibi bir fonksiyon çağırır - Aranacak alt dizinin uzunluğu biliniyorsa, fonksiyon çağrısı belirli uzunluğa uygun birkaç CPU komutuyla, bazen tek komutla değiştirilebilir
- Bu yöntem fonksiyon çağrısı maliyetini ve
memcmpiçindeki maliyeti azaltır
- Genel uygulama alt dize karşılaştırması için
Algoritma 1: Genel amaçlı SIMD
- Genel amaçlı SIMD algoritması, SIMD komut kümelerinin geneline ve SWAR yaklaşımına uygulanabilir
- Temel yüklem, needle'ın ilk karakteri ile son karakterinin ikisinin de eşleşip eşleşmediğini kontrol etmektir
- İlk karakter
Fyazmacına doldurulur - Son karakter
Lyazmacına doldurulur - Her yinelemede haystack'in mevcut ofseti
ikonumundanAparçası,i + k - 1konumundan daBparçası okunur F == AveB == Lhesaplanır, ardından iki sonuç birleştirilerek aday konum maskesi üretilir- Kesin alt dize karşılaştırması yalnızca maskede doğru olan konumlarda yapılır
- İlk karakter
"cat"örneğinin"a_cat_tries"içinde aranmasında, ilk karaktercve son karaktertyalnızca indeks 2'de birlikte tuttuğu için kesin karşılaştırma da sadece bir kez yapılır- İlk ve son karakteri seçmek her zaman iyi bir tercih değildir
- Girdi dizisi büyük ölçüde
'A'içeriyor ve needle"AjohndoeA"ise çok sayıda aday oluşabilir - Uygulama, son karakter yerine ilk karakterden farklı olan en uzak karakteri seçebilir
- Needle'ın tüm karakterleri aynıysa
"AAAAA"gibi desenler için özel bir prosedür kullanılabilir
- Girdi dizisi büyük ölçüde
Uygulamalar arasındaki farklar
- SSE ve AVX2 uygulamaları yapı olarak neredeyse aynıdır ve en az sayıda komut kullanır
- İlk ve son karakterin zaten eşit olduğu bilindiğinden,
memcmpiçinde bu karakterleri yeniden karşılaştırmaya gerek yoktur
- İlk ve son karakterin zaten eşit olduğu bilindiğinden,
- SWAR yaklaşımı, XOR sonucunun 0 olması halinde baytların eşit olduğu özelliğinden yararlanır
- SSE/AVX2'deki gibi ara sonuçları AND ile birleştirmek yerine OR kullanır
- 0 bayt konumlarını bulma süreci daha fazla iş gerektirir
- 64 bit vektörler için C++ uygulaması doğru bayt maskesini hesaplar
- AVX512F'te bayt işlemleri yoktur ve en küçük vektör öğesi 32 bit sözcük olduğundan SWAR tekniği gerekir
- İki XOR ve bir OR, AVX512F komutlarıyla hesaplanır
- 32 bit öğeler içinde 0 bayt içeren öğeler bulunur
- İlgili her 32 bit öğe için 4 aday alt dize kontrol edilir
- ARM Neon 32 bit uygulaması 128 bit SIMD yazmaçları kullanır
- Neon biriminden CPU'ya geri dönüşteki uzun gidiş-dönüş darboğaz oluşturur
- Karşılaştırma sonuçları işlenmek üzere belleğe 64 bit sözcükler olarak yazılır
- İç döngünün 2 kez unroll edilmesi yaklaşık 1.2 kat hız sağlar
- AArch64 uygulaması ARM Neon prosedürüyle neredeyse aynıdır, ancak SIMD yazmaç lane'lerini doğrudan okuma işlemi daha hızlıdır
Algoritma 2: SSE4.1 MPSADBW
- SSE4.1 ve AVX2'deki
MPSADBW, bir yazmacın 4 baytlık alt vektörü ile başka bir yazmacın ardışık 8 adet 4 baytlık alt vektörü arasındaki Manhattan uzaklığını hesaplar - İki alt vektör eşitse L1 uzaklığı 0 olur; bu da aday konum aramada kullanılabilir
- Bu yaklaşım yüklem olarak ilk 4 karakterin eşitliğini kullanır
- İlk 4 karakter karşılaştırıldığı için, bu yöntem ilk ve son karakter karşılaştırmasından daha güçlü görünse de en kötü durumda ikinci dereceden karmaşıklığı önleyemez
- Haystack
"a"ile dolu, needle ise"aaaabcde"ise yüklem tüm girdi karakterlerinde doğru olur
- Haystack
MPSADBWyaklaşımının bazı kısıtları vardır- Temelde uzunluğu 4'ten kısa alt dizeler için uygun değildir
- Uzunluk 3 için kullanılabilir, ancak ek kod gerekir
- SSE
MPSADBWaynı anda yalnızca 8 bayt işler - AVX2
MPSADBW, tam 256 bit yerine 128 bit lane bazında çalıştığı için ek yükleme kodu gerektirir - Komut gecikmesi CPU'ya bağlı olarak 5~7 çevrim ile yüksektir, ancak throughput 1~2 çevrim olduğu için döngü unrolling ile gecikme gizlenebilir
- AVX512F'te
MPSADBWyoktur; ancak 32 bit öğeler doğal olarak desteklendiğinden 4 baytlık prefix eşitliği yüklemi taklit edilebilir- Her yinelemede, olası 4 baytlık prefix'leri içeren 4 AVX512 vektörü oluşturulur
- Her vektör kurulumu için 2 shift ve 1 OR gerekir
- Karşılaştırma döngüsü karmaşıklaşır ve son öğeyi doldurmak için vektörün ötesinden 4 bayt gerekir
Algoritma 3: SSE4.2 PCMPESTRM
- SSE4.2, dize işlemleri için yapı taşları olmayı hedefleyen STNI komut kümesini tanıttı
- Intel daha sonraki işlemcilerde STNI'yi fiilen bırakmış, AVX2 sürümünü de sunmamış ve bu komutlar oldukça yavaş kalmıştır
- 11 çevrim gecikme kabul edilemez olarak değerlendirilir
PCMPxSTRxkomutlarının, dize uzunluğunu belirleme şekline ve sonucu saklama biçimine göre dört varyantı vardır- Uzunluk açıkça verilebilir ya da geleneksel C dizelerinde olduğu gibi ilk 0 bayt son kabul edilebilir
- Sonuç bit/bayt maskesi veya maskede ayarlı ilk/son bitin numarası olarak saklanabilir
- Burada
range orderedkarşılaştırma modu kullanılır- Adına rağmen, alt dize ya da yazmaç genişliğini aşan durumlarda onun prefix'ini bulur
- Örnekte
"ABCD","ABCD_ABC_ABCD_AB"içinde aranırken suffix"AB"de eşleşme olarak değerlendirilebilir - Bu nedenle güvenle varsayılabilecek tek şey ilk karakter eşleşmesidir; geri kalanı
memcmpile doğrulanmalıdır
Performans ölçüm ortamı
- Farklı SIMD uygulamalarının performansı ölçüldü; kısa alt dizeler için çalışma zamanı özelleştirmesi de buna dahildi
- Karşılaştırma için C
strstrde kullanıldı, ancak GNU libc içindekistd::string::findperformans hatası nedeniyle x64 tablolarına eklenmedi - Testlerde her program üç kez çalıştırıldı
- x64 test ortamı
- Westmere i5 M540, GCC 6.2.0
- Bulldozer FX-8150, GCC 4.8.4
- Haswell i7 4470, GCC 5.4.1
- Skylake i7 6700, GCC 5.4.1
- Knights Landing 7210, GCC 5.3.0
- ARM test ortamı
- ARMv7 Raspberry Pi 3, 32 bit kod, GCC 4.9.2
- ARMv8 ARM Cortex A57 / AMD Opteron A1100, 64 bit kod, Clang 3.8.0
x64 sonuçları
- Genel SIMD uygulaması x64'te
strstr'ye kıyasla en büyük hız artışını gösterdi- Westmere'de SSE2 generic 0.74589 saniye,
strstrise 0.82246 saniye - Haswell'de AVX2 generic 0.38653 saniye,
strstrise 0.52786 saniye - Skylake'te AVX2 generic 0.36309 saniye,
strstrise 0.66148 saniye - KNL'de AVX512F generic 1.14057 saniye,
strstrise 4.94606 saniye
- Westmere'de SSE2 generic 0.74589 saniye,
- En yüksek hızlanma Westmere'de 1.10 kat, Haswell'de 1.37 kat, Skylake'te 1.82 kat, KNL'de 4.33 kat oldu
- Bulldozer'deki
strstrperformansı 9.37792 saniye ile çok kötüydü ve sağlam bir referans olarak kullanmak zordu MPSADBWyaklaşımı Skylake dışında genel olarak iyi sonuç vermedi; KNL'de özellikle kötüydüPCMPESTRMyaklaşımıMPSADBW'dan da daha yavaştı
ARM sonuçları
- ARMv7'de
std::strstr7.30405 saniye,std::string::findise 4.17131 saniyeydi - ARMv7'de ARM Neon 32 bit generic 1.29861 saniye ile
std::string::find'e göre en fazla 3.1 kat daha hızlıydı - ARMv8'de
std::strstr3.37546 saniye,std::string::findise 1.81368 saniyeydi - ARMv8'de AArch64 64 bit generic 0.27897 saniye ile
std::string::find'e göre en fazla 6.5 kat daha hızlıydı - ARMv8'de SWAR 64 bit generic 0.46269 saniye ile 32 bit SIMD prosedürüne yakın performans gösterdi
Sonuç ve sınırlamalar
- Genel SIMD algoritması tüm platformlarda C
strstr'den daha hızlıydı - Uygulamada, belirli CPU'da kullanılabilen en yüksek SIMD sürümünü seçmek en iyisidir
MPSADBW, Skylake istisnası dışında iyi performans göstermedi ve Knights Landing'de çok kötüydüPCMPESTRM,MPSADBW'dan daha düşük performans verdi- ARM Neon performansı, SWAR uygulaması da dahil olmak üzere iyiydi
- SWAR sürümü
string::find'den 1.7 kat daha hızlıydı - SIMD sürümü
string::find'den 3.1 kat daha hızlıydı
- SWAR sürümü
strstrile karşılaştırma tamamen adil olmayabilirstrstr, uzunluğu bilinmeyen dizeleri işler- Bu uygulamalar uzunluğu parametre olarak alır ve bundan yararlanır
- Uygulama güvenli değildir
- Girdi dizisinin dışındaki verileri okuyabilir
- Dize, eşlenmemiş belleğin hemen öncesindeyse erişim ihlali oluşabilir
- Address sanitizer da sorun bildirebilir
- Güvenli hale getirmek mümkündü, ancak amaç bu değildi
- Tüm uygulamalar ve test programları GitHub üzerinde bulunabilir
Henüz yorum yok.