2011-11-13 21 views
8

Xin chào, đây là điều thực sự làm phiền tôi và tôi hy vọng ai đó có câu trả lời cho tôi. Tôi đã đọc về ref (và out) và tôi đang cố gắng tìm hiểu xem tôi có đang làm chậm mã của mình không bằng cách sử dụng ref s. Thường tôi sẽ thay thế một cái gì đó như:Chi phí hiệu suất để sử dụng ref thay vì trả lại các loại tương tự?

int AddToInt(int original, int add){ return original+add; } 

với

void AddToInt(ref int original, int add){ original+=add; } // 1st parameter gets the result 

vì để mắt này

AddToInt(ref _value, _add); 

là dễ dàng hơn để đọc và mã hơn thế này

_value = AddToInt(_value, _add); 

tôi biết chính xác những gì tôi đang làm trên đồng de sử dụng ref, thay vì trả về một giá trị. Tuy nhiên, hiệu suất là một cái gì đó tôi nghiêm túc, và rõ ràng dereferencing và dọn dẹp là rất nhiều chậm hơn khi bạn sử dụng refs.

Điều tôi muốn biết là lý do tại sao mỗi bài đăng tôi đọc cho biết có rất ít địa điểm bạn thường vượt qua ref (Tôi biết các ví dụ này được tạo ra, nhưng tôi hy vọng bạn sẽ có được ý tưởng). dường như với tôi rằng ví dụ ref nhỏ hơn, sạch hơn và chính xác hơn. Tôi cũng thích biết tại sao ref thực sự chậm hơn so với trả về một loại giá trị - đối với tôi nó dường như với tôi, nếu tôi định chỉnh sửa giá trị hàm rất nhiều trước khi trả lại nó, nó sẽ nhanh hơn để tham chiếu biến thực tế để chỉnh sửa biến đó trái ngược với phiên bản của biến đó ngay trước khi biến được xóa khỏi bộ nhớ.

+3

tôi thường thích trả lại một giá trị vì lý do phong cách, vì tôi thích tác dụng phụ chức năng miễn phí. – CodesInChaos

+0

Bạn đang cố gắng tìm ra chi phí hiệu suất? Hãy hỏi một hồ sơ, đừng hỏi chúng tôi! – sehe

+0

Trường hợp đầu tiên dễ đọc hơn vì bạn đã đặt tên cho phương thức của mình cho phù hợp. Một cái gì đó như '_value = Add (_value, _add);' hoặc '_value = SumOf (_value, _add);' là dễ đọc hơn đối với tôi. – nawfal

Trả lời

10

Thời gian chính mà "ref" được sử dụng trong sentance giống như hiệu suất là khi thảo luận về một số trường hợp rất điển hình, cho ví dụ trong các kịch bản XNA nơi trò chơi "đối tượng" khá phổ biến được biểu diễn bởi cấu trúc chứ không phải là lớp để tránh các vấn đề với GC (có tác động không cân xứng đối với XNA).Điều này trở nên hữu ích cho:

  • ngăn chặn sao chép một quá khổ struct nhiều lần trên stack
  • ngăn ngừa mất dữ liệu do đột biến một bản sao struct (struct XNA là phổ biến có thể thay đổi, chống lại thói quen bình thường)
  • phép đi qua một struct trong một mảng trực tiếp, chứ không phải bao giờ sao chép nó ra và trở lại trong

trong mọi trường hợp khác, "ref" được phổ biến hơn kết hợp với một tác dụng phụ bổ sung, không dễ dàng bày tỏ trong sự trở lại v alue (ví dụ xem Monitor.TryEnter).

Nếu bạn không có kịch bản như XNA/struct, và không có tác dụng phụ khó xử, thì chỉ cần sử dụng giá trị trả lại. Ngoài việc điển hình hơn (mà bản thân nó có giá trị), nó cũng có thể liên quan đến việc truyền ít dữ liệu hơn (và int nhỏ hơn ref trên x64 chẳng hạn) và có thể yêu cầu ít dereferencing hơn.

