2009-08-19 28 views
5

Đây là một câu hỏi học thuật về hiệu suất hơn là thực tế 'tôi nên sử dụng' nhưng tôi tò mò vì tôi không dabble nhiều trong IL ở tất cả để xem những gì được xây dựng và tôi không có một số liệu lớn trên tay để hồ sơ chống lại.Vòng lặp nào nhanh hơn: gọi thuộc tính hai lần hoặc lưu trữ thuộc tính một lần?

Vì vậy, đó là nhanh hơn:

List<myObject> objs = SomeHowGetList(); 
List<string> strings = new List<string>(); 
foreach (MyObject o in objs) 
{ 
    if (o.Field == "something") 
     strings.Add(o.Field); 
} 

hay:

List<myObject> objs = SomeHowGetList(); 
List<string> strings = new List<string>(); 
string s; 
foreach (MyObject o in objs) 
{ 
    s = o.Field; 
    if (s == "something") 
     strings.Add(s); 
} 

Hãy ghi nhớ rằng tôi không thực sự muốn biết tác động hiệu suất của string.Add (s) (như bất cứ thao tác nào cần được thực hiện không thể thay đổi được), chỉ khác biệt hiệu suất giữa việc thiết lập mỗi lần lặp (chúng ta hãy nói rằng s có thể là bất kỳ kiểu hoặc chuỗi nguyên thủy nào) gọi hàm getter trên đối tượng mỗi lần lặp.

+19

Tại sao bạn hỏi _us_? Bạn đã viết mã. Nhận ra một đồng hồ bấm giờ, chạy nó một tỷ lần cả hai cách, và sau đó bạn sẽ biết đó là nhanh hơn. –

Trả lời

9

Tùy chọn đầu tiên của bạn nhanh hơn đáng kể trong các thử nghiệm của tôi. Tôi là người nổi tiếng như vậy!Nghiêm túc, mặc dù một số ý kiến ​​đã được thực hiện về mã trong thử nghiệm ban đầu của tôi. Đây là mã được cập nhật cho thấy tùy chọn 2 nhanh hơn.

class Foo 
    { 
     public string Bar { get; set; } 

     public static List<Foo> FooMeUp() 
     { 
      var foos = new List<Foo>(); 

      for (int i = 0; i < 10000000; i++) 
      { 
       foos.Add(new Foo() { Bar = (i % 2 == 0) ? "something" : i.ToString() }); 
      } 

      return foos; 
     } 
    } 

    static void Main(string[] args) 
    { 

     var foos = Foo.FooMeUp(); 
     var strings = new List<string>(); 

     Stopwatch sw = Stopwatch.StartNew(); 

     foreach (Foo o in foos) 
     { 
      if (o.Bar == "something") 
      { 
       strings.Add(o.Bar); 
      } 
     } 

     sw.Stop(); 
     Console.WriteLine("It took {0}", sw.ElapsedMilliseconds); 

     strings.Clear(); 
     sw = Stopwatch.StartNew(); 

     foreach (Foo o in foos) 
     { 
      var s = o.Bar; 
      if (s == "something") 
      { 
       strings.Add(s); 
      } 
     } 

     sw.Stop(); 
     Console.WriteLine("It took {0}", sw.ElapsedMilliseconds); 
     Console.ReadLine(); 
    } 
+0

Xin chào, +1 để viết bài kiểm tra! Tôi sẽ cung cấp cho bạn +2 nếu tôi có thể .. –

+0

Andy: Bạn đã thử nghiệm trên nền tảng thời gian chạy/nền tảng nào? Ngoài ra, nó có phải là bản phát hành không? –

+0

Đối với tôi, kết quả là: 2294, 702 mâu thuẫn với kết luận của bạn. –

7

Hầu hết thời gian, đoạn mã thứ hai của bạn phải là ít nhất nhanh bằng đoạn mã đầu tiên.

Hai đoạn mã này không có chức năng tương đương. Các thuộc tính không được bảo đảm trả về cùng một kết quả trên các truy cập riêng lẻ. Kết quả là, trình tối ưu hóa JIT không thể lưu trữ kết quả (ngoại trừ các trường hợp tầm thường) và nó sẽ nhanh hơn nếu bạn lưu trữ kết quả của một thuộc tính chạy dài. Hãy xem ví dụ này: why foreach is faster than for loop while reading richtextbox lines.

