- Basit bir tamsayı toplama fonksiyonu derlenirken GCC ve Clang arasındaki optimizasyon farkının gözlemlendiği bir örnek
- GCC, döngü içinde iki sayıyı aynı anda toplayan
x*2 + 1 biçiminde bir döngü optimizasyonu uygular
- Clang ise döngüyü tamamen kaldırır ve hesaplamayı
v(v - 1)/2 kapalı formülüne dönüştürür
- Bu dönüşümle kod O(n)'den O(1)'e geçer ve matematiksel sadeleştirme otomatik olarak yapılır
- 20 yıldan uzun süredir derleyicilerle çalışan yazarı bile şaşırtacak kadar, derleyicilerin akıllı optimizasyon seviyesini gösteren bir örnek
GCC'nin döngü optimizasyonu
- Basit bir tamsayı toplama fonksiyonu yazıldığında GCC, döngü tabanlı verimli bir toplama kodu üretir
- Döngü içinde
lea edx, [rdx+1+rax*2] komutunu kullanarak iki sayıyı aynı anda toplar
- Bu,
x ile x+1 toplama işleminin x*2 + 1 biçimine dönüştürülmüş hâlidir
- Optimizasyon seviyesi
-O3 değerine çıkarıldığında paralel toplama (vectorization) ile döngü daha hızlı işlenir
- Bu yaklaşım, döngüyü korurken işlem verimliliğini en üst düzeye çıkaran geleneksel bir optimizasyon biçimidir
Clang'in matematiksel optimizasyonu
- Aynı kod Clang ile derlendiğinde döngü tamamen ortadan kalkar
- Clang, giriş değerinin pozitif olup olmadığını kontrol ettikten sonra bir dizi
lea, imul, shr komutuyla hesaplama yapar
- Sonuç olarak
(v² - v)/2, yani tamsayı toplamının kapalı formülü elde edilir
- Bu dönüşüm, döngüyü kaldırıp yerine sabit zamanlı (O(1)) bir hesaplama koyar
- Yazar bu sonucu, “tamsayı toplamının matematiksel çözümünü kendi kendine bulmuş” diye tanımlıyor
Formülün açılım süreci
- Clang'in ürettiği assembly kodu matematiksel olarak geriye doğru izlendiğinde şu dönüşüm ortaya çıkıyor
v + ((v - 1)(v - 2) / 2) - 1
- Bu ifade açılıp sadeleştirildiğinde
(v² - v)/2 sonucuna ulaşılıyor
- Sonunda
v(v - 1)/2 biçimi elde ediliyor; bu da 1'den v'ye kadar sayıların toplamı formülüyle örtüşüyor
- Bu süreç, derleyicinin matematiksel örüntüleri tanıyıp optimize etmesine bir örnek olarak sunuluyor
Derleyicinin akıllı davranışı
- Clang'in bu özel komut dizisini kullanma nedeni olarak taşmayı önleme ve türetilmiş değişkenleri izleme yöntemi gösteriliyor
- İçeride tam olarak ne olduğunun nedeni net değil, ancak bunun Clang'in iç optimizasyon mantığının birleşik bir sonucu olduğu belirtiliyor
- Yazar bu sonucu “alçakgönüllü kılan ve ilham veren bir deneyim” olarak ifade ediyor
Kapanış ve seri bağlamı
- Bu yazı ‘Advent of Compiler Optimisations 2025’ serisinin 24. yazısı
- Derleyicinin kod dönüşümleriyle matematiksel sadeleştirme ve performans artışı sağlamasını inceliyor
- Yazar, “derleyiciler hâlâ beni şaşırtıyor” diyerek uzun yıllara dayanan deneyime rağmen süren teknik hayranlığı vurguluyor
1 yorum
Hacker News yorumları
Bu özelliği uygulayan kod ScalarEvolution.cpp ve IndVarSimplify.cpp içinde yer alıyor
Bu tür optimizasyonlar gerçekten ilgi çekici
Derleyici optimizasyonları kabaca ikiye ayrılıyor — veri akışı analizi ve örüntü tanıma ile yerine koyma
İlki çoğu program için etkilidir, ikincisi ise giderek getirisi azalan bir örüntü biriktirme işidir
Bağlantı verilen örnek akıllıca ve eğlenceli, ama pratikte çok büyük bir değeri yok. 45 yıldır böyle döngüler yazmadım
Elbette bu tür örüntü dönüşümleri üst düzey koddan otomatik üretildiği için hâlâ önemlidir
Dürüst olmak gerekirse, benim optimizer bu optimizasyonu yapamadığı için biraz söyleniyor da olabilirim
Bu, Scalar Evolution(SCEV) denen bir özellik ve LLVM'de oldukça karmaşık biçimde uygulanmış
Benzer bir optimizasyon örneği olarak “Do not optimize away” yazısı var
Bu optimizasyonun gerçekten harika yanı genel(geneneric) olması
Sadece “sonlu tamsayı dizisinin toplamı” örüntüsünü tanımıyor, genel amaçlı biçimde uygulanabiliyor olması etkileyici
Derleyici daha fazla closed form da ekleyebilir gibi görünüyor. Buna değip değmeyeceğini merak ediyorum
İlgili bir kavram olarak Wilf–Zeilberger pair var
Bir kez daha GCC'nin Clang'den daha yavaş çıkmasına şaşırdım
GCC 20 yıl öndeydi ama buna rağmen Clang'in hâlâ daha hızlı kod ürettiği durumlar var
Eskiden bit çıkarma yaparken Clang iki shift ile işi bitirirken, GCC üç tane yapıyordu
Birisi hiç graf boyama problemi(graph coloring) için bunu sabitle değiştiren bir optimizasyon denedi mi diye merak ediyorum
Bu, uygulama ile belirtim arasındaki sınırı bulanıklaştıran bir örnek
Bir uygulama yazdığımızı sanıyoruz ama aslında belirtimin vekilini yazıyoruz
Yani derleyici, emredici bir makine yanılsaması üretiyor
İlk başta Matt'in böyle bir davranışı bilmiyor olmasına şaşırmıştım
Ben de üniversite yıllarımda LLVM IR ile oynarken özyinelemenin çarpmaya indirgenmesine tanık olup şok olmuştum
Matt'in bunu bilmiyor olması, bu optimizasyonun onun uğraşmadığı basit durumlara uygulandığı anlamına da gelebilir
YouTube video bağlantısı