Khai báo một biến bên trong hoặc bên ngoài vòng lặp foreach: cái nào nhanh hơn / tốt hơn?


92

Cái nào trong số này là nhanh hơn / tốt hơn?

Cái này:

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

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

Hoặc cái này:

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

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

Kỹ năng phát triển thành viên mới của tôi cho tôi biết cái đầu tiên tốt hơn, nhưng một người bạn của tôi nói với tôi rằng tôi sai, nhưng không thể cho tôi lý do chính đáng tại sao cái thứ hai tốt hơn.

Có sự khác biệt nào về hiệu suất không?

Câu trả lời:


112

Cả hai ví dụ về hiệu suất đều được biên dịch cho cùng một IL, vì vậy không có sự khác biệt.

Thứ hai tốt hơn, vì nó thể hiện rõ ràng hơn ý định của bạn nếu uchỉ được sử dụng bên trong vòng lặp.


10
Lưu ý rằng có một sự khác biệt nếu biến được chụp bởi một biểu thức lambda hoặc đại biểu vô danh; xem Bẫy biến bên ngoài .
dtb

Bạn có thể giải thích tại sao cả hai được biên dịch cho cùng một IL? Tôi khá chắc chắn rằng C # không nâng các khai báo biến lên đầu hàm như javascript.
súng trường vào

4
@styfle đây là câu trả lời cho câu hỏi của bạn.
David Sherret

Các liên kết Stack Overflow sau đây cung cấp câu trả lời chi tiết hơn: 1) Jon Hanna2) StriplingWarrior
dùng3613932

14

Trong mọi trường hợp, cách tốt nhất là sử dụng một hàm tạo có Tên ... hoặc, nếu không, hãy khai thác ký hiệu ngoặc nhọn:

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

hoặc là

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

hoặc thậm chí tốt hơn, LINQ:

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

Bây giờ, trong khi ví dụ đầu tiên của bạn, trong một số trường hợp, có thể nhanh hơn một cách đáng kinh ngạc, thì ví dụ thứ hai tốt hơn vì nó dễ đọc hơn và trình biên dịch có thể loại bỏ biến (và bỏ qua hoàn toàn) vì nó không được sử dụng vượt quá foreachphạm vi của nó.


6
Bình luận của ngày hôm nay: "hoặc thậm chí tốt hơn, LINQ". Chắc chắn đó là một dòng mã và điều đó khiến chúng tôi với tư cách là nhà phát triển cảm thấy hài lòng. Nhưng phiên bản bốn dòng dễ hiểu hơn, và do đó có thể bảo trì được.
Oskar Austegard

5
Khó khăn. Với phiên bản LINQ, tôi biết rằng những gì tôi đang làm là bất biến và nó đang hoạt động trên mọi yếu tố.
Tordek

6

Một khai báo không làm cho bất kỳ mã nào được thực thi, vì vậy nó không phải là vấn đề về hiệu suất.

Cách thứ hai là ý bạn, và bạn sẽ ít mắc lỗi ngu ngốc hơn nếu làm theo cách thứ hai, vì vậy hãy sử dụng cách đó. Luôn cố gắng khai báo các biến trong phạm vi nhỏ nhất cần thiết.

Và bên cạnh đó, cách tốt hơn là sử dụng Linq:

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

2
Tôi thích dòng "Luôn cố gắng khai báo các biến trong phạm vi nhỏ nhất cần thiết." Tôi nghĩ rằng một dòng duy nhất có thể trả lời câu hỏi rất tốt.
Manjoor

5

Bất cứ khi nào bạn có câu hỏi về hiệu suất, điều duy nhất cần làm là đo lường - chạy một vòng quanh bài kiểm tra của bạn và tính thời gian cho nó.

Để trả lời câu hỏi của bạn - mà không cần đo lường :-) hoặc nhìn vào ilasm được tạo - bất kỳ sự khác biệt nào sẽ không đáng chú ý trong một số lần lặp lại có ý nghĩa và hoạt động tốn kém nhất trong mã của bạn có khả năng được người dùng phân bổ theo một vài đơn đặt hàng về độ lớn, vì vậy hãy tập trung vào độ rõ ràng của mã (như bạn thường làm) và đi với 2.

