Bir foreach döngüsünün içinde veya dışında bir değişken bildirmek: hangisi daha hızlı / daha iyi?


93

Bunlardan hangisi daha hızlı / daha iyi?

Bu:

List<User> list = new List<User>();
User u;

foreach (string s in l)
{
    u = new User();
    u.Name = s;
    list.Add(u);
}

Ya da bu:

List<User> list = new List<User>();

foreach (string s in l)
{
    User u = new User();
    u.Name = s;
    list.Add(u);
}

Acemi geliştirme becerilerim bana birincisinin daha iyi olduğunu söylüyor, ancak bir arkadaşım bana yanıldığımı söylüyor, ancak ikincisinin daha iyi olması için bana iyi bir neden veremedi.

Performansta herhangi bir fark var mı?

Yanıtlar:


114

Performans açısından her iki örnek de aynı IL'ye derlenmiştir, dolayısıyla hiçbir fark yoktur.

İkincisi daha iyidir, çünkü uyalnızca döngü içinde kullanıldığında amacınızı daha net ifade eder .


10
Orada Not olan değişken bir lambda ifadesi veya anonim temsilci tarafından yakalanır eğer fark; bkz. Dış Değişken Tuzak .
dtb

Her ikisinin de neden aynı bilgi düzeyine derlendiğini açıklayabilir misiniz? C # 'ın değişken bildirimlerini javascript gibi işlevin en üstüne taşımadığından oldukça eminim.
styfle

4
@styfle işte sorunuzun cevabı .
David Sherret

Aşağıdaki Stack Overflow bağlantıları daha ayrıntılı yanıtlar sağlar: 1) Jon Hanna ve 2) StriplingWarrior
user3613932

14

Her durumda, en iyi yol, bir Ad alan bir kurucu kullanmaktır ... veya aksi halde küme ayracı gösterimini kullanmaktır:

foreach (string s in l)
{
    list.Add(new User(s));
}

veya

foreach (string s in l)
{
    list.Add(new User() { Name = s });
}

hatta daha iyisi LINQ:

var list = l.Select( s => new User { Name = s});

Şimdi, ilk örneğiniz bazı durumlarda fark edilemeyecek kadar hızlı olabilirken, ikincisi daha okunaklı olduğu için daha iyidir ve derleyici değişkeni atabilir (ve tamamen çıkarabilir) çünkü değişken foreachkapsamı dışında kullanılmaz.


6
Günün nekrofilik yorumu: "veya daha iyisi, LINQ". Elbette bu bir satır kod ve bu da geliştiriciler olarak bizi iyi hissettiriyor. Ancak dört satırlık versiyon, FAR daha anlaşılır ve dolayısıyla bakımı yapılabilir.
Oskar Austegard

5
Zorlukla. LINQ sürümü ile yaptığım şeyin değişmez olduğunu ve her öğe üzerinde çalıştığını biliyorum.
Tordek

6

Bir bildirim herhangi bir kodun çalıştırılmasına neden olmaz, dolayısıyla bu bir performans sorunu değildir.

İkincisi, demek istediğin şey ve ikinci yoldan yaparsan aptalca bir hata yapma olasılığın azalır, o yüzden bunu kullan. Daima gerekli olan en küçük kapsamda değişkenleri tanımlamaya çalışın.

Ayrıca, daha iyi yol Linq'i kullanmaktır:

List<User> users = l.Select(name => new User{ Name = name }).ToList();

2
"Daima değişkenleri gerekli olan en küçük kapsamda tanımlamaya çalışın." Tek bir satırın soruya çok iyi cevap verebileceğini düşünüyorum.
Manjoor

5

Performansla ilgili bir sorunuz olduğunda, yapmanız gereken tek şey ölçmektir - testinizin etrafında bir döngü oluşturun ve zamanlayın.

Sorunuzu cevaplamak için - ölçmeden :-) veya oluşturulan ilasm'a bakmadan - anlamlı sayıda yinelemede herhangi bir fark fark edilmez ve kodunuzdaki en pahalı işlem, muhtemelen birkaç siparişle kullanıcı tahsisi olacaktır. büyüklük açısından, bu nedenle kod netliğine odaklanın (genel olarak yapmanız gerektiği gibi) ve 2 ile devam edin.

Oh, geç oldu ve sanırım sadece bu tür şeyler için endişelenme ya da böyle ayrıntılara kapılma demeye çalışıyorum.

K


bahşiş için teşekkürler, sanırım aswell hakkında yaraladığım başka şeyleri de zamanlayacağım hehe: D
Marcus

Performansı neyin etkilediğini araştırmakla daha ileri gitmek istiyorsanız, bir kod profili oluşturucu kullanmaya bakın. Hiçbir şey değilse, size ne tür kod ve işlemlerin çoğu zaman aldığı konusunda bir fikir vermeye başlayacaktır. ProfileSharp ve EqatecProfilers ücretsiz ve başlamanız için yeterlidir.
Kevin Shea

