Soruya cevap
Tarihsel olarak, Rust mikrobenchmarking'i, ölçümlerin geçersiz hale gelmesini önlemek için black_box fonksiyonu sunan istikrarsız test::Bencher crate'ine dayanıyordu. Ekosistem, stabil Criterion.rs ve özel benchmarking sistemlerine geçtikçe, derleyici içsel std::hint::black_box Rust 1.66'da bu amaç için standartlaştırılmış, sıfır maliyetli bir soyutlama sağlamak üzere istikrara kavuştu. Bu gelişim, derleyici optimizasyonlarının aşırı agresif ölümü ve performans mühendisliğinde belirli gecikme ölçümleri gereksinimi arasındaki temel gerilimi ele aldı.
Ana problem, programın mantığı tarafından kullanılmayan değerler üreten kodları benchmark yapmaktan kaynaklanmaktadır; örneğin, bir hash hesaplamak veya yan etkisi olmayan verileri ayrıştırmak. Rust derleyicisi, LLVM optimizasyonlarını kullanarak, bu hesaplamaların hiçbir gözlemlenebilir etkisi olmadığını belirler ve tamamen ortadan kaldırır, bu da benchmarkların yanlış düşük veya sıfır yürütme süreleri rapor etmesine neden olur. Bu optimizasyon, üretim kodu için faydalı olsa da, mikrobenchmarks'ları işe yaramaz hale getirir çünkü artık niyetlenen hesaplama işini ölçmezler.
std::hint::black_box bunu, sarılı değeri bilinmeyen bir dış varlık tarafından kullanılıyormuş gibi davranmaya zorlayarak çözmektedir. Hesaplamanın çıktısı için yapay bir kullanım yaratarak, derleyicinin tüm önceki talimatları korumasını sağlamalıdır; bu içsel kendisi hiçbir makine kodu üretmez. Bu, gecikme ölçümlerinin bütünlüğünü korumayı sağlar, çalışma zamanı yükü veya güvensiz bellek işlemleri getirir.
Hayattan bir durum
Bir ekip, yüksek frekanslı bir ticaret uygulamasında özel bir ikili format için bir ayrıştırıcıyı optimize etmektedir. 1MB yükü bin kez ayrıştıran bir Criterion.rs benchmark'ı yazıyorlar, ancak başlangıç sonuçları her iterasyon için sıfır nanosaniyelik imkansız bir verimlilik gösteriyor. Derleyici benchmark'ı analiz etmiş, ayrıştırılan çıktının asla tüketilmediğini anlamış ve tüm ayrıştırma döngüsünü ölü kod olarak ortadan kaldırmıştır; bu da performans verilerini anlamlı hale getirmemektedir.
Bir yaklaşım, sonucu volatile bir bellek konumuna yazmayı std::ptr::write_volatile kullanarak manuel olarak yapmak olmuştur. Bu, derleyicinin depolamaları yayımlamasını zorunlu kılarak hesaplamayı korur. Ancak, bu güvensiz kod gerektirir ve gerçek bellek trafiği getirerek önbellek hiyerarşilerini kirletir, gecikme ölçümlerini önbellek hatası senaryolarına kaydırır ve saf ayrıştırma mantığından uzaklaştırır.
Bir diğer seçenek, beklenen çıktının önceden hesaplanmış bir kontrol toplamı ile eşitliğini doğrulamaktır. Bu, hesaplamanın canlı kalmasını sağlarken, derleyici yine de iç dallarını optimize edebilir, eğer bunu sağlamak için ara durumları geçerli kılabiliyorsa. Ayrıca, doğrulama kendisi, karşılaştırma yükü ekler ve bu da ayrıştırma zamanı ile karışarak bench markın doğru olmasını engeller.
Üçüncü bir olasılık, bellek görünürlüğünü zorlamak için statik olarak tahsis edilmiş bir tampon üzerinde std::ptr::read_volatile kullanmaktı. Artıları: Değerin donanım seviyesinde gözlemlenmesini garanti eder. Eksileri: güvensiz kod gerektirir, gerçek bellek taşınımı ekler ve önbellek performans ölçümlerini çarpıtır, ayrıca hizalama veya aliasing kuralları ihlal edilirse tanımsız davranış tetikleyebilir.
Seçilen çözüm, benchmark tekrarından dönüş yapmadan önce son ayrıştırılan yapıyı std::hint::black_box ile sarmak olmuştur. Bu teknik, herhangi bir montaj talimatı veya bellek erişimi oluşturulmadan yapay bir veri bağımlılığı yaratır. Derleyici, bir dış gözlemcinin değeri incelediğini varsaymak zorunda kalır, bu nedenle tüm ayrıştırma hattını korur ve sıfır çalışma zamanı yükü ekler.
Sonuç olarak, her ayrıştırma için 450 mikrosaniyelik gerçekçi ölçüm elde edildi ve bu da önbellek yerleşimi sorununun sıfır maliyetli ölçümle gizlenmiş olduğunu ortaya çıkardı. Bu veri, ayrıştırıcının durum makinesini yeniden yapılandırma çabalarına yöneltti ve üretimde 3 kat verimlilik artışı sağladı.
Adayların sıkça gözden kaçırdığı noktalar
std::hint::black_box, korunmuş talimatların yeniden sıraya konmasını veya spekülatif olarak yürütülmesini önler mi, yoksa yalnızca derleyicinin optimizasyon geçişlerini mi kısıtlar?
std::hint::black_box yalnızca derleyici davranışını etkiler ve makine kodu engelleri oluşturmaz. CPU, bellek modeli tarafından izin verildiği ölçüde, dışarıdan yürütme, spekülatif yükleme ve önbellek satırı optimizasyonları gerçekleştirmek için serbesttir. Donanım seviyesinde zamanlama varyasyonlarını veya yan kanalları önlemek için geliştiricilerin inline assembly seri talimatları veya bellek setleri kullanmaları gerekir, black_box değil.
Black_box, zaman saldırılarına karşı kriptografik uygulamalarını korumak için neden uygunsuzdur, sürekli katlama önlese bile?
black_box, derleyicinin gizli bağımlı dalları kaldırmasını engellerken, donanımdan kaynaklanan mikro-mimari zamanlama sızıntılarını önlemez. Modern CPU'lar, derleyici optimizasyonlarından bağımsız olarak çalışan dal tahmini ve spekülatif yürütme kullanmaktadır. Sabit zamanlı kriptografik kod, spekülasyonu devre dışı bırakmak için algoritmik garantiler, volatile bellek erişimleri veya asm! blokları gerektirirken, black_box yalnızca kodun ikili dosyada görünmesini sağlar.
black_box, bir const bağlamında veya const fn değerlendirmesinde nasıl davranır?
const değerlendirmesi, derleme zamanında MIR yorumlayıcısı içinde gerçekleşir; burada "derleyici optimizasyonu" kavramı, makine kodu oluşturma ile aynı şekilde uygulanmaz. black_box, const değerlendirmesi sırasında etkili olarak bir no-op'tur ve platform içsel bileşenleri o bağlamda desteklenmediğinde derleme hatalarına neden olabilir. const bağlamlarındaki değerler tamamen değerlendirilir ve nihai ikili dosyaya iç içe geçirilir; dolayısıyla, black_box kaynak seviyesinde sabit yayılmayı önlemede anlamsızdır.