Tuy nhiên, đối với một số trường hợp cụ thể như:

for (int i = 0; i < myArray.Length; ++i) 

nơi myArray là một đối tượng mảng, trình biên dịch có thể phát hiện các mô hình và tối ưu hóa mã và bỏ qua các kiểm tra ràng buộc. Nó có thể chậm hơn nếu bạn lưu trữ kết quả của tài sản Length như:

int len = myArray.Length; 
for (int i = 0; i < myArray.Length; ++i) 
+0

Giả sử rằng biên dịch này, o.Field trong cả hai trường hợp sẽ là loại chuỗi. Tôi cũng giả định rằng giá trị cho o.Field, cho mỗi đối tượng trong bộ sưu tập, được đặt thành một số giá trị có ý nghĩa. Có lẽ tôi không hoàn toàn hiểu ý bạn là gì; Bạn có thể đặc sắc hơn không? –

+0

Kiểm tra liên kết trong câu trả lời cập nhật của tôi. Đó là một ví dụ cụ thể trong đó nó tạo ra sự khác biệt đáng kể nếu bạn lưu trữ giá trị trả về. –

2

Lưu trữ giá trị trong trường là tùy chọn nhanh hơn.

Mặc dù cuộc gọi phương thức không áp đặt chi phí khổng lồ, nhưng giá trị này lớn hơn nhiều so với việc lưu giá trị một lần vào biến cục bộ trên ngăn xếp và sau đó truy xuất nó.

Tôi làm một cách nhất quán.

2

Thông thường cái thứ hai nhanh hơn, vì cái đầu tiên tính toán lại thuộc tính trên mỗi lần lặp. Dưới đây là ví dụ về điều gì đó có thể mất nhiều thời gian đáng kể:

var d = new DriveInfo("C:"); 
d.VolumeLabel; // will fetch drive label on each call 
4

Nó thực sự phụ thuộc vào việc triển khai. Trong hầu hết các trường hợp, nó được giả định (như là một vấn đề phổ biến thực hành/lịch sự) rằng một tài sản là không tốn kém. Tuy nhiên, nó có thể là mỗi "get" thực hiện tìm kiếm không được lưu trữ trên một số tài nguyên từ xa. Đối với các thuộc tính tiêu chuẩn, đơn giản, bạn sẽ không bao giờ nhận thấy sự khác biệt thực sự giữa hai loại. Đối với trường hợp xấu nhất, tìm nạp một lần, lưu trữ và sử dụng lại sẽ nhanh hơn nhiều.

Tôi muốn bị cám dỗ sử dụng get hai lần cho đến khi tôi biết có sự cố ... "tối ưu hóa sớm", v.v ... Nhưng; nếu tôi đang sử dụng nó trong một vòng lặp chặt chẽ, rồi Tôi có thể lưu trữ nó trong một biến. Ngoại trừ Length trên một mảng có điều trị JIT đặc biệt ;-p

+0

@Marc: Không phải là vấn đề với đoạn mã đầu tiên 'o.Field' thực sự có thể thay đổi giá trị giữa việc thử nghiệm nó với" cái gì đó "và thêm nó vào' Danh sách'? 'o.Field ==" một cái gì đó "' có thể đánh giá đúng, nhưng do thời gian bạn gọi là 'strings.Add' bạn đang thêm" cái gì khác "? –

+0

@Grant - oh hoàn toàn có thể, nhưng một lần nữa nó sẽ là ... phi tiêu chuẩn - hoặc ít nhất, nên được tài liệu tốt. Nếu nó là do luồng thì chúng tôi chỉ có mình để đổ lỗi, tất nhiên. –

+0

@Marc: Tôi không nói đó là một sự tối ưu hóa sớm, đặc biệt là khi bạn đang xử lý các thuộc tính không phải là O (1) (rất nhiều trong số chúng tồn tại trong WinForms) giống như cái tôi liên kết trong câu trả lời của tôi. Ngoài ra, trong các tình huống đa luồng, bạn có thể muốn giữ kết quả vì mục đích chính xác. –

Các vấn đề liên quan