การประกาศตัวแปรภายในหรือภายนอก foreach loop: ไหนเร็ว / ดีกว่ากัน?


93

อันไหนเร็ว / ดีกว่ากัน

อันนี้:

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

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

หรืออันนี้:

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

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

ทักษะการพัฒนามือใหม่ของฉันบอกฉันว่าอันแรกดีกว่า แต่เพื่อนของฉันบอกฉันว่าฉันผิด แต่ไม่สามารถให้เหตุผลที่ดีว่าทำไมอันที่สองถึงดีกว่า

มีประสิทธิภาพแตกต่างกันหรือไม่?

คำตอบ:


114

ประสิทธิภาพที่ชาญฉลาดทั้งสองตัวอย่างถูกรวบรวมเป็น IL เดียวกันดังนั้นจึงไม่มีความแตกต่าง

อย่างที่สองจะดีกว่าเพราะเป็นการแสดงเจตนาของคุณอย่างชัดเจนยิ่งขึ้นหากuใช้เฉพาะในวง


10
ทราบว่ามีคือความแตกต่างถ้าตัวแปรถูกจับโดยการแสดงออกแลมบ์ดาหรือผู้ร่วมประชุมไม่ระบุชื่อ; ดูนอกดักตัวแปร
dtb

คุณสามารถอธิบายได้หรือไม่ว่าทำไมทั้งสองจึงคอมไพล์เป็น IL เดียวกัน ฉันค่อนข้างมั่นใจว่า C # ไม่ได้ยกการประกาศตัวแปรขึ้นไปด้านบนของฟังก์ชันเหมือนที่จาวาสคริปต์ทำ
สไตล์

4
@styfle นี่คือคำตอบสำหรับคำถามของคุณ
David Sherret

ตามลิงค์ Stack Overflow ให้คำตอบโดยละเอียดเพิ่มเติม: 1) Jon Hannaและ2) StriplingWarrior
user3613932

14

ไม่ว่าในกรณีใดวิธีที่ดีที่สุดคือการใช้ตัวสร้างที่ใช้ชื่อ ... หรือมิฉะนั้นใช้ประโยชน์จากเครื่องหมายปีกกา:

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

หรือ

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

หรือดีกว่านั้น LINQ:

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

ในขณะนี้ในบางกรณีตัวอย่างแรกของคุณอาจเร็วกว่าอย่างเห็นได้ชัด แต่อันที่สองจะดีกว่าเพราะอ่านได้ง่ายกว่าและคอมไพเลอร์อาจละทิ้งตัวแปร (และละเว้นทั้งหมด) เนื่องจากไม่ได้ใช้นอกforeachขอบเขตของ


6
ความคิดเห็นของ Necrophilic ประจำวัน: "หรือดีกว่านั้น LINQ" แน่นอนว่ามันเป็นโค้ดบรรทัดเดียวและนั่นทำให้เราในฐานะนักพัฒนารู้สึกดี แต่เวอร์ชันสี่บรรทัดนั้นเข้าใจได้มากกว่าและสามารถบำรุงรักษาได้
Oskar Austegard

5
แทบจะไม่ ด้วยเวอร์ชัน LINQ ฉันรู้ว่าสิ่งที่ฉันทำนั้นไม่เปลี่ยนรูปและมันใช้ได้กับทุกองค์ประกอบ
Tordek

6

การประกาศไม่ทำให้โค้ดถูกเรียกใช้งานดังนั้นจึงไม่ใช่ปัญหาด้านประสิทธิภาพ

ข้อที่สองคือสิ่งที่คุณหมายถึงและคุณมีโอกาสน้อยที่จะทำข้อผิดพลาดโง่ ๆ ถ้าคุณทำแบบที่สองดังนั้นให้ใช้สิ่งนั้น พยายามประกาศตัวแปรในขอบเขตที่เล็กที่สุดเท่าที่จำเป็นเสมอ

นอกจากนี้วิธีที่ดีกว่าคือการใช้ Linq:

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

2
ฉันชอบบรรทัด "พยายามประกาศตัวแปรในขอบเขตที่เล็กที่สุดเท่าที่จำเป็นเสมอ" ฉันคิดว่าบรรทัดเดียวสามารถตอบคำถามได้เป็นอย่างดี
Manjoor

5

เมื่อใดก็ตามที่คุณมีคำถามเกี่ยวกับประสิทธิภาพสิ่งเดียวที่ต้องทำคือวัดผล - วิ่งวนรอบการทดสอบและกำหนดเวลา

