1 puan yazan GN⁺ 2024-01-06 | 1 yorum | WhatsApp'ta paylaş

Kod hızını artırma: AMD64'te 16 bayttan büyük struct'lar geçirmeyin

  • Neat dilinin performansını artırmak için, bir diziyi tek bir struct parametresi yerine üç pointer parametresi olarak geçirecek şekilde değişiklik yapıldı.
  • Neat dizilerinin D dilindeki dizilerden daha yavaş olmasının nedeni, 24 baytlık dizi boyutunun 16 baytı aşması ve bu yüzden parametrelerin farklı bir yöntemle geçirilmesidir.
  • SystemV AMD64 ABI belirtimine göre, 16 baytı aşan tüm struct'lar pointer üzerinden geçirilir.

Benchmark ile sorunun doğrulanması

  • Benchmark ile, struct geçirme yöntemi ile ayrı alanları geçirme yöntemi arasındaki performans farkı doğrulandı.
  • Struct geçirildiğinde stack üzerinde ayırma ve kopyalama süreci gerekirken, ayrı alanlar geçirildiğinde bunlar doğrudan SSE register'ları üzerinden iletilir.
  • Ayrı alanları geçirme yöntemi, struct geçirme yöntemine kıyasla yaklaşık 2 kat daha hızlı performans gösterdi.

Dil tasarımcısının seçimi

  • C API çağrılırken C ABI'ye uyulması gerekir, ancak dahili olarak kullanılan yüksek seviyeli tiplerin struct olarak ifade edilmesi gerekmez.
  • Dil tasarımcısı; dizilerin, tuple'ların ve sum type'ların nasıl geçirileceğine karar verebilir.
  • 16 baytı aşan tipleri ayrı alanlar olarak geçirmek, performans artışına yardımcı olabilir.

GN⁺ görüşü

  • Bu yazı, yazılım optimizasyonuyla ilgilenen geliştiriciler için oldukça faydalı.
  • Özellikle performansa duyarlı uygulamalar geliştirirken, struct boyutunun ve geçirme yönteminin önemli etkiler yaratabileceğini gösteriyor.
  • Dil tasarımcıları veya API geliştiricileri, bu bilgiyi performansı iyileştirme fırsatı olarak kullanabilir.

1 yorum

 
GN⁺ 2024-01-06
Hacker News yorumu
  • SysV amd64 ABI sorunu bağlamında, dil içi ABI SysV dışında bir şey olarak ayarlanabilir. SysV C çağıranlarına açığa çıkarılmadığı sürece istenen çağrı kuralı kullanılabilir. NeatLang’in farkı, LLVM çağrı kuralını değiştirmekten çok daha karmaşık görünüyor ve yazar C programlarına türleri belirli bir çağrı kuralıyla açığa çıkarmak istiyor olabilir.
  • Argüman iletme maliyeti konusunda çoğu zaman yeterli kavrayış yoktur ve bu konuda yazılanlar faydalıdır. Örneğin Google’da 24 baytlık bir nesneyi değer olarak geçirme pratiği profiler’da görünmez ama her fonksiyonda bir maliyet yaratır.
  • x64’e geçerken, vec3 nesnelerinin (3xfloat) 12 bayttan 16 bayta genişlemesi konusunda endişelenip grafik motorunu benchmark etmişler. 16 bayt kullanmanın 8 baytlık okumalara hizalandığı için daha hızlı olduğunu bulmuşlar. Sonuç olarak vec3, vec4 gibi kullanılmış. Her zaman bütünsel benchmark yapılması tavsiye edilir.
  • Register’lara önceden yüklenmiş argümanlar, stack yazımından daha yüksek performans verir ve stack işlemleri de heap allocation’dan daha hızlıdır. Bu yüzden çok sayıda global değişken kullanan karmaşık kod hızlı çalışabilirken, zarif recursive fonksiyonlar veya tuple/struct/list argümanları yavaş olabilir. İlki yoğun assembly döngülerine optimize edilmeye daha uygundur.
  • MSVC’de 8 baytı aşan struct’lar stack üzerinden geçirilir. Bu, taşınabilir kodda güvenilmemesi gereken bir ABI ayrıntısıdır. Ancak sık çağrılmayan fonksiyonlar için fazla strese girmemek, sık çağrılan küçük fonksiyonlarda ise derleyicinin kodu inline edebilmesine izin vermek gerekir; bu, argümanları register üzerinden geçirmekten daha faydalı optimizasyonları da etkinleştirir.
  • Windows’ta varsayılan cdecl çağrı kuralı kullanıldığında 8 bayttan büyük struct’lar register’larda geçirilmez.
  • amd64 üzerinde sysv amd64 ABI kullanarak 16 baytı aşan struct’ları değer olarak geçirmek ve döndürmek yavaştır, ancak kodu daha anlaşılır kıldığı için çoğu zaman buna değer. Elbette bu örnekte durum böyle değil, ama örneğin her C++ derleyicisi, Golang, OCaml ve SBCL kendi dil içlerinde özel bir ABI kullanabilir.
  • C++’ta kaba bir deneyim kuralı olarak, ilkel olmayan türler için geçerli bir neden yoksa referansla (veya gerekirse pointer ile) geçirme tercih edilmelidir. Bunun bir nedeni ABI’dir, bir nedeni de copy veya move constructor’lardan kaçınmaktır. Performans optimizasyonu istiyorsanız C++’ta dikkat etmeniz gereken sıkıcı düşük seviye ayrıntılardan biridir.
  • Makale çok özel bir benchmark’a bağlantı veriyor; burada Java (JIT) C++’tan daha hızlı, hatta Scala’dan bile hızlı. Bu da Julia HO’nun ne olduğu ve neden bu kadar hızlı olduğu, Python ile PyPy arasındaki hız farkının neden büyük olduğu ve PyPy kullanmamak için bir neden olup olmadığı, bunun standart hâline gelip gelmemesi gerektiği gibi sorular doğuruyor.
  • Verilen örnekte, çağıranı etkilemeden struct Vector parametre türü const struct Vector & referansı olarak geçirilecek şekilde değiştirilebilir. Pointer bug’larının bulunduğu pek çok C++ kodunda pointer’lar gereksiz yere kullanılmıştı; referansla geçirmek hem daha kolay hem de daha güvenli kullanılabilirdi.