GCC -ffast-math ve IEEE-754 Semantiği
-ffast-math yalnız daha agresif kayan noktalı optimizasyonu değildir; NaN, infinity, işaretli sıfır, reassociation ve errno gibi davranışlarda programın sayısal sözleşmesini değiştirebilir.
Kayan noktalı için cebirde doğru olan her dönüşüm program açısından doğru değildir.
(a + b) + c ile a + (b + c) reel sayılarda eşdeğerdir. IEEE-754 kayan noktalı altında yuvarlama nedeniyle farklı sonuç üretebilir. NaN, infinity ve işaretli sıfır eklendiğinde eniyileyici'ın yapabileceği dönüşümlerin sınırı daha da önem kazanır.
C/C++ performans optimizasyonunda -ffast-math bu sınırların bir kısmını bilinçli olarak gevşetir. Başarım ölçümü birkaç yüzde hızlanınca cazip görünür; fakat aslında yalnız performans seçeneği değil, sayısal semantik kararıdır.
Tek bir optimizasyon değildir
GCC dokümantasyonunda -ffast-math bir dizi alt seçeneği etkinleştirir.
Bunlar kayan noktalı işlemlerinde standart tarafından beklenen bazı davranışların korunmayabileceği varsayımını derleyiciye verir.
Eniyileyici ifadeleri yeniden ilişkilendirebilir, NaN/Infinity oluşmayacağını varsayabilir, işaretli sıfır farkını önemsemeyebilir, errno/trapping davranışında daha gevşek olabilir ve ters değer yaklaşımı kullanabilir.
Bu yüzden kod incelemesi'da yalnız seçenek adını görmek yeterli değildir. Hangi varsayımların algoritma için güvenli olduğu incelenmelidir.
Reassociation sonucu değiştirir
Şu toplamı düşünelim:
x = 1e20
y = -1e20
z = 3.14(x + y) + z yaklaşık 3.14 üretir.
x + (y + z) ise y + z aşamasında 3.14 bilgisini sayısal hassasiyet nedeniyle kaybedebilir ve sonuç 0 olabilir.
Eniyileyici büyük indirgeme döngüsü'larında daha iyi SIMD kullanmak için toplama sırasını değiştirmek isteyebilir.
Bilimsel hesaplama veya DSP tarafında bu değişiklik kabul edilebilir de olabilir, olmayabilir de. Cevap algoritmanın error budget'ına bağlıdır.
NaN yalnız hata değildir
NaN bazen veri akışındaki geçersiz örneği bilinçli olarak taşımak için kullanılır.
Kod if (x != x) ile NaN kontrolü yapıyor olabilir. Derleyici NaN oluşmayacağı varsayımına izin veren seçenek altında bu tür kontroller beklenmedik şekilde optimize edilebilir.
Ben sensör, sinyal işleme veya model çıkarım öncesi sayısal işlem hattında NaN politikasının açık olmasını tercih ediyorum. "Normalde gelmez" üretim sözleşmesi değildir.
NaN kabul edilmiyorsa girişte validate edilmelidir. Kabul ediliyorsa derleyici seçeneği bu semantiği korumalıdır.
Signed zero neden önemli olabilir?
IEEE-754 +0.0 ve -0.0 değerlerini ayırır.
Çoğu business uygulamasında fark görünmez. Fakat 1/+0.0 ve 1/-0.0 farklı infinity işaretleri üretir. Complex arithmetic ve bazı branch-cut hesaplarında işaret anlam taşır.
-fno-signed-zeros benzeri varsayımlar eniyileyici'a bu farkı yok sayma izni verir.
Bu karar uygulama domain'i bilinmeden güvenli kabul edilemez.
FMA sonucu neden farklıdır?
Fused multiply-add a * b + c işlemini tek rounding ile gerçekleştirebilir. Ayrı multiply ve add ise iki rounding yapar.
FMA genellikle hem hızlı hem daha doğru olabilir; buna rağmen bit seviyesinde eski sonuçla aynı değildir.
Regression test'i kayan noktalı sonucu exact byte equality ile karşılaştırıyorsa derleyici veya CPU değişiminde test kırılabilir.
Burada iki soru ayrılmalıdır: matematiksel hata daha mı küçük ve bitwise determinism korunuyor mu?
Kritik sistemde hangisinin gerektiği önceden tanımlanmalıdır.
SIMD için cazibesi
-ffast-math indirgeme ve vektörleştirme için derleyiciye daha geniş hareket alanı sağlar.
Büyük dizi toplamı, normalleştirme, dot product veya DSP kernel'larında önemli hızlanma görülebilir.
Fakat benim tercih ettiğim yöntem global seçenek açmaktan önce sıcak noktayı izole etmektir.
Bir uygulamanın yüzde 2'si yoğun kayan noktalı hesap yapıyorsa bütün ikili dosyanın semantiğini gevşetmek yerine ilgili translation unit veya function için kontrollü optimizasyon daha güvenlidir.
Başarım ölçümü sonucu da end-to-end ölçülmelidir. 2x hızlanan kernel toplam gecikmenin yüzde 5'iyse sistem yalnız yaklaşık yüzde 5 kazanır.
Deterministik sistemlerde ayrı risk
Aynı kaynak kod farklı derleyici sürümleri, optimization pass'leri ve CPU ISA'larında farklı işlem sırası üretebilir.
-ffast-math bu serbestliği artırır.
Bir model ön/son işleme hattında küçük sayısal fark tolerans içindeyse sorun olmayabilir. Fakat sağlama toplamı benzeri bitwise reproducibility, scientific regression veya distributed consensus içinde kayan noktalı sonucu kullanılıyorsa ciddi problem oluşabilir.
Kayan noktalı'ı consensus key yapmak zaten çoğu zaman kötü fikirdir; aggressive math bunu daha da kırılgan hale getirir.
Test nasıl yapılmalı?
Flag'i açmadan önce referans gerçekleştirim tutulmalıdır.
Aday optimize sürüm için absolute error, relative error, ULP farkı, NaN/Infinity davranışı, subnormal değerler, işaretli sıfır ve boundary girdiler ölçülmelidir.
Performance test ise aynı CPU frequency politikası, affinity ve workload ile yapılmalıdır.
Yalnız "unit test geçti" sayısal eşdeğerliği kanıtlamaz. Test set'i problem uzayını kapsamayabilir.
Global -Ofast kullanımına dikkat
GCC'de -Ofast, -O3 optimizasyonlarına ek olarak -ffast-math davranışlarını da açar.
Bu nedenle derleme config'te -O3 -> -Ofast değişikliği yalnız "bir seviye daha yüksek optimizasyon" değildir.
Sayısal sözleşme değişir.
Sürüm işlem hattında derleyici seçeneği'lerinin derleme çıktısı üstverisine yazılması bu nedenle değerlidir. Aynı commit'in farklı seçenek ile üretilmiş iki ikili dosyası aynı yazılım sürümü sayılmamalıdır.
Performans kazancı kanıt gerektirir
-ffast-math bazı workload'larda büyük kazanç sağlayabilir, bazılarında neredeyse hiç fark yaratmaz.
Flag'i dogmatik olarak yasaklamak da, varsayılan olarak açmak da doğru değil.
Doğru süreç sıcak noktayı ölçmek, hangi IEEE davranışlarının gerekli olduğunu yazmak, kontrollü eniyilenmiş derleme üretmek, sayısal hata'ı karşılaştırmak ve ancak kazanç anlamlıysa kullanmaktır.
Böylece derleyiciye verdiğimiz özgürlüğün karşılığında ne aldığımız bilinir.
Kayan noktalı optimizasyonunda en pahalı hata birkaç cycle kaybetmek değildir. Algoritmanın kabul ettiği matematiksel sınırı fark etmeden değiştirmektir.
Kaynakça
- GNU Project. GCC 15.2 Manual: Options That Control Optimization. https://gcc.gnu.org/onlinedocs/gcc-15.2.0/gcc/Optimize-Options.html
- IEEE. IEEE Std 754-2019: IEEE Standard for Floating-Point Arithmetic. 2019. https://standards.ieee.org/ieee/754/6210/
Fast-Math Bir Performans Bayrağından Fazlasıdır
-ffast-math derleyiciye yalnız "daha hızlı kod üret" demez; kayan nokta semantiği hakkında daha gevşek varsayımlar kurmasına izin verir. Bu serbestlik SIMD vektörleştirmesi ve yeniden ilişkilendirme için alan açabilir, ancak NaN, infinity, signed zero ve işlem sırası gibi gözlemlenebilir davranışları da etkileyebilir.
Bu nedenle kazanç yalnız benchmark süresiyle değil, kabul edilen sayısal hata ve yeniden üretilebilirlik sınırıyla birlikte ölçülmelidir.