Yazılımda performans ve optimizasyon

Katılım
26 Temmuz 2023
Mesajlar
4.579
Makaleler
25
Çözümler
170
Yer
Başkent
Daha fazla  
Sistem Özellikleri
HP Victus 16 S1035NT
Cinsiyet
Erkek
Meslek
Yazılımcı/Yayıncı
Yüksek öğretim okumadım. Ancak çevremdeki okuyanlardan gördüğüm kadarıyla onlarda da gösterilmemiş gibi geliyor.
Bu konu kullanıcı deneyimi açısından çok ama çok ciddi bir konu. Bana kalırsa yazılımın en önemli konusudur. (Önemlilerinden değil direkt en önemlisi.)
Neden bilmiyorum ama... Devamını okumak için: Yazılımda performans ve optimizasyon konusu

Bu kadar okuyamam ben diyenler için veya üstteki yazdıklarımı anlamayanlar için özet:
Bir yıl önce WinUI 3 ve Optimizasyon konularını öğrenmeye başlamıştım ancak temel kötü olduğundan:
C#:
internal void ResponsiveSizeApply()
{
    bool maximized = mainWindow.WindowState is WindowState.Maximized;
    int size, top;
    if (userSettings.TitleBarVisible)
    {
        top = 28;
        size = maximized ? sizeInt32.Height - 40 : sizeInt32.Height - 32;
    }
    else
    {
        top = maximized ? -12 : -4;
        size = sizeInt32.Height;
    }
    NavViewVertical.Margin = new Thickness(NavViewVertical.Margin.Left, top, NavViewVertical.Margin.Right, NavViewVertical.Margin.Bottom);
    NavViewVertical.MinHeight = size;
}
Böyle olması gereken kod, böyle oluyor:
C#:
public void ResponsiveSizeApply()
{
    if (userSettings.TitleBarVisible)
    {
        NavViewVertical.MinHeight = mainWindow.WindowState is WindowState.Maximized ? sizeInt32.Height - 40 : sizeInt32.Height - 32;
        NavViewVertical.Margin = new Thickness(NavViewVertical.Margin.Left, 28, NavViewVertical.Margin.Right, NavViewVertical.Margin.Bottom);
    }
    else
    {
        NavViewVertical.MinHeight = sizeInt32.Height;
        NavViewVertical.Margin = mainWindow.WindowState is WindowState.Maximized
                               ? new Thickness(NavViewVertical.Margin.Left, -12, NavViewVertical.Margin.Right, NavViewVertical.Margin.Bottom)
                               : new Thickness(NavViewVertical.Margin.Left, -4, NavViewVertical.Margin.Right, NavViewVertical.Margin.Bottom);
    }
}
Aralarında mikro farklar varmış gibi görünmesine aldanmayın. Bu ve benzeri ufak hâtalar büyük projelerde çok ciddi sorun olabilir. Bunlardan yola çıkarak, yeni teklojinin ve yeni konuların ülkemizde de görülmesini umarım ve sizlerin de yorumlarınızı beklerim:)
 
Son düzenleyen: Moderatör:
Orneklerin yetersiz/fark yaratmayacak ornekler oldugunu dusunuyorum. Iki ornegin tahmini uretilmis ASM sonuclari;

Kod:
Example:ResponsiveSizeApply():this (FullOpts):
       push     rbp
       mov      rbp, rsp
       mov      rax, gword ptr [rdi+0x20]
       cmp      dword ptr [rax+0x08], 0
       sete     al
       movzx    rax, al
       mov      rcx, gword ptr [rdi+0x18]
       cmp      byte  ptr [rcx+0x08], 0
       jne      SHORT G_M51224_IG04
       mov      ecx, -12
       mov      edx, -4
       test     eax, eax
       cmove    ecx, edx
       mov      rax, gword ptr [rdi+0x08]
       mov      eax, dword ptr [rax+0x08]
       jmp      SHORT G_M51224_IG06
G_M51224_IG04:  ;; offset=0x0034
       mov      ecx, 28
       test     eax, eax
       je       SHORT G_M51224_IG05
       mov      rax, gword ptr [rdi+0x08]
       mov      eax, dword ptr [rax+0x08]
       add      eax, -40
       jmp      SHORT G_M51224_IG06
G_M51224_IG05:  ;; offset=0x0049
       mov      rax, gword ptr [rdi+0x08]
       mov      eax, dword ptr [rax+0x08]
       add      eax, -32
G_M51224_IG06:  ;; offset=0x0053
       mov      rdx, gword ptr [rdi+0x10]
       mov      rdi, rdx
       mov      esi, dword ptr [rdx+0x0C]
       mov      r8d, dword ptr [rdx+0x14]
       mov      r9d, dword ptr [rdx+0x18]
       mov      dword ptr [rdi+0x0C], esi
       mov      dword ptr [rdi+0x10], ecx
       mov      dword ptr [rdi+0x14], r8d
       mov      dword ptr [rdi+0x18], r9d
       mov      dword ptr [rdx+0x08], eax
       pop      rbp
       ret    

Example:ResponsiveSizeApply():this (FullOpts):
       push     rbp
       mov      rbp, rsp
       mov      rax, gword ptr [rdi+0x18]
       cmp      byte  ptr [rax+0x08], 0
       jne      SHORT G_M56713_IG07
       mov      rax, gword ptr [rdi+0x10]
       mov      rcx, gword ptr [rdi+0x08]
       mov      ecx, dword ptr [rcx+0x08]
       mov      dword ptr [rax+0x08], ecx
       mov      rcx, rax
       mov      rdi, gword ptr [rdi+0x20]
       cmp      dword ptr [rdi+0x08], 0
       jne      SHORT G_M56713_IG04
       mov      edx, dword ptr [rax+0x0C]
       mov      edi, dword ptr [rax+0x14]
       mov      esi, dword ptr [rax+0x18]
       mov      r8d, -12
       jmp      SHORT G_M56713_IG05
