2 puan yazan GN⁺ 2023-11-02 | 1 yorum | WhatsApp'ta paylaş
  • 1989'da Rob Pike'ın programlamaya dair 5 kuralını anlatan bir makale
  • Kural 1: Programın zamanının çoğunu nerede harcayacağını varsayma; darboğazlar beklenmedik yerlerde ortaya çıkabilir. Darboğaz kanıtlanana kadar hız hack'lerinden kaçın.
  • Kural 2: Hız için ayarlama yapmadan önce her zaman ölç. Yalnızca kodun bir bölümünün geri kalan üzerinde anlamlı bir etkisi varsa optimize et.
  • Kural 3: n küçükken karmaşık algoritmalar yavaştır. Çoğu durumda da böyledir. Karmaşık algoritmaları yalnızca n sık sık büyük olduğunda kullan; o zaman bile önce Kural 2'yi uygula.
  • Kural 4: Basit algoritmalar ve veri yapıları tercih edilir. Karmaşık olanlara göre hatalara daha az açıktırlar ve uygulanmaları daha kolaydır.
  • Kural 5: Doğru veri yapısı programlama için belirleyicidir. Veri iyi yapılandırılmışsa algoritmalar kendiliğinden ortaya çıkar.
  • Pike'ın 1. ve 2. kuralları, Tony Hoare'un "erken optimizasyon tüm kötülüklerin kaynağıdır" aforizmasını yansıtır.
  • Ken Thompson, Pike'ın 3. ve 4. kurallarını "şüphe duyduğunda kaba kuvvet kullan" diye yeniden ifade etti.
    1. ve 4. kurallar, KISS (Keep It Simple, Stupid) tasarım felsefesini hayata geçirir.
    1. kural, Fred Brooks'un 'The Mythical Man-Month' kitabındaki sözüyle örtüşür ve sık sık "akıllı nesneler kullanan aptal kod yaz" diye kısaltılır.

