Mendeklarasikan variabel di dalam atau di luar loop foreach: mana yang lebih cepat / lebih baik?

93

Manakah dari berikut ini yang lebih cepat / lebih baik?

Yang ini:

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

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

Atau yang ini:

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

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

Keterampilan pengembangan pemula saya memberi tahu saya bahwa yang pertama lebih baik, tetapi seorang teman saya memberi tahu saya bahwa saya salah, tetapi tidak dapat memberi saya alasan yang baik mengapa yang kedua lebih baik.

Apakah ada perbedaan kinerja sama sekali?

Marcus
sumber

Jawaban:

114

Dari segi performa, kedua contoh dikompilasi ke IL yang sama, jadi tidak ada perbedaan.

Yang kedua lebih baik, karena lebih jelas mengekspresikan maksud Anda jika uhanya digunakan di dalam loop.

dtb
sumber
10
Perhatikan bahwa ada adalah perbedaan jika variabel tersebut ditangkap oleh ekspresi lambda atau mendelegasikan anonim; lihat Perangkap Variabel Luar .
dtb
Bisakah Anda menjelaskan mengapa keduanya dikompilasi ke IL yang sama? Saya cukup yakin bahwa C # tidak mengangkat deklarasi variabel ke atas fungsi seperti yang dilakukan javascript.
styfle
4
@styfle inilah jawaban atas pertanyaan Anda.
David Sherret
Tautan Stack Overflow berikut memberikan jawaban yang lebih rinci: 1) Jon Hanna dan 2) StriplingWarrior
pengguna3613932
14

Bagaimanapun, cara terbaik adalah menggunakan konstruktor yang menggunakan Nama ... atau, jika tidak, gunakan notasi kurung kurawal:

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

atau

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

atau bahkan lebih baik, LINQ:

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

Sekarang, sementara contoh pertama Anda dapat, dalam beberapa kasus, tidak terlihat lebih cepat, yang kedua lebih baik karena lebih mudah dibaca, dan kompilator mungkin membuang variabel (dan menghilangkannya sama sekali) karena tidak digunakan foreachdi luar ruang lingkup.

Tordek
sumber
6
Komentar nekrofilik hari ini: "atau bahkan lebih baik, LINQ". Tentu itu satu baris kode, dan itu membuat kami sebagai pengembang merasa senang. Tetapi versi empat baris JAUH lebih dapat dimengerti, dan karenanya dapat dipelihara.
Oskar Austegard
5
Hampir tidak. Dengan versi LINQ saya tahu bahwa apa yang saya lakukan tidak dapat diubah, dan bekerja pada setiap elemen.
Tordek
6

Deklarasi tidak menyebabkan kode apa pun dijalankan, jadi ini bukan masalah kinerja.

Yang kedua adalah yang Anda maksud, dan Anda cenderung tidak membuat kesalahan bodoh jika Anda melakukannya dengan cara kedua, jadi gunakan itu. Selalu mencoba untuk mendeklarasikan variabel dalam ruang lingkup terkecil yang diperlukan.

Dan selain itu, cara yang lebih baik adalah dengan menggunakan LINQ:

List<User> users = l.Select(name => new User{ Name = name }).ToList();
Mark Byers
sumber
2
Saya suka baris "Selalu mencoba mendeklarasikan variabel dalam ruang lingkup terkecil yang diperlukan." Saya pikir satu baris dapat menjawab pertanyaan dengan sangat baik.
Manjoor
5

Kapan pun Anda memiliki pertanyaan tentang kinerja, satu-satunya hal yang harus dilakukan adalah mengukur - menjalankan pengujian dan menghitung waktunya.

Untuk menjawab pertanyaan Anda - tanpa mengukur :-) atau melihat ilasm yang dihasilkan - perbedaan apa pun tidak akan terlihat dalam sejumlah iterasi yang berarti dan operasi yang paling mahal dalam kode Anda kemungkinan besar adalah alokasi pengguna dengan beberapa pesanan besarnya, jadi berkonsentrasilah pada kejelasan kode (sebagaimana seharusnya secara umum) dan lanjutkan dengan 2.

Oh, ini terlambat dan saya rasa saya hanya mencoba mengatakan jangan khawatir tentang hal semacam ini atau terjebak dalam detail seperti ini.

K

Kevin Shea
sumber
Terima kasih untuk tipnya, saya pikir saya akan mengatur waktu beberapa hal lain yang telah saya bahas juga hehe: D
Marcus
Jika Anda ingin melangkah lebih jauh dengan melihat apa yang memengaruhi kinerja, pertimbangkan untuk menggunakan kode profiler. Jika tidak ada yang lain, ini akan mulai memberi Anda gambaran tentang jenis kode dan operasi apa yang paling lama. ProfileSharp dan EqatecProfilers gratis dan cukup untuk Anda mulai.
Kevin Shea
1

Yang kedua lebih baik. Anda bermaksud memiliki pengguna baru di setiap iterasi.

Jarrett Widman
sumber
1

Secara teknis, contoh pertama akan menghemat beberapa nanodetik karena stack frame tidak perlu dipindahkan untuk mengalokasikan variabel baru, tetapi ini adalah jumlah waktu CPU yang sangat kecil sehingga Anda tidak akan menyadarinya, itu jika kompiler tidak mengoptimalkan perbedaan jauh anyays.

Erik Funkenbusch
sumber
Saya cukup yakin CLR tidak mengalokasikan "variabel baru" pada setiap iterasi loop.
dtb
Nah, kompilator mungkin akan mengoptimalkannya, tetapi ruang tumpukan harus dialokasikan untuk variabel apa pun dalam satu putaran. Ini akan bergantung pada implementasi dan satu implementasi mungkin hanya menjaga frame stack tetap sama, sementara yang lain (katakanlah Mono) dapat merilis stack kemudian membuatnya kembali di setiap loop.
Erik Funkenbusch
16
Semua variabel lokal dalam metode (tingkat atas atau bersarang dalam satu loop) dikompilasi ke variabel tingkat metode di IL. Ruang untuk variabel dialokasikan sebelum metode dijalankan, bukan ketika cabang dengan deklarasi di C # tercapai.
dtb
1
@dtb Apakah Anda memiliki sumber untuk klaim ini?
styfle
1

Dalam skenario ini, versi kedua lebih baik.

Secara umum, jika Anda hanya perlu mengakses nilai di dalam badan iterasi, pilih versi kedua. Di sisi lain, jika ada beberapa status akhir yang akan ditahan variabel di luar badan perulangan, kemudian deklarasikan kemudian gunakan versi pertama.

csj
sumber
0

Seharusnya tidak ada perbedaan kinerja yang bisa dilihat.

Jacob Adams
sumber
0

Saya pergi untuk memverifikasi masalah ini. Secara mengejutkan mengetahui dari tes kotor saya bahwa opsi ke-2 bahkan sedikit lebih cepat sepanjang waktu.

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; }
}

Saya memverifikasi CIL tetapi tidak identik.

masukkan deskripsi gambar di sini

Jadi saya mempersiapkan sesuatu yang saya ingin tes menjadi lebih baik.

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 { }
}

Juga dalam kasus ini metode ke-2 selalu menang tetapi kemudian saya memverifikasi CIL tidak menemukan perbedaan.

masukkan deskripsi gambar di sini

Saya bukan ahli membaca CIL tetapi saya tidak melihat masalah deklerasi. Seperti yang telah ditunjukkan, deklarasi bukanlah alokasi sehingga tidak ada hukuman kinerja di atasnya.

Uji

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; }
Ucho
sumber