C# İlginç Bilgiler

Ben Kenobi

Dekan
Katılım
6 Kasım 2011
Mesajlar
7,336
Reaksiyon puanı
3,155
Puanları
113
C# hakkında her geliştiricinin bilmediği çok gerekli olmasa da sistemin işleyişini daha iyi kavramamıza yardımcı olabilen bazı bilgileri paylaşmak istedim.
Testler Release modunda her bir kod için 5 kez yapılmıştır ancak son 3 sonuç gösterilmektedir ve hesaplamalar için 100 nanosaniye ayırt edebilen Stopwatch sınıfı kullanılmıştır.

for ve foreach

İki döngü de benzer amaçlara hizmet etseler de işleyişleri bakımından farklara sahiptir. Genel olarak baktığımızda ise for döngüsü foreach döngüsüne göre nanosaniye bazında daha hızlıdır.
Kod:
int[] x = new int[100000000];

for (int i = 0; i < x.Length; i++)
{
    if (i % 2 == 0)
    {
        //
    }
}
Kod:
int[] x = new int[100000000];

foreach (int i in x)
{
    if (i % 2 == 0)
    {
        //
    }
}
1. Kod
229 ms
230 ms
226 ms

2. Kod
366 ms
356 ms
359 ms

Bu hız farkının nedeni foreach'in ienumerable arayüzü ve onun getirdiği birtakım ekstra işlerle uğraşmasıdır.
For döngüsü foreach'in yapabildiği her işi yapabilir ancak foreach döngüsü ise bazı durumlarda daha pratik gelir.



Fonksiyonel Bütünlük
Fonksiyon/Metodlar birçok alanda kolaylık, esneklik ve modülerlik sağlar. Ancak bir işin yarısını bir metoda yarısını diğer metoda yaptırmak bize nanosaniyeler bazında kayıp yaşatır.
Kod:
for (int i = 0; i < 100000000; i++)
{
    string x = "";
    string y = "";
}
Kod:
for (int i = 0; i < 100000000; i++)
{
    string x = "";
    Fonksiyon2();
}

void Fonksiyon2()
{
    string y = "";
}
1. Kod
263 ms
266 ms
269 ms

2. Kod
307 ms
313 ms
307 ms

Bu kaybın nedeni yeni metoda geçişteki adres hesaplamaları ve dolaylı olarak ona bağlı işlemlerdir.



İşlemsel Bütünlük
İşlemlerimiz genellikle belli basamaklarda belli işleri yapmak üzere yoğunlaştığından aynı işi yapan kodların arka arkaya gelmesi zor ihtimaldir. Ancak aynı işi yapan kodlar arka arkaya geldiği takdirde programımız nanosaniye bazında hız kazanacaktır.
Kod:
for (int i = 0; i < 100000000; i++)
{
    string x = "a";
    string y = "b";
    string z = "c";

    int a = i;
    int b = i;
    int c = i;

    a++;
    b++;
    c++
}
Kod:
for (int i = 0; i < 100000000; i++)
{
    string x = "a";
    int a = i;
    a++;
    
    string y = "b";
    int b = i;
    b++;
    
    string z = "c";    
    int c = i;    
    c++
}

1. Kod
379 ms
385 ms
371 ms

2. Kod
398 ms
394 ms
396 ms

Aynı işi yapan kodlar arka arkaya geldiğinde register ve cache üzerindeki değer değişimleri azalacağı için oluşan bütünlük artışı hızın kaynağını oluşturur.