Cuối cùng, phương thức trả lại linh hoạt hơn; bạn không muốn cập nhật nguồn. Độ tương phản:

// want to accumulate, no ref 
x = Add(x, 5); 

// want to accumulate, ref 
Add(ref x, 5); 

// no accumulate, no ref 
y = Add(x, 5); 

// no accumulate, ref 
y = x; 
Add(ref y, x); 

Tôi nghĩ rằng cuối cùng là rõ ràng nhất (với "ref" khác một gần đằng sau nó) và sử dụng ref là thậm chí ít hơn rõ ràng trong ngôn ngữ mà nó không phải là rõ ràng (VB ví dụ) .

+0

Cảm ơn, điều đó giải thích chính xác nơi tôi làm và không muốn sử dụng ref và tại sao! Tốt nhất của tất cả các bạn đã cho tôi một loạt các điều để kiểm tra tôi sẽ không có cách khác. – user1044057

+1

Một lợi thế quan trọng của ref, mặc dù, tham khảo ví dụ của bạn: nếu một cái gì đó được thông qua bằng cách tham chiếu đến một phương pháp như Thêm, nó có thể có thể cho các phương pháp để thực hiện cập nhật trong khóa miễn phí, không chặn, nguyên tử, thread-safe thời trang. Vì vậy, nếu hai chủ đề đồng thời gọi một phương thức "Add" như được hiển thị trong ví dụ thứ hai, x sẽ có mười được thêm vào nó. Ngược lại, nếu một hoặc cả hai luồng đã cập nhật như được hiển thị trong ví dụ đầu tiên, một luồng có thể hoàn tác việc cập nhật được thực hiện bởi một chuỗi khác. – supercat

+0

@supercat để làm điều đó họ vẫn sẽ cần phải sử dụng Interlocked vv ... –

3

Đầu tiên, không bận tâm việc sử dụng ref có chậm hay nhanh hơn không. Đó là tối ưu hóa sớm. Trong 99,9999% trường hợp bạn sẽ không gặp phải tình huống này, điều này sẽ gây ra hiện tượng nghẽn cổ chai.

Thứ hai, trả về kết quả của phép tính dưới dạng giá trị trả về trái ngược với việc sử dụng ref được đặt trước vì tính chất 'funcional' thông thường của các ngôn ngữ giống như C. Nó dẫn đến chuỗi các câu lệnh/cuộc gọi tốt hơn.

+16

Tôi luôn giải thích các câu trả lời "Tối ưu hóa sớm" là "Tôi không biết". – Rauhotz

+2

Thực ra tôi cũng biết * và biết cách đo nó. Tuy nhiên, nó không có ý nghĩa gì cả để lãng phí thời gian bởi vì chúng ta đang nói về sự khác biệt của một vài chu kỳ CPU cho mỗi cuộc gọi. –

+4

Tôi không thích (làm việc với) devs luôn sử dụng hiệu suất làm trường hợp chính khi lựa chọn giữa một số kiểu/kiểu thiết kế/kiến ​​trúc mã hóa trong các lĩnh vực không hiệu quả. Xảy ra rất nhiều. – Guillaume86

2

Tôi phải đồng ý với Ondrej tại đây. Từ một cái nhìn phong cách, nếu bạn bắt đầu vượt qua tất cả mọi thứ với ref bạn cuối cùng sẽ kết thúc làm việc với các dev, những người sẽ muốn bóp cổ bạn để thiết kế một API như thế này!

Chỉ trả lại nội dung từ phương thức, không có 100% các phương thức của bạn trả về void. Những gì bạn đang làm sẽ dẫn đến mã rất ô uế và có thể gây nhầm lẫn cho các nhà phát triển khác, những người sẽ làm việc với mã của bạn. Ủng hộ rõ ràng hơn hiệu suất ở đây, vì bạn sẽ không đạt được nhiều trong optomization anyway.

việc kiểm tra này SO bài: C# 'ref' keyword, performance

và bài viết này từ Jon Skeet: http://www.yoda.arachsys.com/csharp/parameters.html