G_M56713_IG04:  ;; offset=0x003A
       mov      edx, dword ptr [rax+0x0C]
       mov      edi, dword ptr [rax+0x14]
       mov      esi, dword ptr [rax+0x18]
       mov      r8d, -4
G_M56713_IG05:  ;; offset=0x0049
       add      rcx, 12
       mov      dword ptr [rcx], edx
       mov      dword ptr [rcx+0x04], r8d
       mov      dword ptr [rcx+0x08], edi
       mov      dword ptr [rcx+0x0C], esi
       pop      rbp
       ret    
G_M56713_IG07:  ;; offset=0x005B
       mov      rax, gword ptr [rdi+0x10]
       mov      rcx, rax
       mov      rdx, gword ptr [rdi+0x20]
       cmp      dword ptr [rdx+0x08], 0
       jne      SHORT G_M56713_IG08
       mov      rdi, gword ptr [rdi+0x08]
       mov      edx, dword ptr [rdi+0x08]
       add      edx, -40
       jmp      SHORT G_M56713_IG09
G_M56713_IG08:  ;; offset=0x0078
       mov      rdx, gword ptr [rdi+0x08]
       mov      edx, dword ptr [rdx+0x08]
       add      edx, -32
G_M56713_IG09:  ;; offset=0x0082
       mov      dword ptr [rcx+0x08], edx
       mov      rcx, rax
       mov      edx, dword ptr [rax+0x0C]
       mov      edi, dword ptr [rax+0x14]
       mov      eax, dword ptr [rax+0x18]
       mov      dword ptr [rcx+0x0C], edx
       mov      dword ptr [rcx+0x10], 28
       mov      dword ptr [rcx+0x14], edi
       mov      dword ptr [rcx+0x18], eax
       pop      rbp
       ret

(Kullanilan nesnelerin yapisini tahmin ederek yazdigim basit bir kodun uretimi bu, tabii ki tahmini degerler fark edebilir. Agresif optimizasyonlar acik ve tahmini gelebilecek inputlara gore kirpmalar yapabiliyor ama yapmasin diye ugrastim biraz.)

Ikisi arasinda 2. paylasitigin kod cok minimal bir marjinde daha optimize. Ama bana sorarsan bir numarayi okumak kat ve kat daha kolay. Ternary operator bu sekilde kullanildigini kodun okunabilirligini siddetle baltaliyor.

JIT kullanirken JIT'in en buyuk artisi zamanla kodun hot pathini cacheleyebilir olmasi. Birebir ayni inputlar geliyorsa ve dinamizim asiri degilse, hesaplamalari inline hale getirebilir, onbellekleyebilir. Bu durumlarda optimize olmayan kod optimize kodla ayni performansi da verebilir. (Compilerin kodu nasil optimize ettigine bagli tabii ki) Ancak burada ki orneklerin asil yeterli olmamasini dusunmemim sebebi, compiler optimizasyon uygulamasa bile ikisi de esit sayida UI degisikligi yapiyor.

Local assignment sayisi bir sey degistirmeyecek cunku primitive typelarin assignmentlari genelde tek cycle'da calisiyor. Nano saniye bile tutmaz primitive typelarin assignment islemleri. Primitive olmayanlarda ise referans copy gerceklesir, dolayisiyla constructor sonrasinda ki assignment islemi tek cycle da calisir. Constructor'da ne kadar fazla seyin baslatildigina/assign edildigine bagli olarak cycle sayisi degisecek ama buradaki Thickness'in tahmini olarak 10-15 cycleda baslatildigini varsayabilirim. Accesslerin surelerini es geciyorum.

Bu da bu iki kod arasinda ki farkin sadece bir iki extra primitive assignment cyclelari oldugu anlamina geliyor. Haliyle bunun yaratacagi farkta total 5-10 cyclei gecmeyecek. 1 Hz = 1 Cycle per second demek. Bir cekirdek eger 4GHz hizda calisiyorsa saniyede 4 milyar cycle anlamina geliyor bu. Bu da 10 cycle icin 0.00000025 saniye surecek demek (yada 2.5 nano saniye). Bu fark ne programi, ne kullaniciyi etkiler. Ancak kodlarin okunabilirligi arasindaki fark gelistiricinin zihnindeki cognitive yuku arttirabilir. Bunlardan 10000 tane olsa programinda, hepsi ortalama 10 nano saniye fark ettiriyor olsa, hepsi birbirine bagimli fonksiyonlar olsa bunlarin, total de fark ettirecekleri yanit suresi alti ustu 0.1ms olurdu. Bu tarz durumlarda okunabilir fonksiyonu tercih etmek gerek diye dusunuyorum.

O yuzden ornekler biraz talihsiz olmus. Daha iyi ornekler sunulabilir. Daha gece gunduz farki tadinda. Ornegin Asal sayi hesaplamalarinda kullanilan Trial division - Wikipedia . Siradan naive yaklasim yerine bir takim basit optimizasyonlarla hizin kac kat degisebilecegini gosteren basit bir yaklasim.
 
Yüksek öğretim okumadım. Ancak çevremdeki okuyanlardan gördüğüm kadarıyla onlarda da gösterilmemiş gibi geliyor.
Bu konu kullanıcı deneyimi açısından çok ama çok ciddi bir konu. Bana kalırsa yazılımın en önemli konusudur. (Önemlilerinden değil direkt en önemlisi.)
Neden bilmiyorum ama... Devamını okumak için: Yazılımda performans ve optimizasyon konusu