int ve diğerleri
int aritmetik hesaplamalarda diğer formatlara göre nanosaniye bazında daha hızlıdır.(uint, long, ulong, short, ushort, byte, sbyte) Aritmetik işlem yapılırken diğer formatlar int dönüştürülüp öyle işleme girer. (long ve ulong hariç)
Kod:
for (int i = 0; i < 100000000; i++)
{
    int x = 0;
    x++;
    x = x * x;
    x = x / x;
}
Kod:
for (int i = 0; i < 100000000; i++)
{
    uint x = 0;
    x++;
    x = x * x;
    x = x / x;
}
Kod:
for (int i = 0; i < 100000000; i++)
{
    byte x = 0;
    x++;
    x = (byte)(x * x);
    x = (byte)(x / x);
}
Kod:
for (int i = 0; i < 100000000; i++)
{
    sbyte x = 0;
    x++;
    x = (sbyte)(x * x);
    x = (sbyte)(x / x);
}
Kod:
for (int i = 0; i < 100000000; i++)
{
    short x = 0;
    x++;
    x = (short)(x * x);
    x = (short)(x / x);
}
Kod:
for (int i = 0; i < 100000000; i++)
{
    ushort x = 0;
    x++;
    x = (ushort)(x * x);
    x = (ushort)(x / x);
}
Kod:
for (int i = 0; i < 100000000; i++)
{
    long x = 0;
    x++;
    x = x * x;
    x = x / x;
}
Kod:
for (int i = 0; i < 100000000; i++)
{
    ulong x = 0;
    x++;
    x = x * x;
    x = x / x;
}

int
525 ms
520 ms
523 ms

uint
576 ms
575 ms
585 ms

short
595 ms
597 ms
592 ms

ushort
602 ms
600 ms
610 ms

sbyte
608 ms
622 ms
601 ms

byte
617 ms
621 ms
614 ms

long
1588 ms
1592 ms
1589 ms

ulong
1527 ms
1523 ms
1531 ms

Sonuçlar küçükten büyüğe sıralanmıştır. Kodlarda görüldüğü üzere short, ushort, byte, sbyte direct casting yapılmadığı sürece derleme hatası vermektedir. Diğer formatların int dönüştürülmesinin sebebi registerların boyutu ile alakalıdır. Unsigned türlerin signed türlerden biraz daha yavaş olmalarının sebebi ise unsigned türlerin aritmetik işlem öncesi ek bir işlem ile signed hale dönüştürülmesinden kaynaklıdır. Çünkü unsigned türler için özel aritmetik işlem yapan instructionlar işlemcide mevcut değildir.
long ve ulongtaki işlemlerin çok daha yavaş olmasının sebebinde ise birçok faktör yatar. C#'ın 64 bit uyumu, işlemcilerin 64 bit uyumu, işlemci L1-L2-L3 cache belleklerindeki 64 bit verilerin girememe oranının artması, sistemde çalışan 32 bit programlar...



i++ ve ++i
İşte ilginç başka bir bilgi. ++i kullanmak i++ kullanmaya göre nanosaniye bazında daha hızlıdır.
Kod:
for (int i = 0; i < 100000000; i++)
{
    int x = 0;
    ++x;
    ++x;
}
Kod:
for (int i = 0; i < 100000000; i++)
{
    int x = 0;
    x++;
    x++;
}

1. Kod
254 ms
256 ms
255 ms

2. Kod
260 ms
269 ms
266 ms

i++ ++i'ye göre 1 adet daha fazla instruction içerir. Nanosaniyelik hız farkının nedeni budur.



sealed ve diğerleri
sealed anahtar kelimesi eklendiği sınıftan başka sınıfların kaynak alınarak oluşturulmasını engeller ve nanosaniyelik hız artışı sağlar.
Kod:
for (int i = 0; i < 100000000; i++)
{
    Sınıf sınıf = new Sınıf();
}

sealed class Sınıf
{
}
Kod:
for (int i = 0; i < 100000000; i++)
{
    Sınıf sınıf = new Sınıf();
}

class Sınıf
{
}

1. Kod
883 ms
871 ms
879 ms

2. Kod
897 ms
907 ms
889 ms

Inheritence işleminin yapılmayacağını bilen compiler buna göre instructionlardaki gereksiz birkaç yeri kaldırır, hız farkının temel sebebini bu oluşturur.