2

Mục đích chính của việc sử dụng từ khóa ref là để biểu thị rằng giá trị của biến có thể được thay đổi bởi hàm mà nó được chuyển vào. Khi bạn chuyển một biến theo giá trị, các cập nhật từ bên trong hàm sẽ không ảnh hưởng đến bản gốc.

Tính năng cực kỳ hữu ích (và nhanh hơn) của nó khi bạn muốn nhiều giá trị trả về và tạo cấu trúc hoặc lớp đặc biệt cho giá trị trả lại sẽ quá mức cần thiết. Ví dụ:

public void Quaternion.GetRollPitchYaw(ref double roll, ref double pitch, ref double yaw){ 
    roll = something; 
    pitch = something; 
    yaw = something; 
} 

Đây là mô hình khá cơ bản trong các ngôn ngữ có sử dụng con trỏ không hạn chế. Trong c/C++ bạn thường xuyên thấy các nguyên thủy được truyền xung quanh bởi giá trị với các lớp và mảng như con trỏ. C# không chỉ là đối diện để 'ref' là tiện dụng trong các tình huống như trên.

Khi bạn chuyển một biến mà bạn muốn cập nhật vào một hàm theo ref, chỉ cần viết 1 thao tác để cung cấp kết quả của bạn. Tuy nhiên, khi trả về các giá trị, bạn thường ghi vào một số biến bên trong hàm, trả về nó, sau đó viết lại nó vào biến đích. Tùy thuộc vào dữ liệu, điều này có thể thêm chi phí không cần thiết. Nhưng dù sao, đây là những điều chính mà tôi thường xem xét trước khi sử dụng từ khóa ref.

Đôi khi ref nhanh hơn một chút khi được sử dụng như thế này trong C# nhưng không đủ để sử dụng nó như một biện minh cho hiệu suất.

Dưới đây là những gì tôi nhận được trên máy tính 7 năm sử dụng mã bên dưới và cập nhật chuỗi 100k theo ref và theo giá trị.

Output:

lặp: 10000000 ByRef: 165ms ByVal: 417ms

private void m_btnTest_Click(object sender, EventArgs e) { 

    Stopwatch sw = new Stopwatch(); 

    string s = ""; 
    string value = new string ('x', 100000); // 100k string 
    int iterations = 10000000; 

    //----------------------------------------------------- 
    // Update by ref 
    //----------------------------------------------------- 
    sw.Start(); 
    for (var n = 0; n < iterations; n++) { 
     SetStringValue(ref s, ref value); 
    } 
    sw.Stop(); 
    long proc1 = sw.ElapsedMilliseconds; 

    sw.Reset(); 

    //----------------------------------------------------- 
    // Update by value 
    //----------------------------------------------------- 
    sw.Start(); 
    for (var n = 0; n < iterations; n++) { 
     s = SetStringValue(s, value); 
    } 
    sw.Stop(); 
    long proc2 = sw.ElapsedMilliseconds; 

    //----------------------------------------------------- 
    Console.WriteLine("iterations: {0} \nbyref: {1}ms \nbyval: {2}ms", iterations, proc1, proc2); 
} 

public string SetStringValue(string input, string value) { 
    input = value; 
    return input; 
} 

public void SetStringValue(ref string input, ref string value) { 
    input = value; 
} 
+0

Ở kích thước đó đối với một đối tượng, việc tạo một bản sao mới sẽ đắt hơn tất nhiên. Đối với hầu hết các trường hợp bình thường, tôi không nghĩ rằng có thể có sự khác biệt lớn. – nawfal

+0

tất cả ném kết quả là chính xác, các hành động không giống nhau trên hai chức năng. Trên thực tế, một chức năng là trả lại cái gì đó, cái kia thì không. Từ khóa ref một mình so sánh với từ khóa non ref không nhanh hơn nhưng gần như giống nhau, nếu không chậm hơn. Để thực hiện một phép đo chính xác, hãy thực hiện hai hàm với cùng các hành động và chỉ có 'ref' phải thay đổi ... – Aristos