Bu kadar okuyamam ben diyenler için veya üstteki yazdıklarımı anlamayanlar için özet:
Bir yıl önce WinUI 3 ve Optimizasyon konularını öğrenmeye başlamıştım ancak temel kötü olduğundan:
C#:
internal void ResponsiveSizeApply()
{
    bool maximized = mainWindow.WindowState is WindowState.Maximized;
    int size, top;
    if (userSettings.TitleBarVisible)
    {
        top = 28;
        size = maximized ? sizeInt32.Height - 40 : sizeInt32.Height - 32;
    }
    else
    {
        top = maximized ? -12 : -4;
        size = sizeInt32.Height;
    }
    NavViewVertical.Margin = new Thickness(NavViewVertical.Margin.Left, top, NavViewVertical.Margin.Right, NavViewVertical.Margin.Bottom);
    NavViewVertical.MinHeight = size;
}
Böyle olması gereken kod, böyle oluyor:
C#:
public void ResponsiveSizeApply()
{
    if (userSettings.TitleBarVisible)
    {
        NavViewVertical.MinHeight = mainWindow.WindowState is WindowState.Maximized ? sizeInt32.Height - 40 : sizeInt32.Height - 32;
        NavViewVertical.Margin = new Thickness(NavViewVertical.Margin.Left, 28, NavViewVertical.Margin.Right, NavViewVertical.Margin.Bottom);
    }
    else
    {
        NavViewVertical.MinHeight = sizeInt32.Height;
        NavViewVertical.Margin = mainWindow.WindowState is WindowState.Maximized
                               ? new Thickness(NavViewVertical.Margin.Left, -12, NavViewVertical.Margin.Right, NavViewVertical.Margin.Bottom)
                               : new Thickness(NavViewVertical.Margin.Left, -4, NavViewVertical.Margin.Right, NavViewVertical.Margin.Bottom);
    }
}
Aralarında mikro farklar varmış gibi görünmesine aldanmayın. Bu ve benzeri ufak hâtalar büyük projelerde çok ciddi sorun olabilir. Bunlardan yola çıkarak, yeni teklojinin ve yeni konuların ülkemizde de görülmesini umarım ve sizlerin de yorumlarınızı beklerim:)
Bu işin temeli Big O notasyonudur. Hangi dil,hangi teknolojiyi kullanırsanız zaman karmaşıklığı konusu bilmiyorsanız,optimizasyondan bahsedilemez.
 
Kalifiye bir CS programinda optimizasyondan bahsedilmiyor olma ihtimali var mi?

"Premature optimization is the root of all evil." D.Knuth.

Kendisinden dinlemek isteyenler ->
Bu içeriği görüntülemek için üçüncü taraf çerezlerini yerleştirmek için izninize ihtiyacımız olacak.
Daha detaylı bilgi için, çerezler sayfamıza bakınız.
 
Orneklerin yetersiz/fark yaratmayacak ornekler oldugunu dusunuyorum. Iki ornegin tahmini uretilmis ASM sonuclari;

Kod:
Example:ResponsiveSizeApply():this (FullOpts):
       push     rbp
       mov      rbp, rsp
       mov      rax, gword ptr [rdi+0x20]
       cmp      dword ptr [rax+0x08], 0
       sete     al
       movzx    rax, al
       mov      rcx, gword ptr [rdi+0x18]
       cmp      byte  ptr [rcx+0x08], 0
       jne      SHORT G_M51224_IG04
       mov      ecx, -12
       mov      edx, -4
       test     eax, eax
       cmove    ecx, edx
       mov      rax, gword ptr [rdi+0x08]
       mov      eax, dword ptr [rax+0x08]
       jmp      SHORT G_M51224_IG06
G_M51224_IG04:  ;; offset=0x0034
       mov      ecx, 28
       test     eax, eax
       je       SHORT G_M51224_IG05
       mov      rax, gword ptr [rdi+0x08]
       mov      eax, dword ptr [rax+0x08]
       add      eax, -40
       jmp      SHORT G_M51224_IG06
G_M51224_IG05:  ;; offset=0x0049
       mov      rax, gword ptr [rdi+0x08]
       mov      eax, dword ptr [rax+0x08]
       add      eax, -32
G_M51224_IG06:  ;; offset=0x0053
       mov      rdx, gword ptr [rdi+0x10]
       mov      rdi, rdx
       mov      esi, dword ptr [rdx+0x0C]
       mov      r8d, dword ptr [rdx+0x14]
       mov      r9d, dword ptr [rdx+0x18]
       mov      dword ptr [rdi+0x0C], esi
       mov      dword ptr [rdi+0x10], ecx
       mov      dword ptr [rdi+0x14], r8d
       mov      dword ptr [rdi+0x18], r9d
       mov      dword ptr [rdx+0x08], eax
       pop      rbp
       ret

Example:ResponsiveSizeApply():this (FullOpts):
       push     rbp
       mov      rbp, rsp
       mov      rax, gword ptr [rdi+0x18]
       cmp      byte  ptr [rax+0x08], 0
       jne      SHORT G_M56713_IG07
       mov      rax, gword ptr [rdi+0x10]
       mov      rcx, gword ptr [rdi+0x08]
       mov      ecx, dword ptr [rcx+0x08]
       mov      dword ptr [rax+0x08], ecx
       mov      rcx, rax
       mov      rdi, gword ptr [rdi+0x20]
       cmp      dword ptr [rdi+0x08], 0
       jne      SHORT G_M56713_IG04
       mov      edx, dword ptr [rax+0x0C]
       mov      edi, dword ptr [rax+0x14]
       mov      esi, dword ptr [rax+0x18]
       mov      r8d, -12
       jmp      SHORT G_M56713_IG05