1

İkincisi daha iyi. Her yinelemede yeni bir kullanıcıya sahip olmak istiyorsunuz.


1

Teknik olarak, ilk örnek birkaç nanosaniye tasarruf sağlayacaktır çünkü yığın çerçevesinin yeni bir değişken ayırmak için taşınması gerekmeyecektir, ancak bu çok küçük bir CPU zamanıdır, bunu fark etmeyeceksiniz, yani derleyici yapmazsa her durumda farkı optimize edin.


CLR'nin bir döngünün her yinelemesinde "yeni bir değişken" tahsis etmediğinden oldukça eminim.
dtb

Derleyici bunu iyi bir şekilde optimize edebilir, ancak bir döngüdeki herhangi bir değişken için yığın alanı tahsis edilmelidir. Bu, uygulamaya bağlı olacaktır ve bir uygulama, yığın çerçevesini basitçe aynı tutabilirken, diğeri (örneğin Mono) yığını serbest bırakabilir ve ardından her döngüde yeniden oluşturabilir.
Erik Funkenbusch

16
Bir yöntemdeki tüm yerel değişkenler (üst düzey veya bir döngüde yuvalanmış), IL'deki yöntem düzeyindeki değişkenlere derlenir. Değişkenler için alan, C # 'da bildirime sahip bir şubeye ulaşıldığında değil, yöntem çalıştırılmadan önce ayrılır.
dtb

1
@dtb Bu iddia için bir kaynağınız var mı?
styfle

1

Bu senaryoda, ikinci versiyon daha iyidir.

Genel olarak, yalnızca yineleme gövdesindeki değere erişmeniz gerekiyorsa, ikinci sürümü seçin. Öte yandan, değişkenin döngünün gövdesinin ötesinde tutacağı bir nihai durum varsa, o zaman ilk sürümü kullanın.




0

Bu sorunu doğrulamaya gittim. Kirli testlerimde şaşırtıcı bir şekilde 2. seçeneğin her zaman biraz daha hızlı olduğunu öğrendim.

namespace Test
{
  class Foreach
  {
    string[] names = new[] { "ABC", "MNL", "XYZ" };

    void Method1()
    {
      List<User> list = new List<User>();
      User u;

      foreach (string s in names)
      {
        u = new User();
        u.Name = s;
        list.Add(u);
      }
    }

    void Method2()
    {

      List<User> list = new List<User>();

      foreach (string s in names)
      {
        User u = new User();
        u.Name = s;
        list.Add(u);
      }
    }
  }

  public class User { public string Name; }
}

CIL'i doğruladım ama aynı değil.

görüntü açıklamasını buraya girin

Bu yüzden çok daha iyi bir test olmasını istediğim bir şey hazırladım.

namespace Test
{
  class Loop
  { 

    public TimeSpan method1 = new TimeSpan();
    public TimeSpan method2 = new TimeSpan();

    Stopwatch sw = new Stopwatch();

    public void Method1()
    {
      sw.Restart();

      C c;
      C c1;
      C c2;
      C c3;
      C c4;

      int i = 1000;
      while (i-- > 0)
      {
        c = new C();
        c1 = new C();
        c2 = new C();
        c3 = new C();
        c4 = new C();        
      }

      sw.Stop();
      method1 = method1.Add(sw.Elapsed);
    }

    public void Method2()
    {
      sw.Restart();

      int i = 1000;
      while (i-- > 0)
      {
        var c = new C();
        var c1 = new C();
        var c2 = new C();
        var c3 = new C();
        var c4 = new C();
      }

      sw.Stop();
      method2 = method2.Add(sw.Elapsed);
    }
  }

  class C { }
}

Ayrıca bu durumda 2. yöntem her zaman kazanıyordu ama sonra CIL'in hiçbir fark bulmadığını doğruladım.

görüntü açıklamasını buraya girin

CIL okuma gurusu değilim ama beyan sorunu görmüyorum. Daha önce de belirtildiği gibi beyanname tahsis değildir, dolayısıyla herhangi bir performans cezası yoktur.

Ölçek

namespace Test
{
  class Foreach
  {
    string[] names = new[] { "ABC", "MNL", "XYZ" };

    public TimeSpan method1 = new TimeSpan();
    public TimeSpan method2 = new TimeSpan();

    Stopwatch sw = new Stopwatch();

    void Method1()
    {
      sw.Restart();

      List<User> list = new List<User>();
      User u;

      foreach (string s in names)
      {
        u = new User();
        u.Name = s;
        list.Add(u);
      }

      sw.Stop();
      method1 = method1.Add(sw.Elapsed);
    }

    void Method2()
    {
      sw.Restart();

      List<User> list = new List<User>();

      foreach (string s in names)
      {
        User u = new User();
        u.Name = s;
        list.Add(u);
      }

      sw.Stop();
      method2 = method2.Add(sw.Elapsed);
    }
  }

  public class User { public string Name; }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.