4 puan yazan GN⁺ 2024-10-19 | 1 yorum | WhatsApp'ta paylaş

Go'da eşzamanlılık ve paralelliğin kullanımı

  • Sayı hesaplama yeteneğini artırmak için Go'nun eşzamanlılık ve paralellik özelliklerinden yararlanmayı amaçlayan bir projenin tanıtımı.
  • SIMD (Same Instruction Multiple Data) komutları kullanılarak donanım düzeyinde paralel hesaplama yapılabilir.
  • Go derleyicisi SIMD'den yararlanmadığı için ve uygun genel amaçlı bir SIMD paketi bulunamadığından, paketi doğrudan geliştirmeye karar verildi.

Plan9 assembly dili

  • Go, Plan9 adlı kendi assembly dilini kullanır; bu dil, belirli platformların komutlarını ve register'larını biraz değiştirerek kullanır.
  • x86 Plan9 ile ARM Plan9 birbirinden farklıdır.
  • Plan9'ın basit bir örneği üzerinden temel kullanım açıklanır.

Plan9 örneği

  • AddInts_amd64.s dosyası ve main.go dosyası üzerinden Plan9'da temel fonksiyon bildirimi ve kullanım biçimi açıklanır.
  • Go'nun çağrı kurallarına göre fonksiyon argümanlarının ve dönüş değerlerinin stack'te nasıl saklandığı anlatılır.

Paket tasarım planı

  • Aritmetik ve bit işlemleri için SIMD işlerine yönelik ince bir soyutlama katmanı sağlayan bir paket tasarlanır.
  • Mimariye özel Plan9 implementasyonlarını içeren bir iç paket oluşturulur ve bu yapı başlatma fonksiyonu üzerinden ayarlanır.

SIMD örneği

  • x86 SIMD Plan9 fonksiyon örnekleriyle SIMD'in kullanım biçimi açıklanır.
  • Supported_amd64.s ve AddFloat32_amd64.s dosyaları üzerinden SSE desteğinin nasıl kontrol edildiği ve float32 toplama işleminin nasıl yapıldığı gösterilir.

Performans ve gelecek

  • Go'nun yazılım implementasyonu ile Plan9 SIMD implementasyonu arasındaki performans farkını gösteren grafik üzerinden yaklaşık %200-450 hız artışı görüldüğü belirtilir.
  • Bu notların, Plan9 ve SIMD kullanan projeler için ilham vermesi umulur.

# GN⁺ özeti

  • Bu yazı, Go'nun eşzamanlılık ve paralellik özelliklerini kullanarak performansı en üst düzeye çıkarmanın yollarını tanıtır.
  • Plan9 assembly dili ve SIMD komutlarıyla donanım düzeyinde paralel hesaplamanın nasıl yapılabileceğini açıklar.
  • Go geliştiricileri için Plan9 ve SIMD'in kullanım potansiyelini gösterir ve performans artışı için yeni yaklaşımları keşfetmede faydalı olabilir.
  • Benzer işlevlere sahip projeler olarak Rust'un SIMD destek kütüphaneleri veya C++'taki SIMD ile ilgili kütüphaneler önerilir.