G_M56713_IG04:  ;; offset=0x003A
       mov      edx, dword ptr [rax+0x0C]
       mov      edi, dword ptr [rax+0x14]
       mov      esi, dword ptr [rax+0x18]
       mov      r8d, -4
G_M56713_IG05:  ;; offset=0x0049
       add      rcx, 12
       mov      dword ptr [rcx], edx
       mov      dword ptr [rcx+0x04], r8d
       mov      dword ptr [rcx+0x08], edi
       mov      dword ptr [rcx+0x0C], esi
       pop      rbp
       ret
G_M56713_IG07:  ;; offset=0x005B
       mov      rax, gword ptr [rdi+0x10]
       mov      rcx, rax
       mov      rdx, gword ptr [rdi+0x20]
       cmp      dword ptr [rdx+0x08], 0
       jne      SHORT G_M56713_IG08
       mov      rdi, gword ptr [rdi+0x08]
       mov      edx, dword ptr [rdi+0x08]
       add      edx, -40
       jmp      SHORT G_M56713_IG09
G_M56713_IG08:  ;; offset=0x0078
       mov      rdx, gword ptr [rdi+0x08]
       mov      edx, dword ptr [rdx+0x08]
       add      edx, -32
G_M56713_IG09:  ;; offset=0x0082
       mov      dword ptr [rcx+0x08], edx
       mov      rcx, rax
       mov      edx, dword ptr [rax+0x0C]
       mov      edi, dword ptr [rax+0x14]
       mov      eax, dword ptr [rax+0x18]
       mov      dword ptr [rcx+0x0C], edx
       mov      dword ptr [rcx+0x10], 28
       mov      dword ptr [rcx+0x14], edi
       mov      dword ptr [rcx+0x18], eax
       pop      rbp
       ret

(Kullanilan nesnelerin yapisini tahmin ederek yazdigim basit bir kodun uretimi bu, tabii ki tahmini degerler fark edebilir. Agresif optimizasyonlar acik ve tahmini gelebilecek inputlara gore kirpmalar yapabiliyor ama yapmasin diye ugrastim biraz.)

Ikisi arasinda 2. paylasitigin kod cok minimal bir marjinde daha optimize. Ama bana sorarsan bir numarayi okumak kat ve kat daha kolay. Ternary operator bu sekilde kullanildigini kodun okunabilirligini siddetle baltaliyor.

JIT kullanirken JIT'in en buyuk artisi zamanla kodun hot pathini cacheleyebilir olmasi. Birebir ayni inputlar geliyorsa ve dinamizim asiri degilse, hesaplamalari inline hale getirebilir, onbellekleyebilir. Bu durumlarda optimize olmayan kod optimize kodla ayni performansi da verebilir. (Compilerin kodu nasil optimize ettigine bagli tabii ki) Ancak burada ki orneklerin asil yeterli olmamasini dusunmemim sebebi, compiler optimizasyon uygulamasa bile ikisi de esit sayida UI degisikligi yapiyor.

Local assignment sayisi bir sey degistirmeyecek cunku primitive typelarin assignmentlari genelde tek cycle'da calisiyor. Nano saniye bile tutmaz primitive typelarin assignment islemleri. Primitive olmayanlarda ise referans copy gerceklesir, dolayisiyla constructor sonrasinda ki assignment islemi tek cycle da calisir. Constructor'da ne kadar fazla seyin baslatildigina/assign edildigine bagli olarak cycle sayisi degisecek ama buradaki Thickness'in tahmini olarak 10-15 cycleda baslatildigini varsayabilirim. Accesslerin surelerini es geciyorum.

Bu da bu iki kod arasinda ki farkin sadece bir iki extra primitive assignment cyclelari oldugu anlamina geliyor. Haliyle bunun yaratacagi farkta total 5-10 cyclei gecmeyecek. 1 Hz = 1 Cycle per second demek. Bir cekirdek eger 4GHz hizda calisiyorsa saniyede 4 milyar cycle anlamina geliyor bu. Bu da 10 cycle icin 0.00000025 saniye surecek demek (yada 2.5 nano saniye). Bu fark ne programi, ne kullaniciyi etkiler. Ancak kodlarin okunabilirligi arasindaki fark gelistiricinin zihnindeki cognitive yuku arttirabilir. Bunlardan 10000 tane olsa programinda, hepsi ortalama 10 nano saniye fark ettiriyor olsa, hepsi birbirine bagimli fonksiyonlar olsa bunlarin, total de fark ettirecekleri yanit suresi alti ustu 0.1ms olurdu. Bu tarz durumlarda okunabilir fonksiyonu tercih etmek gerek diye dusunuyorum.

O yuzden ornekler biraz talihsiz olmus. Daha iyi ornekler sunulabilir. Daha gece gunduz farki tadinda. Ornegin Asal sayi hesaplamalarinda kullanilan Trial division - Wikipedia . Siradan naive yaklasim yerine bir takim basit optimizasyonlarla hizin kac kat degisebilecegini gosteren basit bir yaklasim.
İlk attığım okunabilirlik açısından da daha iyi değil mi? Optimizasyonda da IL fazlalığı olmasından ve kod tekrarından kaçınılmıyor mu?
Ayrıca her branchta tekrar nesne oluşturulmasından kaçınılması lazım değil midir?
Kalifiye bir CS programinda optimizasyondan bahsedilmiyor olma ihtimali var mi?
Türk müfredatlarından bahsediyorum. Kendi aldığım ve çevremdeki insanlardan gördüğüm üzere hepsi yabancı kaynaklardan öğreniyor bu konuları.
Ben, o yok sayılabilir performans kaybını bile yaşatmak istemiyorum. Biliyorum belki fazla kafaya takmış gibi geliyor olabilirim sizin için ama benim düşüncem bu yönde.
Ayrıca bunun gibi binlerce hatta onbinlerce kod bloğunun olduğunu düşünürsek bence bu farkın önemli olacağını düşünüyorum. Bir de bu örnek çok cüzzi çok ufak örnek. Benzer örneklere bakabilirsiniz blogtan.
"Premature optimization is the root of all evil." D.Knuth.