เพื่อตอบคำถามของคุณ - โดยไม่ต้องวัด :-) หรือดูความรู้สึกที่สร้างขึ้น - ความแตกต่างใด ๆ จะไม่สามารถสังเกตเห็นได้ในจำนวนการทำซ้ำที่มีความหมายและการดำเนินการที่แพงที่สุดในโค้ดของคุณมีแนวโน้มที่จะมีการจัดสรรผู้ใช้ด้วยคำสั่งไม่กี่คำสั่ง ของขนาดดังนั้นให้มุ่งเน้นไปที่ความชัดเจนของรหัส (ตามที่คุณควรจะเป็นโดยทั่วไป) และไปกับ 2

โอ้มันสายไปแล้วฉันเดาว่าฉันแค่พยายามบอกว่าอย่ากังวลกับเรื่องแบบนี้หรือจมอยู่กับรายละเอียดแบบนี้

เค


ขอบคุณสำหรับเคล็ดลับฉันคิดว่าฉันจะได้เวลาเรื่องอื่น ๆ ที่ฉันได้รับบาดเจ็บเช่นกันฮิฮิ: D
มาร์คัส

หากคุณต้องการศึกษาเพิ่มเติมเกี่ยวกับสิ่งที่ส่งผลต่อประสิทธิภาพการทำงานให้พิจารณาโดยใช้ตัวสร้างรหัส หากไม่มีอะไรอื่นระบบจะเริ่มให้คุณทราบว่าโค้ดและการดำเนินการประเภทใดใช้เวลามากที่สุด ProfileSharp และ EqatecProfilers นั้นฟรีและเพียงพอสำหรับการเริ่มต้นของคุณ
Kevin Shea


1

ในทางเทคนิคตัวอย่างแรกจะประหยัดเวลาไม่กี่นาโนวินาทีเนื่องจากไม่ต้องย้ายสแต็กเฟรมเพื่อจัดสรรตัวแปรใหม่ แต่นี่เป็นเวลาที่ CPU เพียงเล็กน้อยที่คุณจะไม่สังเกตเห็นนั่นคือถ้าคอมไพเลอร์ไม่ได้ เพิ่มประสิทธิภาพความแตกต่างใด ๆ


ฉันค่อนข้างมั่นใจว่า CLR ไม่ได้จัดสรร "ตัวแปรใหม่" ให้กับการวนซ้ำแต่ละครั้ง
dtb

คอมไพเลอร์อาจปรับให้เหมาะสมที่สุด แต่ต้องมีการจัดสรรพื้นที่สแต็กสำหรับตัวแปรใด ๆ ในลูป สิ่งนี้จะขึ้นอยู่กับการใช้งานและการนำไปใช้งานหนึ่งอาจทำให้สแต็กเฟรมเหมือนเดิมในขณะที่อีกอันหนึ่ง (พูดว่าโมโน) สามารถปล่อยสแต็กจากนั้นสร้างใหม่ในแต่ละลูป
Erik Funkenbusch

16
ตัวแปรโลคัลทั้งหมดในเมธอด (ระดับบนสุดหรือซ้อนกันในลูป) ถูกคอมไพล์ไปยังตัวแปรระดับวิธีการใน IL พื้นที่สำหรับตัวแปรจะถูกจัดสรรก่อนที่เมธอดจะดำเนินการไม่ใช่เมื่อถึงสาขาที่มีการประกาศใน C #
dtb

1
@dtb คุณมีแหล่งที่มาสำหรับการอ้างสิทธิ์นี้หรือไม่?
styfle

1

ในสถานการณ์นี้เวอร์ชันที่สองจะดีกว่า

โดยทั่วไปหากคุณต้องการเข้าถึงเฉพาะค่าภายในเนื้อความของการวนซ้ำให้เลือกเวอร์ชันที่สอง ในทางกลับกันหากมีสถานะสุดท้ายตัวแปรจะค้างอยู่เหนือเนื้อความของลูปให้ประกาศจากนั้นใช้เวอร์ชันแรก




0

ฉันไปตรวจสอบปัญหานี้ น่าแปลกใจที่พบว่าการทดสอบสกปรกของฉันที่ตัวเลือกที่ 2 นั้นเร็วกว่าเล็กน้อยตลอดเวลา

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 แล้ว แต่ไม่เหมือนกัน

ป้อนคำอธิบายภาพที่นี่

ดังนั้นฉันจึงเตรียมบางสิ่งที่ฉันต้องการจะทดสอบได้ดีกว่ามาก

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

นอกจากนี้ในกรณีนี้วิธีที่ 2 มักจะชนะ แต่แล้วฉันก็ตรวจสอบ CIL แล้วว่าไม่พบความแตกต่าง

ป้อนคำอธิบายภาพที่นี่

ฉันไม่ใช่กูรูด้านการอ่าน CIL แต่ฉันไม่เห็นปัญหาความเสื่อม ตามที่ได้ชี้ไปแล้วว่าการประกาศไม่ใช่การจัดสรรดังนั้นจึงไม่มีการลงโทษด้านประสิทธิภาพ

ทดสอบ

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; }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.