LINQ Sorgusu mu? ForEach mi? Bir Türlü Karar Veremedim

Merhaba Arkadaşlar,

Bilim Kurgu fanatiklerinin kafasında her zaman hayranı oldukları filmlerden kesitler, sahneler kalır. Matrix filmini izleyenler eminimki Neo' ya uzatılan kırmızı ve mavi hap serenatını gayet iyi hatırlayacaktır. Morpheus haplardan birisinde Alice Harikalar Diyarının kapılarını ardına kadar açabileceğini ifade ederken, diğer hapı yuttuğunda, Neo' nun yatağında hiç bir şey olmamış gibi uyanacağını ve tüm bunların bir hayalden ibaret olduğunu düşüneceğini belirtir. Tabi Neo amacına ulaşmak için zaten hangi hapı içmesi gerektiğini biliyordur ki son bölümde aslında gerçekten hapı yutmaktadır Yell

Bizde yazılımcılar olarak bazen karar verirken tabir yerinde ise sürüncemede kalabiliriz. Böyle durumlarda ufak tefek gözüken noktaların aslında çok büyük riskler taşıdığını da düşünmemiz gerekmektedir. Çünkü karar vermek için basit bir kaç test kodu çok işimize yarayacaktır. İşte bu yazımızda böyle bir konuya değiniyor olacağız.

Aslında konunun çıkış noktası Microsoft Teknoloji Günleri Akşam Sınıfındaki bir meslektaşımın sorusu oldu. Değerli meslektaşım uygulama kodunda koleksiyon bazlı sorgulamaları gerçekleştirirken pek çok vakada foreach döngülerini tercih ettiğini söyledi. Tabi her durumda değil. Bende bu noktada aynı amaca hizmet eden bir LINQ sorgusu ile ForEach çalışması arasındaki performans farklılıklarını irdelemeye karar verdim. Nitekim performans her zaman için karar vermeden önem arz eden kriterlerden birisidir. Anlayacağınız basit bir test ve sonuçlarını irdeliyor olacağız bu kısa yazımızda.

Örnek uygulamamızda Enumerable.Range metodu yardımıyla elde edilen bir int sayı dizisi içerisinde 2 ile tam bölünebilen sayıların adedini hesap ettirmekteyiz. Tahmin edeceğiniz üzere bu tip bir işlemi LINQ sorgusu yardımıyla anlamlı bir kod ifadesi ile yerine getirebiliriz. Ayrıca bunu bir foreach döngüsü ile de gerçekleştirebiliriz. İşte test kodlarımız.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace LINQForEachPerformance
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 1; i < 10; i++)
            {
                IEnumerable<int> range = Enumerable.Range(i,(i+1)*10000000);
                WithLinq(range);
                WithForeach(range);
            }
            
        }

        static void WithLinq(IEnumerable<int> range)
        {
            Stopwatch sWatch = new Stopwatch();
            sWatch.Start();

            int count = (from i in range
                         where i % 2 == 0
                         select i).Count<int>();
            Console.WriteLine(count.ToString());

            sWatch.Stop();
            Console.WriteLine("LINQ Total Time : {0}",sWatch.ElapsedMilliseconds.ToString());
        }

        static void WithForeach(IEnumerable<int> range)
        {
            Stopwatch sWatch = new Stopwatch();
            sWatch.Start();

            int count = 0;
            foreach (int i in range)
            {
                if (i % 2 == 0)
                    count++;
            }
            Console.WriteLine("{0}",count.ToString());

            sWatch.Stop();
            Console.WriteLine("ForEach Total Time : {0}", sWatch.ElapsedMilliseconds.ToString());
        }
    }
}

Örnekte arka arkaya 10 deneme yapılmaktadır. WithLinq metodu LINQ sorgusunu kullanarak ikiye tam bölünen sayıların adedini vermektedir. WithForeach metodu ise aynı işlemi foreach döngüsü yardımıyla gerçekleştirmektedir. Stopwatch tipi yardımıyla her hesaplamanın toplam süresi bulunmaktadır. Uygulamanın Intel çift çekirdek işlemcili, 4Gb Ram' i olan makinemdeki çalışma zamanı sonuçlarından bir tanesi aşağıdaki ekran görütüsündeki gibidir.

Aslında her zaman için süreler farklı olacaktır ancak grafiksel eğriler benzer olacaktır. Durumun daha net bir şekilde görülmesi için değerlerin Excel üzerinde Chart olarak gösterilmesi yeterlidir. İşte sonuçlar.

