Sembolik makina diline ihtiyacınız var mı?

Aslında, her ne yapıyorsanız bölmek istemem, ama burada zor kazanılmış tecrübelerin sonucu olarak bazı tavsiyelerde bulunacağım.

Artılar ve Eksiler

Sembolik makina dilinin (Assembly) avantajları

Sembolik makina dili (Assembly) pekçok düşük seviyeli şeyi ifade edebilir:

  • makinaya bağlı yazmaç ve Giriş/Çıkışlara (I/O) erileşilirsiniz

  • çoklu yazılım parçalarının ya da donanım aygıtlarının ölümcül kilitlenmesini içeren kritik bölümlere özgü kod davranışlarını kontrol edebilirsiniz.

  • alışıldık derleyicinizin herkesçe kabul görmüş kurallarını kırabilirsiniz, ki bu da bazı eniyilemelere izin vermektedir (bellek tahsisiyle, evrelerle (threading), çağrı kurallarıyla ilgili kuralları geçici olarak kırmak gibi).

  • kod parçaları arasında, uygun olmayan (örn. değişik derleyiciler tarafından üretilen veya düşük seviye arayüzlerle ayrılan) kurallar kullanarak arayüzler tasarlayabilirsiniz.

  • işlemcinizin alışılmadık programlama kiplerine erişebilirsiniz (örn. açılış arayüzü için 16 bitlik kip, aygıt yazılımları, Intel bilgisayarlardaki kalıtsal kodlar).

  • kötü ve eniyileme yapmayan derleyicilerle uyumlu çalışacak sıkı döngüler için oldukça hızlı kodlar üretebilirsiniz. (Ancak, eniyileme yapabilen özgür derleyiciler var!)

  • her ne kadar başka herhangi birininkine ait olmayacak olsa da, size özel aygıt ayarlarınız için el ile eniyilenmiş mükemmel bir kod üretebilirsiniz

  • yeni dilinizin eniyileme yapabilen derleyicisi için bir kod yazabilirsiniz (bu, çok azımızın yapacağı ve hatta pek de sıklıkla yapmayacağı bir iştir).

  • örn. kendi kodunuzu tamamiyle kontrol altına alabilirsiniz

Sembolik makina dilinin (Assembly) dezavantajları

Sembolik makina dili, oldukça düşük seviyeli bir dildir (bundan daha aşağıda ikilik komutları el ile kodlamak bulunmaktadır). Bu da şu manalara gelmektedir:

  • ilk başlarda yazmak, oldukça uzun ve can sıkıcıdır
  • hata yapmaya oldukça meyillidir
  • hatalarınızı takip etmek oldukça zor olabilir
  • kodunuzu anlamak ve değiştirmek oldukça zordur, örn. bakımını yapmak
  • sonuç, şu anda veya gelecekte var olacak mimarilere taşınabilir değildir
  • kodunuz aynı mimarinin sadece belli bir gerçekleştirimi için eniyilenecektir: mesela, Intel uyumlu platformlar arasında, herbir CPU tasarımı ve türevi (işlemci birimlerinin nispi gecikme süresi, üretilen iş (through-output) ve kapasitesi, önbellekler (cache), RAM, taşıt (bus), diskler, FPU, MMX, 3DNOW, SIMD uzantılarının varlıkları, v.b.) tamamiyle farklı eniyileme tekniklerini ifade etmektedir. İşlemci (CPU) tasarımları halihazırda şunları içermektedir: Intel 386, 486, Pentium, PPro, PII, PIII, PIV; Cyrix 5x86, 6x86, M2; AMD K5, K6 (K6-2, K6-III), K7 (Athlon, Duron). Yeni tasarımlar yukarılara doğru tırmanmayı sürdürmektedir, dolayısıyle ne bu listenin ne de kodunuzun güncel kalacağını ummayın.

  • çok az bir ayrıntı üzerinde fazlaca zaman harcarsınız, hızlanmanın büyük kısmını oluşturan küçük ve geniş algoritmik tasarımlar üzerine odaklanamazsınız (örn. liste/diziler üzerinde değişikler yapan hızlı ve ilkel nesneler oluşturmak için çokça zaman harcayabilirsiniz; oysa sadece hesaba dayalı bir adresleme (hash) tablosu veya başka bir yaklaşımda ikilik ağaç ya da pekçok CPU kümesine dağıtılmış olan yüksek seviyeli bir yapı programınızı çok daha hızlandırırdı).

  • algoritmik tasarımınızdaki ufak bir değiliklik var olan sembolik makina kodunuzu tamamiyle geçersiz hale getirebilir. Bu durumda ya tamamen tekrar yazmaya (yazabilemeye) hazırsınızdır veya belirli bir algoritmik tasarımı yapmaktan sıkılmışsınızdır.

  • Standart karşılaştırmalı değerlendirmeden (benchmark) uzak olmayan kod üzerinde, eniyileme yapan ticari derleyiciler elle kodlanmış sembolik makina dili gerçekleştirirler (aslında, RISC mimarisine göre bu durum x86 mimarisi üzerinde daha az geçerlidir ve belki de geniş bir şekilde bulunan/kullanılan derleyiciler için de daha az doğrudur; herneyse, tipik bir C kodu için GCC oldukça iyidir);

  • Ve her durumda, comp.compilers'da bulunan yönetici John Levine'nin dediği gibi,

        "derleyiciler karmaşık veri yapılarının kullanımını oldukça kolay
        hale getirmektedir ve derleyiciler işin yarısından sonra sıkılmamakta
        ve güvenilir oldukça güzel kodlar üretmektedir."
    

    Prosedür ve modül sınırları arasında kodu eniyilerken, aynı zamanda da tüm (büyük) program boyunca düzgün biçimde kod dönüşümleri üreteceklerdir.