Ồ, đã muộn và tôi đoán tôi chỉ muốn nói rằng đừng lo lắng về những điều này hoặc bị cuốn vào những chi tiết như thế này.

K


thx cho mẹo, tôi nghĩ rằng tôi sẽ dành thời gian cho một số thứ khác mà tôi đã bị thương là tốt rồi hehe: D
Marcus

Nếu bạn muốn đi xa hơn với việc xem xét điều gì ảnh hưởng đến hiệu suất, hãy xem xét sử dụng một trình biên dịch mã. Nếu không có gì khác, nó sẽ bắt đầu cung cấp cho bạn ý tưởng về loại mã và các hoạt động tốn nhiều thời gian nhất. ProfileSharp và EqatecProfilers là miễn phí và đủ để giúp bạn bắt đầu.
Kevin Shea

1

Cái thứ 2 là tốt hơn. Bạn muốn có một người dùng mới trong mỗi lần lặp lại.


1

Về mặt kỹ thuật, ví dụ đầu tiên sẽ tiết kiệm một vài nano giây vì khung ngăn xếp sẽ không phải di chuyển để phân bổ một biến mới, nhưng đây là một lượng thời gian CPU rất nhỏ mà bạn sẽ không nhận thấy, đó là nếu trình biên dịch không tối ưu hóa bất kỳ sự khác biệt nào.


Tôi khá chắc chắn rằng CLR không phân bổ "một biến mới" trên mỗi lần lặp lại của một vòng lặp.
dtb

Chà, trình biên dịch cũng có thể tối ưu hóa điều đó, nhưng không gian ngăn xếp phải được phân bổ cho bất kỳ biến nào trong một vòng lặp. Điều này sẽ phụ thuộc vào việc triển khai và một triển khai có thể đơn giản giữ nguyên khung ngăn xếp, trong khi một khung khác (giả sử Mono) có thể giải phóng ngăn xếp sau đó tạo lại nó trên mỗi vòng lặp.
Erik Funkenbusch

16
Tất cả các biến cục bộ trong một phương thức (cấp cao nhất hoặc được lồng trong một vòng lặp) được biên dịch thành các biến cấp phương pháp trong IL. Khoảng trống cho các biến được cấp phát trước khi phương thức được thực thi, không phải khi đến một nhánh có khai báo trong C #.
dtb

1
@dtb Bạn có nguồn cho yêu cầu này không?
styfle

1

Trong trường hợp này, phiên bản thứ hai tốt hơn.

Nói chung, nếu bạn chỉ cần truy cập giá trị bên trong nội dung của phép lặp, thì hãy chọn phiên bản thứ hai. Mặt khác, nếu có một số trạng thái cuối cùng mà biến sẽ giữ bên ngoài phần thân của vòng lặp, thì hãy khai báo rồi sử dụng phiên bản đầu tiên.




0

Tôi đã đi xác minh vấn đề này. Thật ngạc nhiên khi phát hiện ra trong các bài kiểm tra bẩn của tôi rằng tùy chọn thứ 2 thậm chí còn nhanh hơn một chút.

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

Tôi đã xác minh CIL nhưng nó không giống hệt nhau.

nhập mô tả hình ảnh ở đây

Vì vậy, tôi đã chuẩn bị một thứ gì đó mà tôi muốn để làm bài thi tốt hơn nhiều.

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

Cũng trong trường hợp này, phương pháp thứ 2 luôn thắng nhưng sau đó tôi xác minh CIL thì không thấy khác biệt.

nhập mô tả hình ảnh ở đây

Tôi không phải là chuyên gia đọc CIL nhưng tôi không thấy vấn đề gì về sự suy giảm. Như đã được chỉ ra, tuyên bố không phải là phân bổ vì vậy không có hình phạt về hiệu suất trên đó.

Kiểm tra

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; }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.