SearchValues<T>, seyrek ayırıcılar (delimiters) için büyük tamponları (buffers) tararken .NET 10'da ölçülebilir bir avantaj sağlıyor; 32 MB'lık bir veri yükünde SearchValues 3.3 ms'de tamamlanırken, IndexOfAny 5.6 ms sürüyor.
SearchValues neden ortaya çıktı
.NET 8, bir karakter (veya bayt) kümesini önceden hesaplamak ve çalışma zamanının (runtime) bu küme için en hızlı tarama algoritmasını seçmesine olanak tanımak amacıyla SearchValues<T>'yi tanıttı. Nesneyi bir kez oluşturun ve bir span içindeki değerlerden herhangi birini bulmanız gerektiğinde tekrar kullanın. Çalışma zamanı; vektörleştirilmiş talimatlar (vectorized instructions), dallanmasız döngüler (branch-free loops) veya el yazımı alternatiflerin büyük bir çaba sarf etmeden yakalayamayacağı diğer düşük seviyeli hileleri kullanabilir.
Önemli olan kıyaslamalar (Benchmarks)
Tartışmayı başlatan testler, beş ayırıcı karakter içeren 32 MB'lık bir tampon kullandı. .NET 10 üzerinde üç yaklaşım zamanlandı:
- IndexOfAny(char[]) – 10.2 ms
- Span üzerinde manuel foreach döngüsü – 19.4 ms
- SearchValues
– 9.7 ms
SearchValues, yerleşik IndexOfAny yöntemini sadece yarım milisaniye farkla geçti. Sonuç, forumlarda dolaşan o çarpıcı "beş kat daha hızlı" iddiası gibi değil; modern .NET zaten küçük kümeler için IndexOfAny yöntemini optimize ederek aradaki farkı kapatıyor.
Ayırıcılar nadir olduğunda —her 60 baytta bir yerine her 40 KB'da bir göründüğünde— tablo değişiyor:
- IndexOfAny(char[]) – 5.6 ms
- SearchValues
– 3.3 ms
Artık SearchValues 1.7 kat daha hızlı, çünkü motor vektörleştirilmiş adımlarla eşleşmeyen verilerin uzun dizileri boyunca hızla ilerlerken, IndexOfAny daha az agresif bir yola başvuruyor.
Gizli maliyet
SearchValues bedava değildir. Nesneyi oluşturmak bellek ayırır ve dahili arama tabloları (lookup tables) inşa eder. Eğer her metot çağrısında onu örneklendirirseniz (instantiate), oluşan yük (overhead) çalışma zamanındaki her türlü kazancı gölgede bırakır. Kısa satırlarda tarama rutinini bir milyon kez çağıran bir kıyaslama şunu gösterdi:
- Statik (yeniden kullanılan) SearchValues – toplam 25.1 ms
- Çağrı başına SearchValues – toplam 70.2 ms
Nesneyi her çağrı için oluşturmak, tüm işlemi SearchValues hiç kullanmamaktan üç kat daha yavaş hale getirir. Örneği (instance) bir static readonly alanda saklayın veya çağrılar arasında yeniden kullanın.
Ne zaman SearchValues kullanılmalı
- Büyük girdiler, az eşleşme – Tarayıcı, bir ayırıcıya çarpmadan uzun bölümleri atlayabildiğinde vektörleştirilmiş yol parlar.
- Aynı küme ile tekrarlanan taramalar – Eğer aynı karakterler birçok kez aranıyorsa, tek seferlik kurulum kendini amorti eder.
Ne zaman IndexOfAny ile kalınmalı
- Kısa dizeler veya çok sayıda eşleşme – Ek kurulum maliyeti, marjinal hız kazancından daha ağır basar.
- Zaten IndexOfAny kullanan kodlar – Modern .NET'in uygulaması küçük karakter kümeleri için zaten oldukça optimize edilmiştir, bu nedenle
SearchValuesile değiştirmek fark yaratmayabilir.
Yaygın hatalar
- Nesne oluşturmayı yoğun döngülerin (hot loops) içine gömmek – Yukarıda gösterilen üç katlık yavaşlamaya yol açar.
- Manuel döngülerle çalışma zamanını "atlatmaya" çalışmak – Manuel
foreachsürümü, her iki yerleşik yöntemden de iki kat daha yavaştı; bu da .NET kütüphanelerini SIMD talimatları hakkında derin bilgi sahibi olmadan yenmenin zor olduğunu doğruluyor. - Evrensel hızlanmalar varsaymak – Yoğun verideki yarım milisaniyelik kazanç,
SearchValues'un evrensel bir "hile kodu" olmadığını kanıtlar; faydaları veriye bağlıdır.
Özet
SearchValues<T>, yalnızca büyük tamponları seyrek karakterler için taradığınızda ve tek seferlik kurulum maliyetini karşılayabildiğinizde net bir avantaj sağlar. Günlük dize arama senaryolarının çoğunluğu için —kısa girdiler, sık eşleşmeler veya zaten IndexOfAny'ye dayanan kodlar— yerleşik yöntem daha basit ve daha hızlı bir seçenek olmaya devam eder. SearchValues'u seçici olarak kullanın, örneği önbelleğe alın; böylece gerçek performans kazancını elde ederken gizli cezadan kaçınmış olursunuz.
Kaynak kod ve tam test paketi: https://dev.to/ssukhpinder/when-searchvalues-actually-pays-off-310l