Değerlendirme

Tüm bunlardan sonra, sembolik makina dili kullanmanın bir ihtiyaç olduğunu düşünebilirsiniz ve hatta ihtiyaç olmadığı bazı yerlerde kullanmak çok da faydalı olabilir. Şunları yapmak isteyeceksiniz:

  • sembolik makina dilinin kullanımını küçültmek
  • iyi tanımlanmış arayüzler içine bu kodları koymak (encapsulate)
  • sembolik makine dili dışında yüksek seviyeli dillerle tanımlanmış yapılar tarafından sembolik makine dili kodunuzun otomatik üretilmesi (örn. GCC satıriçi (inline) makrolar)
  • bu programları otomatik araçların sembolik makine dili koduna dönüştürmesi
  • bu kodun eğer mümkünse eniyilenmesi
  • yukarıdakilerin tümü, örn. bir derleyici (ya da derleyiciye bir eklenti) arka ucu (back-end)

Sembolik makina diline gerek duyulsa (örn. İşletim sistemi geliştirmek) bile, yukarıdakilerin çok daha fazlasına gerek duyulmayacağını göreceksiniz ve de üstteki prensipler de varlığını koruyacaktır.

Bununla ilgili olarak Linux çekirdek kaynaklarına bakınız: ne kadar az sembolik makina diline gerek duyulursa, neticesinde hızlı, güvenilir, taşınabilir ve sürdürülebilir işletim sistemi oluşmaktadır. Hatta DOOM gibi başarılı bir oyun dahi yoğun şekilde C ile yazılmıştır, sadece küçük bir kısmı hızı arttırmak için sembolik makine dili ile yazılmıştır.

Sembolik makina dili nasıl kullanılmaz

Başarılı kodu gerçeklemek için adımlar

comp.compilers'daki Charles Fiterman'ın bilgisayara karşın insanın ürettiği sembolik makina kodlarıyla ilgili söylediği gibi:

İnsanoğlu her zaman kazanmak zorundadır ve işte bu da nedenidir.

Birincisi insanoğlu herşeyi yüksek seviyeli dilde yazar.
İkincisi üzerinde zaman harcadığı sıcak noktaları bulacak
şekilde programın taslağını hazırlar.
Üçüncüsü elinde bu kısımlar için derleyicinin ürettiği kod vardır.
Dördüncüsü makina kodu üzerinde ufak gelişmeler sağlayarak onlara
bir canlılık kazandırabilir.

İnsanoğlu kazanır çünkü makinaları kullanabilmektedir.

Eniyileme yapan derleyiciler ile dilleri

Diğerleri arasında ObjectiveCAML, SML, CommonLISP, Scheme, ADA, Pascal, C, C++ gibi dillerin programınızın şişkinliğini eniyileyecek özgür derleyicileri vardır ve genellikle, sıkı döngüler için bile elle yazılmış sembolik makina kodundan daha iyisini üretirler, bu arada da daha yüksek seviyeli ayrıntılar üzerinde odaklanmanızı sağlarlar ve de belli bir kararlı düzeye geldikten sonra da yukarıda bahsedilen şekilde belli miktar başarıma el koymanızı yasaklamazlar. Elbette, bu dillerin çoğu için eniyileme yapan ticari derleyiciler de vardır!

Bazı diller C kodu üreten ve sonrasında eniyilemesini C derleyicisine yaptıran derleyicilere sahiptir: LISP, Scheme, Perl ve diğer pekçoğu. Hız oldukça iyidir.

Kodunuzu hızlandıracak için genel adımlar

Kodunuzun hızlı çalışmasını sağlamak için, analiz araçlarından birinin kodunuzun belli bir yerini sürekli bir performans darboğazı olarak tanımlaması gerekmektedir.