readonly ve diğerleri
readonly anahtar kelimesi eklendiği değişkenin constructor ve variable initializer haricinde atanmasını engeller ve nanosaniyelik ölçüde o değişkenle ilgili işlemleri yavaşlatır.
Kod:
class Sınıf
{
    int x = 0;
    public Sınıf()
    {
        for (int i = 0; i < 100000000; i++)
        {
            x = i;
            x++;
        }
    }
}
Kod:
class Sınıf
{
    readonly int x = 0;
    public Sınıf()
    {
        for (int i = 0; i < 100000000; i++)
        {
            x = i;
            x++;
        }
    }
}

1. Kod
249 ms
258 ms
248 ms

2. Kod
260 ms
269 ms
263 ms

yavaşlamanın temel nedeni readonly özelliği sağlamak için compilerın değişkene ek getter ve setter özellikleri eklemesidir.



const ve diğerleri
const değişmeyecek sabit değerleri isimlendirmek için kullanılır ve nanosaniye bazında hız artışı sağlar.
Kod:
const int x = 100;

int a;
int b;
int c;
public Sınıf()
{
    for (int i = 0; i < 100000000; i++)
    {
        a = x;
        b = x;
        c = x;
    }
}
Kod:
int x = 100;

int a;
int b;
int c;
public Sınıf()
{
    for (int i = 0; i < 100000000; i++)
    {
        a = x;
        b = x;
        c = x;
    }
}

1. Kod
281 ms
279 ms
277 ms

2. Kod
307 ms
298 ms
310 ms

Const bellekte yer kaplamaz, instruction içerisine immediate olarak gömülür, adres hesabı gerektirmez ve hızını buradan alır.



static ve diğerleri
static eklendiği değişkeni türetilen objeler içerisinde ortak hale getirir ve değişkene ek hız kazandırır.
Kod:
static int x = 100;

public Sınıf()
{
    for (int i = 0; i < 100000000; i++)
    {
        x++;
        x--;
        x = x * x;
        x = x / x;
    }
}
Kod:
int x = 100;

public Sınıf()
{
    for (int i = 0; i < 100000000; i++)
    {
        x++;
        x--;
        x = x * x;
        x = x / x;
    }
}

1. Kod
1765 ms
1781 ms
1773 ms

2. Kod

1832 ms
1856 ms
1842 ms

static değişkenler bir nesneye bağlı olmadıkları için ek adres hesabı gerektirmez ve normal değişkenlerin hız kaybı yaşamasının nedeni budur.



Ekstra Kodlar
Kodların daha kolay okunabilmesi için işlemler parçalanıp her bir parça ayrı yazılması sıkça kullanılır ancak bu da bize nanosaniyelik kayıplar yaşatır.
Kod:
int x = 100;
Random random = new Random();
public Sınıf()
{
    for (int i = 0; i < 200000000; i++)
    {
       int sonuç = (x + 10) * (x - 10) * random.Next();
    }
}
Kod:
int x = 100;
Random random = new Random();
public Sınıf()
{
    for (int i = 0; i < 200000000; i++)
    {
        int a = x + 10;
        int b = x - 10;
        int c = random.Next();
        int sonuç = a * b * c;
    }
}

1. Kod
3024 ms
3012 ms
3016 ms

2. Kod
3154 ms
3140 ms
3155 ms

Compiler kodları satır satır yorumlar ve satırlar birbirine tamamen bağlı olsa bile birçok faktörden dolayı ayrı işlemlermiş gibi değerlendirir ve bazı optimizasyonlar bu yüzden yapılamaz. Hız kaybının kaynağını bu oluşturur.



Şimdi gelelim son söze.
Bu bilgiler pratikte hiçbir işinize yaramayacak ve size herhangi bir hız kazandırmayacak.
Ancak başta da söylediğim üzere makinenin iç yapısını anlamanızı belki bir nebze de olsa kolaylaştırır diye paylaşıyorum.
Zaten bazı bilgiler tüm programlama dillerinde geçerli.

Büyük kısmını şuradan aldım.
http://www.dotnetperls.com/optimization

Saygılar, sevgiler...
 

orcnd