Kendisinden dinlemek isteyenler ->
Bu içeriği görüntülemek için üçüncü taraf çerezlerini yerleştirmek için izninize ihtiyacımız olacak.
Daha detaylı bilgi için, çerezler sayfamıza bakınız.

Gerçekten konuştukları çok mantıklı geliyor ancak bazı yapılar vardır sonradan değişiklik gerekince projeyi kökünden değiştirmen gerektirebilir. Örneğin ben Resw dosyası ile Loc yapıyorum ama aralarında minimum fark olsa da yayımlarken iniparse edip paketliyorum. Kontrol için Resw çok daha esnek ve bunu biliyorum.
 
Son düzenleme:
Ben, o yok sayılabilir performans kaybını bile yaşatmak istemiyorum. Biliyorum belki fazla kafaya takmış gibi geliyor olabilirim sizin için ama benim düşüncem bu yönde.

Biz de istemiyoruz performans kaybi. Burada isin icine surduruebilirlik, okunabilirlik, test edilebilirlik, efor & kazanc hesaplamalari girer.

Senin diskten bir sey okuyup sonra sort eden kodda yapacagin %10 kazanim bir sey ifade etmez zaten I/O bound bir islem.

Kodu daha karisik hale getirirsin, okumasi & anlamasi & degistirmesi & test etmesi & senelerce up-to-date tutmasi daha zor hale getirir ve sonunda dersin ki "lanet olsun su %10 yavas calissin ama bas agritmasin".

Sonucta muhendislikte alacagin her kararin artisini ve eksisini teraziye koyup tartarsin. O yuzden bazen hic kod yazmamak bug yazmaktan daha efektif olacaktir. Zaten Knuth'un anlattigi da bu.

Biz de zamaninda Redis'ten aldim Kafka'dan gecirdim Casandradan asenkron 100 prefetch cayir cayir process ettim diye boburlendigimiz kodlar yazdik ama bir gun piyasada karsina bir gereksinim cikar en basit ve isi kurtaran haliyle yapip gecmek en dogru cozum olur.

Sahsen ben ilk yolladigin kodu tercih ederim; okumasi daha kolay oldugu icin. Sadece harcdoded "-4" , "28" gibi degerleri sevmem; onlari constant ya da parametrik yapip test edilebilir hale getirmeyi tercih ederdim.
 
İlk attığım okunabilirlik açısından da daha iyi değil mi? Optimizasyonda da IL fazlalığı olmasından ve kod tekrarından kaçınılmıyor mu?
Ama bana sorarsan bir numarayi okumak kat ve kat daha kolay. Ternary operator bu sekilde kullanildigini kodun okunabilirligini siddetle baltaliyor.
Evet. Ilki daha okunabilir, ikinci kagit ustunde daha "optimize" cunku ayni islemleri daha az sayida ara elemanla yapiyorsun. Ama dedigim gibi bu seviyede optimizasyon gereksiz bir ugras ve cogu zaman kodun okunabilirliginin icinden geciyor. Ekstra 10 nano saniyelik kazanim icin kodun okunabilirligini baltalamamak lazim.

Kod tekrarindan kacinmak size optimizasyonu ve maintainability icin olur, hiz artisi saglamaz. Dosyaya yazdirma islemini belirli bir formatta 10 satirda yapiyorsan ve bu kod 50 farkli yerde tekrar ediyorsa, 49 * 10 = 490 gereksiz satir demek. Bunun iki dezavantaji var;
1. Herhangi bir mantik degisikligini 50 yere de uygulaman lazim. Dolayisiyla maintainability dustu.
2. Compile ettiginde C#'ta source filelar IL olarak executable'in icine paketleniyor. dotnet compileri kendi trimming yaparak zaten temizliyor tekrar eden kisimlari ve kullanilmayan methodlari vs ama temizlemezse bu satirlarda paketlenecegi icin boyut buyumesi yasaniyor. 500 satir cok gozukmeyebilir (alti ustu bi kac kb tutacak) ama bunu neredeyse her fonksiyon icin yaptigini dusunursen, buyuk bir projede gereksiz boyut artisina sebep olacak.

IL artisi da tek basina bir problem degil. JIT compiler ve dotnet compiler fazlasiyla akilli, deterministic (kesin) ve non-deterministic (tahmini) analizler yaparlar kodun hangi yollari kullanabilecegine dair.

JIT compiler bir de kod calisirken hangi yollarin daha cok kullanildigini da takip eder. Bu durumlarda eger daha anlasilabilir/tahmin edilebilir bir kod yazildiysa (daha duz bir mantik, ne kadar az ve net branching o kadar iyi) kalkip bu alani kendi kisaltabilir. Looplara mesela unroll yapip looplarin calisma hizini arttirabilir. jump operation tek basina bir kac cycle tuketiyor mesela, loop unrolling ise tahmin edilebilir sayida calisan dongulerde jump islemini aradan kaldirip cagrilari arka arkaya dizer, boylece jump aradan ciktigi icin sirali islemler gecikme olmaksizin calisir. Yani optimizasyonun statik kurallari yok. Daha cok duruma bagli.