1 yorum

 
GN⁺ 2024-10-19
Hacker News yorumları
  • Go assembly hakkında birkaç noktaya değinmek gerekirse, amd64’te şu int’ler aslında 64 bittir.
    int32 kullanırsanız parametre listesinde word hizalaması olur, ama bir tuzak var: 64 bit sistemlerde dönüş değerleri her zaman doubleword hizalı bir offset’ten başlar.
    NOSPLIT, Go derleyicisinin otomatik olarak sağladığı textflag.h içinde tanımlıdır. Ancak okuduğum kadarıyla NOSPLIT yalnızca runtime.XX fonksiyonlarında dikkate alınıyor gibi; bu yüzden burada hiçbir şey yapmıyor ve gerekli de değil.
    NOSPLIT, derleyicinin, stack’in taşma ihtimaline karşı bölme gerekip gerekmediğini kontrol eden kodu eklememesi anlamına gelir. Stack alanına ihtiyaç duymayan bir fonksiyon için teknik olarak gereksizdir; temelde, stack bölmeyi kontrol eden fonksiyonun kendisinin içine bu kontrol kodunun enjekte edilmesini engellemek için vardır.

  • 4 değeri “NOSPLIT”i temsil ediyor ve bir nedenden gerekli denmiş; merak edenler için ekleyeyim: Normalde frame boyutunun (NOSPLIT’ten sonraki parametre) ardından argüman boyutu gelir ve ikisi eksi işaretiyle ayrılır.
    Bu bir çıkarma işlemi değil, sadece kendine özgü bir söz dizimidir. $24-8 frame boyutu, fonksiyonun 24 baytlık bir frame’e sahip olduğu ve çağıranın frame’indeki 8 baytlık argümanlarla çağrıldığı anlamına gelir.
    TEXT içinde NOSPLIT belirtmezseniz argüman boyutunu mutlaka vermeniz gerekir. Go prototipi olan assembly fonksiyonları için go vet, argüman boyutunun doğru olup olmadığını denetler.
    Kaynak: https://go.dev/doc/asm

  • “Go, Plan9 adlı kendi dahili assembly dilini kullanıyor” denmiş; bu dil gerçekten böyle mi adlandırılıyor?

    • Hayır. Sadece Go assembly.
      Sözdizimi Plan 9’dan türemiş olsa da biz ona Go assembly diyoruz.
      Bkz. https://go.dev/doc/asm
    • Geçerli bir soru. Başta ben de doğru olduğunu varsaymıştım. Bu konuyu araştıran birinin böyle bir şeyi yanlış yazmayacağını düşündüm; projeyi biraz biliyorsanız bu ad da bir ölçüde mantıklı geliyor.
      Ama daha fazla baktıkça bunun bir LLM halüsinasyonu olduğu hissine kapılıyorum.
      Assembly biçimi dokümanı buna düzgün bir ad vermiyor, sadece go assembler diyor.
      Bu halüsinasyonun kaynağı muhtemelen ilk paragraf: “Assembler, Plan 9 assembler’ının girdi stilini temel alır ve… bu belge söz diziminin bir özetini, farkları ve Go ile etkileşen assembly kodu yazarken dikkat edilmesi gereken özel noktaları açıklar.”
    • Ayrı bir adı yok. Plan 9 bir işletim sistemi; bu assembly söz dizimi stili de o işletim sisteminde kullanılan assembler’dan geliyor.
      “GNU Compiler Collection, Unix adlı kendi dahili assembly dilini kullanıyor” demeye benziyor.
  • Go ekibinin neden böyle özel bir assembly biçimini seçtiğini merak ediyorsanız, Rob Pike 2016’daki bir sunumunda Go assembler tasarımını ele almıştı [1][2].
    Ana fikir, çoğu assembly dilinin genel olarak birbirine benzemesinden hareketle, “makinenin en alt seviyesiyle konuşurken yeni bir söz dizimi öğrenme gereğini ortadan kaldıran” ortak bir assembly dili oluşturmak gibi görünüyor.
    Ayrıca yeni bir mimarinin instruction manual PDF’ini girdi olarak alıp çalışan assembler’ı otomatik üretmeyi de mümkün kılıyor.
    [1]: https://www.youtube.com/watch?v=KINIAgRpkDA
    [2]: https://go.dev/talks/2016/asm.slide#1

    • Gerçekten de işe yaradı. Go, yeni bir programlama dilinin cross-compilation’ı temel yetenek olarak sunması gerektiği beklentisini oluşturdu; o dönemde bunu iyi yapan dil neredeyse yoktu.
    • Not olarak, SpiderMonkey de yaklaşık 25 yıldır neredeyse aynı işi yapıyor; diğer JavaScript sanal makinelerinin de böyle yaptığını düşünüyorum.
  • “Slice’lar üzerinde SIMD işlemleri gerçekleştirecek fonksiyonlara ihtiyaç vardı” denmiş; bunun pratikte nasıl kullanıldığını merak etmiştim.
    Yazının tamamında arama yaptım ama slice’lar üzerinde yapılan herhangi bir işlem bulamadım.
    Düzenleme: Bağlantı verilen dokümanda buldum: https://pkg.go.dev/github.com/pehringer/simd#pkg-index
    Temel olarak toplamak istediğiniz 2 slice varsa, for döngüsü yerine SIMD ile paralel işleyip simd.AddInt32(slice1, slice2, result) gibi kullanabiliyorsunuz.

  • Go’nun desteklediği işlemcilerle ilgili olarak şu bölüm alakalı (1). Varsayılan x64 desteğine SSE ve SSE2 dahil.
    Ancak Go derleyicisinin bunları gerçekten üretip üretmediğini bilmiyorum. Performansı en yüksek öncelik yapan gcc gibi çok karmaşık derleyicilerin aksine, Go derleyicisi Wirth tarzı (2), basit ve hızlı bir derleyiciyi tercih ediyor.
    (1) https://go.dev/wiki/MinimumRequirements#amd64
    (2) https://irreal.org/blog/?p=7075
    https://smartgo.blog/2024/01/06/niklaus-wirth/

    • Gerçekte ise 2008 civarından sonraki tüm çiplerin SSE4.1 desteklediğini düşünüyorum.
  • Yazarın bu kısmı karıştırmış gibi göründüğü için referans bağlantısı bırakıyorum: https://en.wikipedia.org/wiki/Plan_9_from_Bell_Labs

  • Ben de bunun kodun LLM tarafından yanlış yorumlanmasının sonucu gibi göründüğünü söylemek üzereydim.
    Plan 9 terimini bilip assembly’ye kadar inmesine rağmen nasıl bir suya girdiğinin farkında olmamasını başka türlü pek hayal edemiyorum. Başkalarının da aynı şeyi düşündüğünü görünce anladım.
    Eğer durum buysa, yazarın utanmış ya da “yakalandım” diye hissetmek yerine dürüst olmasını isterim. Ancak böyle biz de bir şeyler öğrenebiliriz. Bu tür “LLM’e maruz kalma” örüntülerinden emin olmak istiyorum ama ne kadar bariz görünse de insanların bunu kabul ettiği neredeyse hiç olmuyor gibi.
    Elbette burada bariz olan bir şey yok; bu yalnızca çok aceleci ve yargılayıcı bir tahmin.

    • Dürüst olmak gerekirse? Hayır. LLM’in zaten düzgün yapması gereken işi yapmamak için bir kestirme yol olduğunu sanan o kibirden utanılmalı.
      Böyle şeyler gerçekten sinirimi bozuyor.
  • Benim seviyemin biraz üzerindeydi ama yazının okuru yanında götürme biçimi keyifliydi.
    Bu tür bir deneme ilk kez yapılıyor olamaz, değil mi? SIMD’ye susamış kelimenin tam anlamıyla onlarca Gopher vardır herhalde. Daha yaygın örüntü CGO kullanmak mı?

    • cgo’nun sorunu, fonksiyon çağrısı ek yükünün büyük olması. Yalnızca epey büyük iş parçaları için kullanmak daha iyi.
      Go’dan assembly fonksiyonu çağırmak çok daha ucuz.
      https://pkg.go.dev/github.com/grailbio/base/simd adresinde bu yönde yaptığım çalışmalar var.
    • İnsanların eskiden beri bunu kesinlikle denediğini düşünüyorum. Neredeyse 10 yıl önce kardeşim Go’da bir SIMD kütüphanesi yapmaya çalışırken (Skype mıydı?) konuştuğumuzu hatırlıyorum.
      Yanlış hatırlamıyorsam o dönemde birçok AVX komutu Go’nun Plan 9 assembler’ında encode bile edilemiyordu; bayt olarak elle encode etmek gerekiyordu [0].
      Gördüğüm en olgun kütüphane, hiç kullanmamış olsam da, CGO’yu kısmen kullanırken o ek yükten kaçınan temiz bir hack uyguluyor [1].
      [0]: https://github.com/slimsag/rand/blob/f1e8d464c0021a391d5cd64...
      [1]: https://github.com/alivanz/go-simd/
    • Böyle bir şeyin standart kütüphaneye eklenmesi önerisi reddedildi, ama arka planı anlamaya yardımcı oluyor: https://github.com/golang/go/issues/53171
  • Go assembly programları yazacaksanız Avo’ya (https://github.com/mmcloughlin/avo) bakmanızı öneririm.
    Tip güvenliği sağlar ve geçerli assembly çıktısı üretip üretmediğine dair bazı kontroller yapar. Register’ları dinamik olarak tahsis edebilir; stack ve frame boyutu gibi şeyleri sizin hesaplamanıza gerek bırakmaz.
    Çağrı kuralı ayrıntılarını da ele alabilir; böylece argümanları istediğiniz register’a ya da konuma kolayca yükleyebilirsiniz.
    Yakın zamanda Go’nun crypto kütüphanesindeki amd64 assembly’nin tamamını Avo’ya port ettim; bu tür işler için çok kullanışlı bir kütüphane.