Bu nedenle, bir kod parçasını çok yavaş olarak tanımladıktan sonra, şunları yapmalısınız:

  • öncelikle daha iyi bir algoritma kullanmaya çalışın;
  • yorumlamak yerine onu derleyin;
  • daha sonra derleyicinizdeki eniyilemeyi etkinleştirip onunla oynayın
  • daha sonra derleyiciye nasıl eniyileme yapacağına dair ipuçları verin (LISP'de bilgi girmek; GCC ile yazmaç kullanmak; pekçok derleyicideki seçenekler, v.b)
  • bundan sonra ancak son çare sembolik makina dilidir.

Son olarak, sembolik makina diliyle yazmayı bitirmeden önce, üretilen kodu incelemelisiniz, problemin gerçekten de kötü kod üretiminden kaynaklandığını görmelisiniz, ki bu herzaman sanılan durum olmayabilir: derleyicinin ürettiği kod sizin yazdığından daha iyi olabilir, özellikle modern çoklu ardışık düzen (multi-pipelined) mimarilerinde! Programın yavaş olan kısımları esas olarak böyle olabilir. Hızlı işlemcili modern mimarilerde temel sorun, bellek erişim gecikmeleri, önbellek atlamaları, TLB (TLB-misses) kayıpları ve sayfa hatalarından kaynaklanmaktadır; yazmaç kullanımı gereksiz olmaktadır ve veri yapılarını daha kazançlı bir şekilde ve bellek erişimini daha iyi yapmanın yollarını tekrar düşüneceksiniz. Belki de tamamen farklı bir yaklaşım sorunun çözümüne yardımcı olabilir.

Derleyicinin ürettiği kodu incelemek

Derleyicinin ürettiği kodu incelemek için pek çok neden vardır. İşte size bu kodla neler yapacağınız:

  • üretilen kodun el yordamıyla geliştirilip geliştirilemeyeceğini kontrol edin (veya derleyici seçenekleriyle oynayarak)

  • durum böyle ise, o zaman üretilmiş kod ile başlayın, onu yeniden yazmak yerine değiştirin

  • genel olarak, dış dünyaya karşı sizin sembolik makine rutinlerinizi anlayan üretilen kodu değişiklik yapmak için bir yama gibi kullanın

  • derleyicinizdeki hataları takip edin (tahminim en az sıklıkla)

Sembolik makina dilinizin oluşması için standart yol derleyicinizi -S parametresiyle çalıştırmaktır. Bu, GCC C derleyicisini de içeren (GCC) pekçok Unix derleyicinde çalışmaktadır. GCC'ye gelince, -fverbose-asm komut satırı parametresiyle çok daha anlaşılabilir sembolik makina kodları üretecektir. Elbette, iyi sembolik makina kodu elde etmek istiyorsanız, herzamanki eniyileme seçeneklerinizi ve ipuçlarını unutmayınız!

Linux ve Assembly

Muhtemelen farkettiğiniz üzere, genel olarak, Linux programlamada sembolik makine dilini kullanmaya ihtiyaç duymazsınız. DOS'takinin aksine, Linux sürücülerini sembolik makine dili ile yazmanız zorunlu değildir (aslında, eğer gerçekten istiyorsanız yapabilirsiniz). Ve günümüzün eniyileme yapan derleyicileriyle, farklı işlemcilerdeki hızı dikkate alıyorsanız, C ile yazmak çok daha basittir. Yine de, bunu okuyorsanız, C/C++ yerine sembolik makine dili kullanmak için bir nedeniniz olabilir.

Sembolik makine dilini kullanmak ihtiyacında olabilirsiniz veya sadece kullanmak isteyebilirsiniz. Kısaca, sembolik makine dili krallığına dalmaktaki temel pratik sebepler (ihtiyaç) kısa kodlar ve libc bağımsızlıklarıdır. Pratik olarak ve en sık karşılaşılan nedense (istek), 20 yıllık ve herşeyi sembolik makine dili ile yapma alışkanlığı olan eski bir bilgisayar kurdu olmaktır.

Yine de, eğer Linux'u gömülü bir sisteme yerleştiriyorsanız, tüm sistemin büyüklüğüne göre kısa olabilirsiniz: çekirdeği, libc ve tüm diğer şeylerin (file|find|text|sh|v.b) uygulamalarını birkaç yüz kilobyte'a oturtmalısınız ve her kilobyte'ın değeri çok fazladır. Bundan dolayı, mümkün olan yollardan birisi, sistemin bazı (veya tüm) kısımlarını sembolik makine dili ile yazmaktır ve bu durum da pekçok yer tasarrufu sağlayacaktır. Mesela, basit bir, sembolik makine dili ile yazılmış, httpd 600 byte'tan az tutmaktadır; kernel, httpd ve ftpd içeren bir sunucuyu 400kb veya daha az boyutta ayarlayabilirsiniz... Bunu düşünün.