Daha az IL != daha optimize kod. Genel bir yanlis anlasilma bu sadece. Interpreted diller icin dogruydu bu mevzu yillar yillar once ama simdi onlarda bile cesitli onbellekleme, branch prediction vs yapiliyor. Dolayisiyla her zaman daha az kod != daha hizli program. Bazen dogru olabilir, gereksiz islemler giriyorsa araya program yavaslayabilir mesela, yada bazen kullanilan sozde daha kisa koddaki islemlerden biri daha agir bir method kullaniyordur, daha hafif versiyonu daha uzundur yazim olarak gibi gibi.

Ayrıca her branchta tekrar nesne oluşturulmasından kaçınılması lazım değil midir?
Her zaman degil. Branchlere bagli, eger tum branchler icin ortaksa, branchin execution timeini kisaltmak icin cikarabilirsin icinden. Ancak her zaman branchin suresinin kisalacaginin bir garantisi yok, constructori cagirmak (mesela tum degiskenler lock kullaniyorsa, setterlar once lock sonra unlock yapacak, haliyle buna ihtiyac duymayan constructor daha hizli calisacak) setter cagirmaktan daha kisa sure alabilir. Sartlara bagli yani.

Yada mesela onden parametreleri hazirlarken constructor icin eger kullandigin nesnelerden herhangi biri complex bir sinif yada memory heavy bir nesneyse, daha sonrasinda new X(memory_heavy_object) ile cagirmak beklediginden daha cok memory kullanilmasina sebep olabilir. Sonucta C#'ta fonksiyon parametreleri ref, out veya in ile isaretlenmediyse pass by value ile yapilir. Bu da copy ile sonuclanabilir, copy ile sonuclanmasi halindeyse bir sonraki GC dalgasina kadar memory kullaniminda artisa sebep olabilir.

Asil branch sayisini azaltmak, olabildigince branchingten kacinmak gerekiyor. CPU'lar branch prediction dedigimiz bir islem yapiyor, elde ettigi dataya gore onden branchi belirleyip stackte siraladiktan sonra jump islemi olmadan isine devam ediyor. Bu da performansi arttiriyor haliyle, cunku if'i execute ettikten sonra jump yapmak yerine direkt ucuna ekleyip devam ediyor.

Ancak if dogru cikmazsa, branch prediction dogru tahmin edemediyse, stackteki call sirasini bozup yerine diger karsilanan durumunkini ekler. Eger cok fazla branching yoksa bu islem cok agir degil, ancak atiyorum bir loop icerisinde 10 farkli branch varsa ve CPU'nun branch predictioni basarisiz olduysa baslayacak siradan if'leri, else if leri calistirmaya. Bir de eger branchlerde ortak bir sey varsa compiler yada JIT compiler onlari zaten kendi de gruplayacak, branch icerisinde ki islem sayisini azaltacak. Boylece branch tahmini yanlis gittiginde yeniden siralamasi gereken islem sayisi azalacak. Gibi gibi.
 
Biz de istemiyoruz performans kaybi. Burada isin icine surduruebilirlik, okunabilirlik, test edilebilirlik, efor & kazanc hesaplamalari girer.

Senin diskten bir sey okuyup sonra sort eden kodda yapacagin %10 kazanim bir sey ifade etmez zaten I/O bound bir islem.

Kodu daha karisik hale getirirsin, okumasi & anlamasi & degistirmesi & test etmesi & senelerce up-to-date tutmasi daha zor hale getirir ve sonunda dersin ki "lanet olsun su %10 yavas calissin ama bas agritmasin".

Sonucta muhendislikte alacagin her kararin artisini ve eksisini teraziye koyup tartarsin. O yuzden bazen hic kod yazmamak bug yazmaktan daha efektif olacaktir. Zaten Knuth'un anlattigi da bu.

Biz de zamaninda Redis'ten aldim Kafka'dan gecirdim Casandradan asenkron 100 prefetch cayir cayir process ettim diye boburlendigimiz kodlar yazdik ama bir gun piyasada karsina bir gereksinim cikar en basit ve isi kurtaran haliyle yapip gecmek en dogru cozum olur.

Sahsen ben ilk yolladigin kodu tercih ederim; okumasi daha kolay oldugu icin.
Asenkron programlama için dediklerinize %101 katılıyorum zamanı gelince ciddi baş yorabilir. Ben bir sene öncesine kadar full senkron çalışıyordum ve optimizasyon da olmayınca çok kötü oluyordu. Veritabanı ve MT'yi ilgilendirmeyen bütün işlemleri asnyc yapıp fazlasını abartmama yönündeyim bunda hemfikiriz. Ancak senkron olupta ekstradan nesne, değişken oluşturmak, fazladan kod blokları olması gibi şeylere hoş bakmıyorum. Bana göre 1. yöntem daha da okunulabilir geliyor çünkü eğer o method içinde başka nesneleri de yönetmem gerektiğinde 10 satırlık yazacağım kod 20 belki 30 satıra çıkabilir ki bloktaki örneğimde olduğu gibi.
Sadece harcdoded "-4" , "28" gibi degerleri sevmem; onlari constant ya da parametrik yapip test edilebilir hale getirmeyi tercih ederdim.
O da doğru. Bazen gözümden kaçıyor işte. Teşekkürler.
Evet. Ilki daha okunabilir, ikinci kagit ustunde daha "optimize" cunku ayni islemleri daha az sayida ara elemanla yapiyorsun. Ama dedigim gibi bu seviyede optimizasyon gereksiz bir ugras ve cogu zaman kodun okunabilirliginin icinden geciyor. Ekstra 10 nano saniyelik kazanim icin kodun okunabilirligini baltalamamak lazim.

