2010-07-30 40 views
12

một câu hỏi nhanh, đơn giản từ tôi về các vòng lặp.C# - Đối với nội suy vòng lặp

Tình huống Tôi hiện đang viết một số mã hiệu suất cao khi tôi đột nhiên tự hỏi làm thế nào để vòng lặp thực sự hoạt động. Tôi biết tôi đã tình cờ gặp điều này trước đây, nhưng không thể cho cuộc sống của tôi tìm thấy thông tin này một lần nữa:/

Tuy nhiên, mối quan tâm chính của tôi là với giới hạn. Nói chúng ta có:

for(int i = 0; i < something.awesome; i++) 
{ 
// Do cool stuff 
} 

Câu hỏi là something.awesome được lưu trữ như là một biến nội bộ hoặc là vòng lặp liên tục lấy something.awesome làm logic-kiểm tra? Tại sao tôi hỏi là tất nhiên bởi vì tôi cần phải lặp qua rất nhiều công cụ được lập chỉ mục và tôi thực sự không muốn thêm chức năng gọi trên cao cho mỗi vượt qua.

Tuy nhiên nếu something.awesome chỉ được gọi một lần, thì tôi sẽ quay lại dưới tảng đá hạnh phúc của tôi! :)

+4

Vâng, hãy thử nó với 'for (int i = 0; i <(i + 2); ++ i)';) – Dario

+4

Chỉ cần sang một bên; có * là * một kịch bản mà JIT làm một cái gì đó thông minh ở đây; với mảng nó phát hiện trường hợp '

+0

Hmm, tôi thực sự không tin tưởng JIT rất nhiều kể từ khi tôi đang phát triển trong XNA đó là một chút kỳ quặc trên 360. Không được unappreciative mặc dù, điều này có thể có ích cho các ứng dụng PC. :) – Vectovox

Trả lời

20

Bạn có thể sử dụng một chương trình mẫu đơn giản để kiểm tra các hành vi:

using System; 

class Program 
{ 
    static int GetUpperBound() 
    { 
     Console.WriteLine("GetUpperBound called."); 
     return 5; 
    } 

    static void Main(string[] args) 
    { 
     for (int i = 0; i < GetUpperBound(); i++) 
     { 
      Console.WriteLine("Loop iteration {0}.", i); 
     } 
    } 
} 

Kết quả như sau:

GetUpperBound called. 
Loop iteration 0. 
GetUpperBound called. 
Loop iteration 1. 
GetUpperBound called. 
Loop iteration 2. 
GetUpperBound called. 
Loop iteration 3. 
GetUpperBound called. 
Loop iteration 4. 
GetUpperBound called. 

Chi tiết về hành vi này được mô tả trong Đặc tả Ngôn ngữ C# 4.0, mục 8.3.3 (Bạn sẽ tìm thấy e đặc tả bên C: \ Program Files \ Microsoft Visual Studio 10.0 \ VC# \ Thông số kỹ thuật \ 1033):

A cho statement được thực hiện như sau:

  • Nếu một cho- initializer là hiện tại, biến initializers hoặc tuyên bố biểu thức được thực hiện theo thứ tự chúng được viết. Bước này chỉ thực hiện một lần là .

  • Nếu có điều kiện, nó được đánh giá.

  • Nếu không có điều kiện hoặc nếu đánh giá mang lại giá trị đúng, thì điều khiển được chuyển sang câu lệnh được nhúng . Khi nào và nếu đạt kiểm soát điểm cuối nhúng tuyên bố (có thể là từ thực hiện một lệnh continue), các từ ngữ của phi iterator, nếu có, được đánh giá theo thứ tự, và sau đó lặp khác là được thực hiện, bắt đầu với đánh giá của các điều kiện trong bước ở trên.

  • Nếu có điều kiện và đánh giá mang lại kết quả sai, kiểm soát sẽ được chuyển đến điểm cuối của để có tuyên bố.

+0

Cảm ơn bạn đã cung cấp thêm thông tin! Tôi không biết câu trả lời luôn ở trước mặt tôi! :) – Vectovox

4

Nó được đánh giá mỗi lần. Hãy thử điều này trong một ứng dụng giao diện điều khiển đơn giản:

public class MyClass 
{ 
    public int Value 
    { 
     get 
     {     
      Console.WriteLine("Value called"); 
      return 3; 
     } 
    } 
} 

sử dụng theo cách này:

MyClass myClass = new MyClass(); 
for (int i = 0; i < myClass.Value; i++) 
{     
} 

sẽ dẫn đến ba dòng in ra màn hình.

Cập nhật

Vì vậy, để tránh điều này, bạn có thể này:

int awesome = something.awesome; 
for(int i = 0; i < awesome; i++) 
{ 
// Do cool stuff 
} 
1

something.awesome sẽ được đánh giá lại mỗi khi bạn đi qua các vòng lặp.

Sẽ tốt hơn để làm điều này:

int limit = something.awesome; 
for(int i = 0; i < limit; i++) 
{ 
// Do cool stuff 
} 
1