Dekan
Katılım
13 Ekim 2008
Mesajlar
6,394
Reaksiyon puanı
255
Puanları
63
php'de for döngüsünde kullanılan değişkenlerin döngü öncesi tanımlanması da performans kazandırıyor. c# da da denemek laızm
 

Ben Kenobi

Dekan
Katılım
6 Kasım 2011
Mesajlar
7,336
Reaksiyon puanı
3,155
Puanları
113
php'de for döngüsünde kullanılan değişkenlerin döngü öncesi tanımlanması da performans kazandırıyor. c# da da denemek laızm

Kod:
Random random = new Random();
for (int i = 0; i < 1000000; i++)
{             
    int x = random.Next();
}
Kod:
for (int i = 0; i < 1000000; i++)
{
    Random random = new Random();             
    int x = random.Next();
}

1. Kod
13 ms
14 ms
13 ms

2. Kod
1612 ms
1614 ms
1654 ms

Burada performans farkı çok bariz olduğu için yazmaya gerek duymadım hehe. :D
 

sdmh

Profesör
Katılım
22 Ağustos 2008
Mesajlar
1,108
Reaksiyon puanı
14
Puanları
38
Bu zamanlar nasıl ölçülüyor. Bunun yöntemi nedir?
 

EropaKING

Profesör
Katılım
29 Temmuz 2009
Mesajlar
1,652
Reaksiyon puanı
17
Puanları
0
Eline sağlık Hocam... Gerçekten güzel ve yararlı bilgiler.
 

abdullahcelik

Asistan
Katılım
10 Mart 2012
Mesajlar
417
Reaksiyon puanı
0
Puanları
0
güzel ve yaralı gercekten boyle bir deneyime sahip olman kac yilini aldi...
 

pcman

Öğrenci
Katılım
30 Nisan 2006
Mesajlar
98
Reaksiyon puanı
1
Puanları
0
Bu zamanlar nasıl ölçülüyor. Bunun yöntemi nedir?
Bu örnekteki değerler işlemin başlaması ve bitmesi arasında ölçülmüştür büyük ihtimalle.
Bu tür değerlere işlemci üreticinsinin sayfasından da ulaşılabilir.
Ölçü birimi komutun işlenme süresinde harcadığı pulse ile ölçülür.

Örneğin MOV komutu 3 pulse iken XOR komutu 2 pulse ile işlenebilir.
Tabii bu her zaman homojonik değildir çalışma frekansı, akıllı dallanma, işlem iplikçikleri vb. teknolojiler ile bunlar değişiklik gösterebilir.
Intel bu konuya özel olarak her yeni işlemci duyurduğunda optimizasyonlar için geliştirici kitleri yayınlar.
 

Ben Kenobi

Dekan
Katılım
6 Kasım 2011
Mesajlar
7,336
Reaksiyon puanı
3,155
Puanları
113
Bu zamanlar nasıl ölçülüyor. Bunun yöntemi nedir?
Testler Release modunda her bir kod için 5 kez yapılmıştır ancak son 3 sonuç gösterilmektedir ve hesaplamalar için 100 nanosaniye ayırt edebilen Stopwatch sınıfı kullanılmıştır.
Aslında ekran görüntüsü şeklinde koyacaktım sonuçları ama biraz uğraştırıcı olacağı için elle yazdım.
 

Amenofis

Öğrenci
Katılım
27 Aralık 2010
Mesajlar
36
Reaksiyon puanı
9
Puanları
8
Kod:
Random random = new Random();
for (int i = 0; i < 1000000; i++)
{             
    int x = random.Next();
}
Kod:
for (int i = 0; i < 1000000; i++)
{
    Random random = new Random();             
    int x = random.Next();
}

1. Kod
13 ms
14 ms
13 ms

2. Kod
1612 ms
1614 ms
1654 ms

Burada performans farkı çok bariz olduğu için yazmaya gerek duymadım hehe. :D

O iki döngü aynı işi yapmadığı için karşılaştırmak hatalı.
 
Üst