Görüldüğü üzere foreach döngüsü ile yapılan hesaplamalar değer aralığı büyüse dahi LINQ sorgusuna göre daha kısa sürede icra edilmektedir. Buna göre foreach' in daha hızlı olduğunu söyleyebilir miyiz? Bu senaryo için evet. Wink Ama bildiğiniz üzere LINQ daha karmaşık sorgular yazılması noktasında elbetteki iç içe geçecek sayısız foreach kullanımından çok daha etkili bir yöntemdir. Ancak başta da belirttiğimiz gibi insan bir an için hangi hapı yutacağına karar veremiyor. Tabi farklı sorgulama senaryoları ile farklı denemeler yaparak karşılaştırmalara devam etmekte yarar olabilir. Bu kutsal görevi de siz değerli okurlarıma bırakıyorum. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

LINQForEachPerformance.rar (23,30 kb) [Örnek Visual Studio 2010 Ultimate ile geliştirilmiş ve test edilmiştir]

Yorumlar (10) -

  • Böyle bir sonuç çıkacağını tahmin ediyordum Smile

    Hocam aslında Entity Framework 4.0 ve Linq To Sql karşılaştırması da yapsanız çok makbule geçerdi.

    Urfadan, selamlar..
  • Hocam bende sizinkine ek olarak Dynamic LINQ ile denedim.
    Malesef DLINQ classic linq'den daha geride kaldı testte..

    int cont = range.Where(r => r % 2 == 0).Select(i => i).Count<int>();
  • Merhaba Burak Hocam;

    Gerçekten başarılı bir çalışma olmuş. Ayrıca bizimde uygulamaların performansını izlemede yararlı bir yazı olmuş.

    LINQ rahatlığı, kolaylığı gerçekten biz yazılımcılar için çoğu zaman tercih sebebi oluyor. Yani yazılım hayatının keskin uçurumlarında bazen rahatımız için kırmızı hapı hemen yutuyoruz. Smile) Bu zaten olması gereken biğr performans farkıydı. Çünkü acı çekme oranımız azaldıkça(az kod ile çok iş) bu gibi farkları kabullenmemiz gerekiyor.

    İyi Çalışmalar..
  • Merhabalar

    Bu örneği inceleyince aslında linq ile yazılan sorgulama foreach'e nazaran daha anlaşılması zor vede işlem sayısı daha fazla.Biraz uğraşınca şu 2 sorguyu  buldum.

    range.Count(x=>x%2==0);
    bu en sade ve en uygunu sanırım.Performans olarak daha iyi çalışıyor ama yinede foreach kadar değil.
    range.Sum(x=>x%2==0 ? 1:0);
    bu sorguda fena sayılmaz.

    İyi çalışmalar
  • @Yakup Çok doğru. Tabi bildiğiniz üzere yazılan tüm LINQ sorguları aslında arka planda Extension Method' lar ile ele alınmakta. Dolayısıyla Count, Sum gibi Extension metodları kullandığımızda LINQ sorgusuna göre zaman zaman daha anlaşılır ifadeler ortaya çıkabilse de, işleyiş biçimi ve şekli değişmiyor.
  • @bsenyurt Aradaki performans farkına ne sebeb oluyor acaba ?
  • @Kazim Büyük ihtimalle çalışma zamanında ki Extension metod çağrıları.
  • Açıkçası ben arka planda bunların herhangi bir çevrim işlemine tabi tutulduğunu düşünmüyorum.

    (from i in range where i % 2 == 0 select i).Count<int>();
    burada büyük ihtimalle parantez içi işlemlerden sonra Count metodu devreye giriyor.
  • Count metodunun koduna bakıldığında foreach mantığı kullanıldığı görülecektir. Üzerine fazladan kontroller ve metot çağrıları eklendiğinde çıkan sonuçlar şaşırtmamaktadır. Fakat, burada sadece deffered execution uygulanmıştır. Yani aslında bir sayı dizisi değil, bir iteratör üretilmiştir. Aynı kodu sadece Range metodunun sonuna .ToArray() ekleyerek gerçekten bir sayı dizisi haline çevirdiğimiz zaman sonuçlar bu safer LINQ lehine değişmektedir. Örnek sorguda bende en kötü sonuç:
    LINQ Total Time : 1360
    ForEach Total Time : 688
    ile benzerken, dizi kullanıldığında sonuçlar :
    LINQ Total Time : 586
    ForEach Total Time : 752
    çıkmaktadır. Sorun başka bir soruna yol açtı Smile

Yorum ekle

Loading