Mỗi lần trình biên dịch sẽ truy xuất giá trị của something.awesome và đánh giá nó

1

Điều kiện được đánh giá mỗi lần, kể cả lấy giá trị của something.awesome. Nếu bạn muốn tránh điều đó, hãy đặt biến tạm thời thành something.awesome và so sánh với biến tạm thời để thay thế.

6

Nếu something.awesome là trường , nó có khả năng truy cập mỗi lần vòng vòng vì thứ gì đó trong phần thân của vòng lặp có thể cập nhật. Nếu thân của vòng lặp đủ đơn giản và không gọi bất kỳ phương thức nào (ngoài các phương thức mà trình biên dịch inline), thì trình biên dịch có thể chứng minh rằng an toàn để đặt giá trị của something.awesome trong thanh ghi. Các nhà biên dịch được sử dụng để đi đến rất nhiều khả năng để làm điều này ngắn.

Tuy nhiên những ngày này phải mất một thời gian rất dài để truy cập một giá trị từ bộ nhớ chính, nhưng khi giá trị đã được đọc lần đầu tiên, nó được huấn luyện bởi CPU. Đọc giá trị lần thứ hai từ bộ nhớ cache CPU nhanh hơn rất nhiều so với tốc độ đọc nó từ một thanh ghi rồi đọc nó từ bộ nhớ chính. Bộ nhớ CPU nhanh hơn hàng trăm lần so với bộ nhớ chính.

Bây giờ nếu something.awesome là một tài sản , thì đó là phương thức thực hiện cuộc gọi phương thức. Trình biên dịch sẽ gọi phương thức mỗi lần vòng lặp. Tuy nhiên, nếu thuộc tính/phương thức chỉ là một vài dòng mã, nó có thể được inline biên dịch. Inlineing là khi trình biên dịch đặt một bản sao của mã phương thức trực tiếp thay vì gọi phương thức, do đó, một thuộc tính vừa trả về giá trị của một trường sẽ hoạt động giống như ví dụ trường ở trên.

Evan khi thuộc tính không được gạch chân, nó sẽ nằm trong bộ nhớ cache CPU sau khi nó được gọi là lần đầu tiên. Vì vậy, ales nó là rất phức tạp hoặc vòng lặp đi vòng nhiều lần phải mất rất nhiều thời gian để gọi lần đầu tiên vòng vòng, có thể bởi nhiều hơn một yếu tố của 10.

Trong những ngày cũ, it được sử dụng để dễ dàng bởi vì tất cả các truy cập bộ nhớ và hành động cpu mất cùng một lúc. Những ngày này bộ nhớ cache của bộ xử lý có thể dễ dàng thay đổi thời gian cho một số truy cập bộ nhớ và các cuộc gọi phương thức theo số trên hệ số 100. Profiler có xu hướng vẫn giả định rằng tất cả truy cập bộ nhớ đều mất thời gian! Vì vậy, nếu bạn hồ sơ, bạn sẽ được yêu cầu thực hiện các thay đổi có thể không có bất kỳ ảnh hưởng nào trong thế giới thực.

Thay đổi mã để:

int limit = something.awesome; 
for(int i = 0; i < limit; i++) 
{ 
// Do cool stuff 
} 

Will trong một số trường hợp lây lan nó lên, mà còn làm cho nó phức tạp hơn. Tuy nhiên

int limit = myArray.length; 
for(int i = 0; i < limit; i++) 
{ 
    myArray[i[ = xyn; 
} 

là chậm hơn sau đó

for(int i = 0; i < myArray.length; i++) 
{ 
    myArray[i[ = xyn; 
} 

như .net kiểm tra các ràng buộc của mảng mỗi khi họ được truy cập và có logic để loại bỏ việc kiểm tra khi vòng lặp đơn giản là đủ.

Vì vậy, cách tốt nhất là giữ mã đơn giản và rõ ràng cho đến khi bạn có thể chứng minh có sự cố. Bạn kiếm được nhiều lợi ích hơn bằng cách dành thời gian của bạn để cải thiện thiết kế tổng thể của một hệ thống, điều này rất dễ làm nếu mã bạn bắt đầu với đơn giản.

+0

Cảm ơn rất nhiều vì đã đọc tuyệt vời! Trong tình hình hiện tại của tôi, nó thực sự là một mảng, tuy nhiên tôi đã đi qua các trường hợp trước khi nó không phải là một, ví dụ. tính chất. Nhưng tôi sẽ "giữ nó sạch" dưới sự cân nhắc nặng nề. Thứ hai đoán quyết định của tôi là sau khi tất cả phản tác dụng! :) – Vectovox

0

Nó sẽ lưu trữ một số biến và xóa nó sau khi sử dụng.

sử dụng (int hạn = something.awesome)
{
for (int i = 0; i < limiti; i ++) {

// Mã.
}
}

Bằng cách này, nó sẽ không kiểm tra mọi lúc.

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