Kod tekrarindan kacinmak size optimizasyonu ve maintainability icin olur, hiz artisi saglamaz. Dosyaya yazdirma islemini belirli bir formatta 10 satirda yapiyorsan ve bu kod 50 farkli yerde tekrar ediyorsa, 49 * 10 = 490 gereksiz satir demek. Bunun iki dezavantaji var;
1. Herhangi bir mantik degisikligini 50 yere de uygulaman lazim. Dolayisiyla maintainability dustu.
2. Compile ettiginde C#'ta source filelar IL olarak executable'in icine paketleniyor. dotnet compileri kendi trimming yaparak zaten temizliyor tekrar eden kisimlari ve kullanilmayan methodlari vs ama temizlemezse bu satirlarda paketlenecegi icin boyut buyumesi yasaniyor. 500 satir cok gozukmeyebilir (alti ustu bi kac kb tutacak) ama bunu neredeyse her fonksiyon icin yaptigini dusunursen, buyuk bir projede gereksiz boyut artisina sebep olacak.
İncelediğim örneklerde ve tutoriallerde genelde ilk yöntem kullanılıyor. Tabi yeni başlayanlar için 2. yöntem ağırlıklı olarak yapılıyor. Benim için Hâla ilk yöntem çok da okunulabilir oluyor. İleride değişmeyeceği kesin olan ve birbiriyle bağlantılı olan bölümlerde bu optimizasyonun uygulaması, problem yaratmayacağını düşünüyorum ki bir projede normalde 1-2 yılda berbat kodla yapabileceğimi 3 yılda düzgün kodla yapmaya çalışıyorum ki hâla daha düzeltiyorum/iyileştiriyorum. Her geçen gün belirli kod bloklarının "Artık tamam bu paketlenip sipariş verilmeye hazır" denecek seviyeye getirdikten sonra o bölümü bitirmiş sayıyorum.
IL artisi da tek basina bir problem degil. JIT compiler ve dotnet compiler fazlasiyla akilli, deterministic (kesin) ve non-deterministic (tahmini) analizler yaparlar kodun hangi yollari kullanabilecegine dair.

JIT compiler bir de kod calisirken hangi yollarin daha cok kullanildigini da takip eder. Bu durumlarda eger daha anlasilabilir/tahmin edilebilir bir kod yazildiysa (daha duz bir mantik, ne kadar az ve net branching o kadar iyi) kalkip bu alani kendi kisaltabilir. Looplara mesela unroll yapip looplarin calisma hizini arttirabilir. jump operation tek basina bir kac cycle tuketiyor mesela, loop unrolling ise tahmin edilebilir sayida calisan dongulerde jump islemini aradan kaldirip cagrilari arka arkaya dizer, boylece jump aradan ciktigi icin sirali islemler gecikme olmaksizin calisir. Yani optimizasyonun statik kurallari yok. Daha cok duruma bagli.

Daha az IL != daha optimize kod. Genel bir yanlis anlasilma bu sadece. Interpreted diller icin dogruydu bu mevzu yillar yillar once ama simdi onlarda bile cesitli onbellekleme, branch prediction vs yapiliyor. Dolayisiyla her zaman daha az kod != daha hizli program. Bazen dogru olabilir, gereksiz islemler giriyorsa araya program yavaslayabilir mesela, yada bazen kullanilan sozde daha kisa koddaki islemlerden biri daha agir bir method kullaniyordur, daha hafif versiyonu daha uzundur yazim olarak gibi gibi.
Bu bölüm çoğunlukla doğru oluyor ama bazen istisnalar olabiliyor. Daha az demek daha az kod demek savunmasını geçen seneye kadar yapıyordum. Artık olmadığını ben de biliyorum. Örneğin:
C#:
bool deneme, funded;
string[] dizi; (eleman eklediğimi düşünün)
if (deneme)
    foreach(var item in deneme)
        if (item == searched)
            funded = true;

// Üstteki daha optimize sandığım ama aslında itemi bulduktan sonra döngüden çıkması gerekiyor.
Ve benzeri ufak tefek kod azaltmaları.
Her zaman degil. Branchlere bagli, eger tum branchler icin ortaksa, branchin execution timeini kisaltmak icin cikarabilirsin icinden. Ancak her zaman branchin suresinin kisalacaginin bir garantisi yok, constructori cagirmak (mesela tum degiskenler lock kullaniyorsa, setterlar once lock sonra unlock yapacak, haliyle buna ihtiyac duymayan constructor daha hizli calisacak) setter cagirmaktan daha kisa sure alabilir. Sartlara bagli yani.

Yada mesela onden parametreleri hazirlarken constructor icin eger kullandigin nesnelerden herhangi biri complex bir sinif yada memory heavy bir nesneyse, daha sonrasinda new X(memory_heavy_object) ile cagirmak beklediginden daha cok memory kullanilmasina sebep olabilir. Sonucta C#'ta fonksiyon parametreleri ref, out veya in ile isaretlenmediyse pass by value ile yapilir. Bu da copy ile sonuclanabilir, copy ile sonuclanmasi halindeyse bir sonraki GC dalgasina kadar memory kullaniminda artisa sebep olabilir.

Asil branch sayisini azaltmak, olabildigince branchingten kacinmak gerekiyor. CPU'lar branch prediction dedigimiz bir islem yapiyor, elde ettigi dataya gore onden branchi belirleyip stackte siraladiktan sonra jump islemi olmadan isine devam ediyor. Bu da performansi arttiriyor haliyle, cunku if'i execute ettikten sonra jump yapmak yerine direkt ucuna ekleyip devam ediyor.