1 yorum

 
GN⁺ 2023-11-02
Hacker News yorumları
  • Veri hükmeder” sözüne tamamen katılıyorum
    Bu yüzden LeetCode mülakatları bana hep tuhaf gelmiştir. Genelde algoritmalara odaklanırlar; oysa gerçekte en baştan böyle yaklaşmamak gereken çok durum vardır ve veri yapıları daha merkezde olmalıdır
    Elbette algoritmaları hiç bilmiyorsanız, istisnai durumları ya da belirli bir nedenle belirli bir algoritmaya yaslanmanız gereken anları fark edemeyebilirsiniz. Yine de algoritmalar görece kısa sürede öğretilebilirken, hangi veri yapısının kullanılacağına dair sezgi kazanmak insanlara daha zor geliyor gibi

    • Kendi deneyimime göre de katılıyorum. Mülakatta FizzBuzz türü bir algoritma kontrolünün ötesine geçip doğrudan veri yapıları, mimari ve alanla eşleştirme konuşulmaya başlandığında, mülakatçıların çok daha fazla saygı gösterdiğini gördüm
      O anda atmosfer “Ah, gerçekten kıdemli bir mühendis geldi” havasına dönüyor; teknik sorunlar daha açık biçimde konuşuluyor ve “kod yazmayı biliyor mu”yu kanıtlama tavrı da azalıyor
      Tersine, iyi değişimler yaratmakta, kilometre taşlarına ulaşmakta ve ekip iş birliğinde en çok zorlandığım ekipler, veri yapılarını ve kod mimarisini düzgün oturtan kimsenin olmadığı yerlerdi. Çerçevenin her şeyi halledeceğine, olmazsa daha akıllı birinin yaptığı bir eklenti ya da ara katmanın çözeceğine alışmış çok kişi var gibi
      Veri yapılarından kaçan mühendis kendi ayağına sıkmış olur; en yararlı araçlardan birinden vazgeçtiği için sınırları gündelik olarak ortaya çıkar
    • Yarışmalı programlama yarışmalarına hazırlanan yeğenime yardım ederken, çoğu problemde veriyi uygun bir veri yapısına dönüştürmenin çözümün büyük bir kısmı olduğunu gördüm
      Örneğin kod, ağırlıklı yönlü çevrimsiz grafikte (DAG) en uzun yolu bulmak için çokça kullanılabilir; ama asıl mesele, problemi ağırlıklı DAG olarak ifade edebileceğini fark etmekti. Bunu göremezseniz yine çözebilirsiniz, ama çözüm çok daha yavaş ve karmaşık olur
    • Genel LeetCode problemleri de aslında veri yapılarına odaklanır. Çünkü adayın, problemi ve çözümü örüntü eşleştirmesiyle bağlarken zihninden çıkaracağı bir veri yapıları listesine sahip olması gerekir
      Mülakatçı baştan öncelik kuyruğu, komşuluk matrisi, trie gibi şeyler kullanmanız gerektiğini söylemez. Takılırsanız ipucu verebilir, ama fazla yönlendirme güçlü bir işe alım sinyali olarak görülmesi zor bir şeydir
    • “Bana akış şemalarını gösterip tabloları saklarsan kafam karışmaya devam eder. Bana tabloları gösterirsen akış şemalarına bakmam gerekmez; çünkü her şey kendiliğinden netleşir.”
    • İkisinden birini seçmek için diğerine dair kabaca bir sezgiye ihtiyaç yok mu diye düşünüyorum. Veriye nasıl erişileceğini hiç bilmiyorsanız hangi veri yapısını kullanmanız gerektiğini anlamak zordur
  • “Süslü algoritmalar n küçükken yavaştır ve n genelde küçüktür” sözüyle ilgili olarak, yakın zamanda bir projede hissettiğim şey büyük n’in sanıldığından çok daha büyük olabileceği
    “100 bin işlem yapmam gerekiyor, kesin optimize etmeliyim” diye düşünmek kolay; ama bilgisayarlar hızlıdır ve 100 bin çarpma çoğu zaman o kadar hızlıdır ki üzerinde derinlemesine düşünmeye gerek kalmayabilir
    Hiç düşünmeyelim demiyorum; ama modern donanımın ne kadar çılgınca hızlı olduğu sık sık şaşırtıyor

    • Buna güçlü biçimde katılmakta zorlanıyorum. İkinci dereceden zaman algoritmaları, hiç beklemediğiniz anda ısırabilen türdendir
      Yanlışlıkla ikinci dereceden zamana dönüşen kod yüzünden üretim kesintisi yaşandığını gördüm; kullanıcıların %99’u her zaman küçük n kullansa bile bazı kullanıcılar sık sık büyük n ile karşılaşıp çok yavaş bir uygulama yaşayabilir
      Çoğu durumda, genel durumda biraz daha yavaş ve uygulaması biraz daha karmaşık olsa bile ikinci dereceden zamandan daha iyi bir algoritma seçmek isterim. Sık görülen yavaş yollar optimize edilir; ama nadir yavaş yollar geliştiricinin bizzat üstüne basmadan geçip gidebildiği ya da üretimde patlayan şeylerdir
      Elbette algoritma çok karmaşıksa basit bir ikinci dereceden zaman uygulamasını seçebilirim; ama varsayılanım mümkünse ikinci dereceden küçük bir şeyden yana olur. Bununla ilgili yazdığım bir yazı da var: https://kevincox.ca/2023/05/09/less-than-quadratic/
    • Bellek hiyerarşisi de bunu etkiler. Pek çok süslü algoritmanın referans yerelliği kötüdür ve ek dallanmalar getirir
      Bu yüzden CPU’nun belleğe kıyasla o kadar hızlı olmadığı ve tüketici donanımında dal tahmini hatalarına pek önem verilmediği 40 yıl önce daha isabetli bir söz olabilir
    • LeetCode mülakat sorularında 100 bin öğelik bir listeyi birkaç kez dolaşmayı sürekli görüyorum. En optimal olmayabilir; ama gerçek çalışma süresi açısından hemen ardından yapılan ağ çağrısına kıyasla 100 bin öğe dolaşmak hiçbir şey değildir
      Her mülakatta işe alım yöneticisi bunu ister; ama operasyonun yaralarını henüz yaşamamış LeetCode acemisi kararı reddeder
    • Bu konunun klasik referans kaynaklarından biri Scalability! But at what COST?
      https://www.frankmcsherry.org/assets/COST.pdf
    • 2000’lerin başında bir oyun şirketindeki ilk düzgün programlama işime başladığımda, teknik direktör “ele aldığın öğe sayısı 10 bin civarındaysa optimize etme” diye tavsiye etmişti
      Son 20 yıldaki bilgisayar performansı artışını düşününce bu eşiği 100 bine çıkarmak oldukça makul görünüyor
  • “Erken optimizasyon tüm kötülüklerin kaynağıdır” şeklindeki ünlü özdeyiş aslında Tony Hoare’dan değil, Donald Knuth’tan gelir ve çoğu zaman bağlamından koparılarak genel olarak optimizasyona karşı bir söz gibi kullanılır.
    Cümlenin tamamı şöyledir: “Küçük verimlilikleri, diyelim ki vakaların %97’sini unutmalıyız. Erken optimizasyon tüm kötülüklerin kaynağıdır. Ancak kritik %3’te fırsatları kaçırmamalıyız.”
    Ana fikir, etkisi olan yerlerde zaman harcayıp optimizasyon yapmaktır.

    • Knuth bunun Hoare’ın sözü olduğunu, Hoare ise Knuth’un sözü olduğunu söylüyor; yani kime inanacağınız meselesi. En iyisi ikisine birden atfetmek gibi görünüyor.
      Muhtemelen Tony önce söylemiş, Knuth da bunu rafine edip yayımlamış olabilir. Gerekli bağlamı veren uzun alıntıyı yanında bulundurmak her zaman iyidir.
    • Bu alıntının 1970’lerin sonunda söylendiği de sıkça unutuluyor. Neredeyse 50 yıl önce.
      O dönemde programlama bugünkünden çok farklıydı. O zamanki “erken optimizasyon”, “ölçeklenebilirliği iyi olan popüler bir kütüphaneyi kullanalım”dan ziyade, “yalnızca bu donanımda çalışan, anlaşılması imkânsız bir bit oynama algoritması kullanalım”a daha yakındı.
    • Uzun alıntının anlamlı bir ek bağlam sunduğunu düşünmüyorum. Önemli %3’ü ölçüp bulduysanız, o durum artık erken değildir.
      “Erken optimizasyon tüm kötülüklerin kaynağıdır” sözünün içinde zaten bu anlam var; özdeyiş “optimizasyon tüm kötülüklerin kaynağıdır” değil.
    • Bu sözü dogma gibi kabul edip verimli yöntemleri öğrenmeyen çok fazla insan var.
      Şirketin veri yapıları ve algoritmalar mülakatlarında, bubble sort’un en iyi seçenek olduğunu söyleyen sayısız frontend geliştirici gördüm. Anında türetmesini beklemeye gerek yok; birkaç yöntemi bilip probleme uygun iyi bir seçimi söylemesi yeterli.
      “Erken optimizasyon yapma”yı o kadar uçlarda yaşayıp verimli yöntemleri bile bilmiyorsanız, nerelerin önemli olduğunu nasıl anlayacaksınız?
    • Bu bağlamda genel olarak optimizasyona karşı bir anlamda kullanılmış gibi görünmüyor.
  • “Veri yapıları esastır” sözü veritabanları için iki kat önemlidir.
    DB’yi aptal bir bit deposu ya da nesne tanımlarının 1:1 yansıması gibi kullananlar, DB bunu kişisel algılayıp performansı bozduğunda şaşırırlar.
    ORM’in ürettiği bir DB şemasını tekrar görürsem, bu fazla erken bir yeniden karşılaşma olacak gibi.

    • Çoğu ORM’in şemayı siz ne istediyseniz ona göre ürettiğini düşünüyorum. ORM kullanmak, elle yapmaktan daha kötü bir veritabanı tasarımını otomatik olarak ortaya çıkarmaz.
      Sorun, bazı ya da birçok geliştiricinin SQL bilmemesi ve ORM kullanmak için gereken DB bilgisinden de yoksun olmasıdır.
      ORM, altında ne olduğunu bilmeniz gereken, epey sızdıran bir soyutlamadır. Bunu anlarsanız çoğu ORM ile de makul şemalar oluşturabilirsiniz.
    • Buna Conway Yasası da eklenebilir. “Sistem tasarlayan organizasyonlar, o organizasyonun iletişim yapısını kopyalayan tasarımlar üretir” diyen yasa.
      Veri yapılarını iyi organize etmek ve tasarım değişse bile bunu korumak için veri ile kodu organizasyon düzeyinde ayırmak gerekir.
      DB şema tasarımı, kullanım senaryoları ve aralarındaki eşlemeler uygulamanın geri kalanından ayrılmalı; bu grup bütünlük kontrollerini vb. de yazmalıdır. Organizasyon yapısı veri ile kodu ayırmıyorsa, kod ile veriyi ayırmak zordur.
    • Stored procedure kazanır.
  • Benim ek kuralım şu: küçük performans israfları biriktikçe, her biri tek başına önemsiz olsa da sonunda programı yavaşlatır.
    Karmaşıklık, okunabilirlik, sürdürülebilirlik ve uygulama maliyetine etkisi yoksa performansı öylece harcamamak gerekir. Diğer koşullar neredeyse eşitse, iki seçenekten daha yavaş olanı seçmek doğru değildir.
    Ayrıca n’in küçük olduğunu varsayarsanız neredeyse her şey çalışır. Ama n 100’ün altındayken iyi çalışan, 10000’in üstünde dağılan bir kod, örneğin O(n²) gibi bir şey kullanıyorsanız, doğrudan bir sınır koymalısınız. Küçük n varsayımı bozulduğunda büyük bir hata vermek, AWS faturası bombası ya da kilitlenmiş bir programdan iyidir.

    • Burada kural 1 ve 2 geçerlidir.
  • Bu ilkelerin önemli bir kısmı, sonuçta aşırı tasarımı engelleyen stratejilere varıyor.
    Deneyimime göre erken optimizasyon en pahalı tuzaklardan biridir. Olası bir sorunu fazla erken baypas ederseniz, o varsayım doğrulanmaz ve sonraki ekip gereksiz karmaşıklığı çözmek için pahalı çözümler üretmek zorunda kalır.
    Öğrendiğim yaklaşım şu: optimizasyon tahminlere dayanır ve baştaki tahminler sık sık yanlıştır.
    Ayrıca insanların gereğinden karmaşık kod üretmesini engellemek için ego yönetimi ve psikolojiyi anlamanın oldukça önemli olduğunu da öğrendim.

    • “Elindeki problemi çöz. Elinde olduğunu düşündüğün problemi çözme” diye ifade ederim.
    • Bu kavram, lean ve Six Sigma’daki israfı belirleme ile de örtüşüyor.
      Aşırı üretim genellikle en kötü israf sayılır; çünkü yalnızca gereksiz bir şey üretmekle kalmaz, gerçekten gereken şeye harcanabilecek emeği de tüketir. Aşırı tasarım da buna benzer.
    • Bir adım daha derine inersek, aşırı tasarım; ileride karmaşıklığın gerekebileceği ve o zaman sistemi genişletmenin daha zor ya da riskli olacağı düşüncesinden doğar.
      Örneğin yalnızca 100 kullanıcınız varken bile, bir gün 1 milyon kullanıcıya ulaşırsanız monoliti yeniden tasarlamak zor olur diye mikroservis mimarisi ile başlamak gibi.
      Bu yüzden önce kodun zamanla neden daha az esnek hâle geldiğini ele almak gerekir.
    • Hata işleme konusunda gösterişe kaçmayıp erken ve basit şekilde başarısız olmak iyidir.
  • Genel olarak iyi kurallar, ama pratikte 1. madde olduğu gibi geçerli değil
    Başlarken neyin darboğaz olacağına dair bir hipoteze ihtiyaç var. XYZ’yi uyguladıktan sonra neyin yavaş olduğunu ölçüp düzeltmek her zaman mümkün değil. X, Y, Z birbirine bağlı olduğundan Y’yi hızlı yapmak için X ve Z’yi belirli bir şekilde tasarlamak gereken durumlar var; ayrıca Y’nin darboğaz olacağını zaten bildiğiniz durumlar da var
    Sonradan ölçüp neyin yavaş olduğunu öğrenseniz bile, onu daha hızlı hâle getirecek yaklaşıma bahis oynamanız gerekir. Bu bahis ne kadar bilgili olursa o kadar iyi
    İyi programcılar ölçüm yapar; ama neyin yavaş, hataya açık ve çok bellek tüketen olacağını öngörebildikleri için daha az yineleme yaparlar. Performans davranışının öngörülemeyeceğini bir kural gibi söylemek, iyi programcıların biriktirdiği deneyim ve beceriyi yok saymak olur

      1. kural, ona inanmayanlar için katı bir yasa; inananlar içinse esnek bir kılavuzdur
        Çünkü 1. maddeye uyma süreci, darboğazları tahmin etmek için gereken iyi sezgiyi ve ampirik arka planı kazanmanın en iyi yoludur
    • Yavaş olacak gibi görünen algoritmalar spike implementasyon ile doğrulanabilir. Genellikle yavaş algoritmaların uygulanması ve test edilmesi basittir
      Hız tahmininiz yanlış çıkarsa, projenin ömrü boyunca gereksiz yere karmaşık kod taşımış olursunuz
      İnsanlar algoritma hızını sık sık yanlış tahmin eder. Bilgisayar zamanının %99’unu DB sunucusundan n’i getirmekle geçiriyorsa O(n) ile O(n²) çoğu zaman gerçek süre açısından aynı görünebilir
      C ile yazılmış bir algoritmanın eşdeğer Python kodundan daha yavaş olduğu durumlar da olur; bunun nedeni bytecode derleyicisinin akıllı şeyler yapması olabilir
      Legacy kodları hızlandırma işini çok yaptım; genellikle sanılandan çok daha kolay olur ve asıl yazara açık gelmemiş nedenlerle yavaştır. Çoğu zaman gerçekte yavaş olmasının nedeni, kod tabanının fazla karmaşıklaşması ve ilk yazarın artık üzerinde akıl yürütememesidir. Benim elimde “fazla yavaş” olan somut bir örnek olduğundan, çalıştırıp yavaş noktaları gözlemleyerek debug etmek kolaydır
    • Bu, metnin tamamına verilmiş bir tepki gibi görünmüyor. Orada darboğazı bilene kadar hız hack’i eklemeyin deniyor. Anlattığınız durum bundan farklı
      Çok sayıda fiziksel nesne içeren bir video oyunu yapıyorsanız ve deneyimden çarpışma algılamanın büyük sorun olacağını kesin biliyorsanız, oyunu ve sistemi bunun etrafında tasarlamak hız hack’i değildir
      Performansın büyük bir konu olacağını bildiğiniz bir işte elbette ölçüm yapmalısınız. Amaç bunun bir konu olup olmadığını doğrulamak değil, bu konuyu ne kadar iyi ele aldığınızı doğrulamaktır
    • Somut bir örneği merak ediyorum. Çoğu durumda gerçekten fark yaratıp yaratmadığından şüpheliyim
      Yeni gereksinimlerle yeni bir sistem yapıyorsanız, çoğu zaman doğrudan başlamak sorun olmaz gibi geliyor. Yaparsınız, test edip ölçersiniz, atar veya refactor edersiniz, tekrar edersiniz
      Rust’ı örnek alırsak, taslak bir dil ve OCaml ile yapılmış bir derleyiciyle başlayıp yinelediler. Bir gün OCaml’dan self-hosting’e geçebileceklerini bilseler bile bunun büyük fark yaratıp yaratmadığından pek emin değilim
    • Geliştiriciler neyin yavaş olacağını bu kadar iyi öngörebiliyorsa, geliştirici odaklı startup’ların başarı oranı %100 olmalı değil mi diye düşünüyorum
      Kullanıcınız yoksa saatler süren bir fonksiyon bile, optimize edilince milisaniyelere inen bir fonksiyona kıyasla hâlâ yeterince hızlıdır. Böyle tahminleri doğru yapabildiğini kanıtlamış biri var mı, pek bilmiyorum
    1. maddeye karşı argüman olarak, basit verinin üzerindeki karmaşık algoritmalar büyük performans kazancı sağlayabilir, engelleri ortadan kaldırabilir ve hatta işi basitleştirebilir
      Örneğin BinaryTree nesnesi yerine sıralı bir dizide ikili arama yaparsanız, birleştirme concat sonrası sıralama olduğundan basitleşir; pointer olmadığı için serileştirme kolaylaşır ve bazı durumlarda serileştirmenin kendisi gereksiz olur. Dizi diskte ya da bellekte, hatta mmap ile ikisinde birden bulunabilir; RAM’den büyük verilerle de çalışabilir ve yalnızca bir dosyayı veya mapping’i gösterip hemen çalıştıran cold start da mümkün olur. Cache-oblivious özellikleri de vardır
      Huffman kodlaması da bir örnek. Üniversitede muhtemelen ağaç tabanlı algoritma ve O(n log n) karmaşıklığıyla öğrenmişsinizdir; ama Huffman ağacını yerinde, dizi tabanlı olarak doğrusal zamanda kurmanın bir yolu olduğunu bilmiyordum
      Elbette zamanın %99’unda backend microservice geliştirirken standart collection veri yapılarını kullanıyoruz. Ama işte big data çalışıyorsam, o dönemin popüler MapReduce türevlerini devreye almak yerine, yerelde büyük diskleri olan tek bir makinede işlemeyi çok daha fazla tercih ederim
    • İkili aramayı havalı bir algoritma olarak görmüyorum. Asıl havalı olan modern sıralama fonksiyonları; bunlarda ince hatalar olabilir, bu yüzden sıradan geliştiricilerin kendi başlarına yazmaması gerekir. Quicksort’ta bile tuzaklar var
      Rob Pike olsa önce kodu profiling ile inceler, sonra havalı kodun ya da alternatif veri yapısının gerçekten daha hızlı olup olmadığına bakın derdi
    • Bu bir karşı argüman gibi görünmüyor. Pike’ın tavsiyesi açısından “sıralı dizide ikili arama” ile “BinaryTree nesnesi” aynı veri yapısının farklı implementasyonlarından ibaret
    • Geliştiricinin zamanın %99’unda en pahalı kaynak olduğunu unutmamak gerek. Bakım yapılabilirlik ve pazara çıkış hızı genellikle çok daha önemlidir
  • Bu yazıyı 10 yılı aşkın süre önce ilk kez cat-v’de okudum; tasarıma ve karmaşıklığa yaklaşma ve bunları düşünme biçimimde silinmez bir etkisi oldu
    http://doc.cat-v.org/bell_labs/pikestyle

  • “Veri yapıları esastır” şeklindeki özgün kuralın nasıl “akıllı nesneler kullanan aptal kod yazın”a indirgenebildiğini anlamıyorum
    “smart objects” ifadesi çok kötüydü; özgün kural daha uzun olsa da çok daha iyi

    • Rob Pike’ın da “smart objects”ın yanlış bir düşünme biçimi olduğuna katılacağını düşünüyorum: https://commandcenter.blogspot.com/2012/06/less-is-exponenti...
    • “Akıllı” mantığı daha üst seviyeye taşımak; anlamayı, test etmeyi ve değiştirmeyi kolaylaştırır. smart objects’ları birbiriyle uyumlu ve bütünlüklü hâle getirmek çok daha zor bence
    • Bunu, iyi yapılandırılmış nesnelerden doğal olarak ortaya çıkan kodu yazın anlamında görmek gerekir