Ancak if dogru cikmazsa, branch prediction dogru tahmin edemediyse, stackteki call sirasini bozup yerine diger karsilanan durumunkini ekler. Eger cok fazla branching yoksa bu islem cok agir degil, ancak atiyorum bir loop icerisinde 10 farkli branch varsa ve CPU'nun branch predictioni basarisiz olduysa baslayacak siradan if'leri, else if leri calistirmaya. Bir de eger branchlerde ortak bir sey varsa compiler yada JIT compiler onlari zaten kendi de gruplayacak, branch icerisinde ki islem sayisini azaltacak. Boylece branch tahmini yanlis gittiginde yeniden siralamasi gereken islem sayisi azalacak. Gibi gibi.
Branchtan kaçınılamaz konumlarda branch içini olabildiğince az tutma mantığı peki?

O da doğru. Bazen gözümden kaçıyor işte. Teşekkürler.
Mesela orayı da şöyle düzelttim.

C#:
internal void ResponsiveSizeApply()
{
    bool maximized = mainWindow.WindowState is WindowState.Maximized;
    int size, top;
    if (userSettings.TitleBarVisible)
    {
        top = Constants.VERTICAL_MARGIN_TOP;
        size = sizeInt32.Height - (maximized ? Constants.VERTICAL_MINSIZE_MAX : Constants.VERTICAL_MINSIZE);
    }
    else
    {
        top = maximized ? Constants.VERTICAL_MARGIN_TOP_MAX : Constants.VERTICAL_MARGIN_TOP_V;
        size = sizeInt32.Height;
    }
    NavViewVertical.Margin = new Thickness(NavViewVertical.Margin.Left, top, NavViewVertical.Margin.Right, NavViewVertical.Margin.Bottom);
    NavViewVertical.MinHeight = size;
}
Bence şuan tam da olmasını istediğim gibi. Yani:
"Artık tamam bu paketlenip sipariş verilmeye hazır"

C#:
namespace Windoc.Utilities;
class Constants
{
    internal const int
        VERTICAL_MARGIN_TOP = 28,
        VERTICAL_MARGIN_TOP_V = -4,
        VERTICAL_MARGIN_TOP_MAX = -12,
        VERTICAL_MINSIZE = 32,
        VERTICAL_MINSIZE_MAX = 40;
}
 
Son düzenleme:
Branchtan kaçınılamaz konumlarda branch içini olabildiğince az tutma mantığı peki?
Branch icini az tutmak maintainability acisindan arti sunabilir ama performans acisindan pek bir farki olmayacak. Bir branch execute edilmiyorsa zaten onun icinde ne kadar islem oldugu bir sey fark ettirmiyor. Ben nesting sevmedigim icin mumkun oldugunda guard clauselar ile (reverse condition) tek ice girmeye indiririm. Aksi halde okunabilirligi korumaya odaklanirim. Bu bir branchte daha fazla satir olmasi demekse, daha fazla satirla yaparim. Dedigim gibi, dikkate alinabilir bir performans farki olmayacak cunku.
 
Branch icini az tutmak maintainability acisindan arti sunabilir ama performans acisindan pek bir farki olmayacak. Bir branch execute edilmiyorsa zaten onun icinde ne kadar islem oldugu bir sey fark ettirmiyor. Ben nesting sevmedigim icin mumkun oldugunda guard clauselar ile (reverse condition) tek ice girmeye indiririm. Aksi halde okunabilirligi korumaya odaklanirim. Bu bir branchte daha fazla satir olmasi demekse, daha fazla satirla yaparim. Dedigim gibi, dikkate alinabilir bir performans farki olmayacak cunku.
Peki uzun lafın kısası olarak:
C#:
public void ResponsiveSizeApply()
{
    if (userSettings.TitleBarVisible)
    {
        NavViewVertical.MinHeight = mainWindow.WindowState is WindowState.Maximized ? sizeInt32.Height - 40 : sizeInt32.Height - 32;
        NavViewVertical.Margin = new Thickness(NavViewVertical.Margin.Left, 28, NavViewVertical.Margin.Right, NavViewVertical.Margin.Bottom);
    }
    else
    {
        NavViewVertical.MinHeight = sizeInt32.Height;
        NavViewVertical.Margin = mainWindow.WindowState is WindowState.Maximized
                               ? new Thickness(NavViewVertical.Margin.Left, -12, NavViewVertical.Margin.Right, NavViewVertical.Margin.Bottom)
                               : new Thickness(NavViewVertical.Margin.Left, -4, NavViewVertical.Margin.Right, NavViewVertical.Margin.Bottom);
    }
}
Böyle mi yazamak, yoksa böyle mi tercih edersiniz:
C#:
internal void ResponsiveSizeApply()
{
    bool maximized = mainWindow.WindowState is WindowState.Maximized;
    int size, top;
    if (userSettings.TitleBarVisible)
    {
        top = Constants.VERTICAL_MARGIN_TOP;
        size = sizeInt32.Height - (maximized ? Constants.VERTICAL_MINSIZE_MAX : Constants.VERTICAL_MINSIZE);
    }
    else
    {
        top = maximized ? Constants.VERTICAL_MARGIN_TOP_MAX : Constants.VERTICAL_MARGIN_TOP_V;
        size = sizeInt32.Height;
    }
    NavViewVertical.Margin = new Thickness(NavViewVertical.Margin.Left, top, NavViewVertical.Margin.Right, NavViewVertical.Margin.Bottom);
    NavViewVertical.MinHeight = size;
}

C#:
namespace Windoc.Utilities;
class Constants
{
    internal const int
        VERTICAL_MARGIN_TOP = 28,
        VERTICAL_MARGIN_TOP_V = -4,
        VERTICAL_MARGIN_TOP_MAX = -12,
        VERTICAL_MINSIZE = 32,
        VERTICAL_MINSIZE_MAX = 40;
}
 

Technopat Haberler

Geri
Yukarı