2009-02-09 31 views
20

Làm ra các tham số trong C# có bất kỳ tác động nào về hiệu suất mà tôi nên biết không? (Giống như trường hợp ngoại lệ)Hiệu suất tham số C# out

Ý tôi là, có một phương pháp tốt với tham số out trong vòng lặp sẽ chạy vài triệu lần mỗi giây?

Tôi biết đó là xấu xí nhưng tôi đang sử dụng nó theo cách tương tự như Int32.TryParse đang sử dụng chúng - trở về một bool để biết một số xác nhận đã thành công và có một tham số out chứa một số dữ liệu bổ sung nếu nó đã thành công.

Trả lời

31

Tôi nghi ngờ rằng bạn sẽ tìm thấy bất kỳ hình phạt hiệu suất đáng kể nào khi sử dụng thông số out. Bạn phải lấy lại thông tin cho người gọi bằng cách nào đó hoặc khác - out chỉ là một cách khác để thực hiện nó. Bạn có thể thấy có một số hình phạt nếu bạn sử dụng tham số ngoài rộng rãi trong phương thức, vì nó cũng có thể có nghĩa là một mức độ chuyển hướng bổ sung cho mỗi quyền truy cập. Tuy nhiên, tôi sẽ không mong đợi nó là đáng kể. Như bình thường, hãy viết mã dễ đọc nhất và test whether performance is already good enough trước khi cố gắng tối ưu hóa thêm.

CHỈNH SỬA: Phần còn lại của điều này là một cách hiệu quả. Nó chỉ thực sự phù hợp với các loại giá trị lớn, mà nên thường tránh được anyway :)

Tôi không đồng ý với xác nhận của Konrad về "giá trị trả về cho tất cả các loại> 32 bit được xử lý tương tự hoặc giống hệt với đối số ngoài mức máy" Tuy nhiên. Dưới đây là một ứng dụng thử nghiệm nhỏ:

using System; 
using System.Diagnostics; 
using System.Runtime.CompilerServices; 

struct BigStruct 
{ 
    public Guid guid1, guid2, guid3, guid4; 
    public decimal dec1, dec2, dec3, dec4; 
} 

class Test 
{ 
    const int Iterations = 100000000; 

    static void Main() 
    { 
     decimal total = 0m; 
     // JIT first 
     ReturnValue(); 
     BigStruct tmp; 
     OutParameter(out tmp); 

     Stopwatch sw = Stopwatch.StartNew(); 
     for (int i=0; i < Iterations; i++) 
     { 
      BigStruct bs = ReturnValue(); 
      total += bs.dec1; 
     } 
     sw.Stop(); 
     Console.WriteLine("Using return value: {0}", 
          sw.ElapsedMilliseconds); 

     sw = Stopwatch.StartNew(); 
     for (int i=0; i < Iterations; i++) 
     { 
      BigStruct bs; 
      OutParameter(out bs); 
      total += bs.dec1; 
     } 
     Console.WriteLine("Using out parameter: {0}", 
          sw.ElapsedMilliseconds); 
    } 

    [MethodImpl(MethodImplOptions.NoInlining)] 
    public static BigStruct ReturnValue() 
    { 
     return new BigStruct(); 
    } 

    [MethodImpl(MethodImplOptions.NoInlining)] 
    public static void OutParameter(out BigStruct x) 
    { 
     x = new BigStruct(); 
    } 
} 

Kết quả:

Using return value: 11316 
Using out parameter: 7461 

Về cơ bản bằng cách sử dụng một tham số ra chúng tôi đang ghi dữ liệu trực tiếp đến đích cuối cùng, chứ không phải là ghi nó vào stack frame phương pháp nhỏ của và sau đó sao chép nó trở lại khung ngăn xếp của phương thức Main.

Hãy thoải mái chỉ trích ứng dụng điểm chuẩn mặc dù tôi có thể đã bỏ lỡ điều gì đó!

+0

Kết quả intersting - trong trường hợp của tôi tham số out chỉ là một int enum, không có gì lớn mặc dù. –

+0

Jon: hãy xem xét câu trả lời của tôi cho nhận xét của bạn. Mã của bạn thực sự khá tốt vì nó cho thấy sự thiếu tối ưu hóa đáng thương trong các giá trị trả lại nhưng nó không phải là ý tôi với câu nói của tôi. –

+1

@Konrad: Sau đó, tôi đề nghị bạn sửa lại tuyên bố của mình. Tham số C# out rõ ràng là * không * được xử lý giống như giá trị trả về C# ở mức máy trong .NET. –

5

Không có tác động về hiệu suất. out về cơ bản giống như bất kỳ đối số cũ nào đi qua, từ quan điểm kỹ thuật. Mặc dù có vẻ hợp lý rằng các dữ liệu khổng lồ được sao chép (ví dụ: đối với các cấu trúc lớn), điều này thực sự giống như đối với các giá trị trả lại.

Thực tế, giá trị trả lại cho tất cả các loại> 32 bit được xử lý tương tự như out đối số trên mức máy.

Xin lưu ý rằng tuyên bố cuối cùng không đề xuất trả về giá trị == out tham số trong .NET. Tiêu chuẩn của Jon cho thấy điều này rõ ràng (và đáng tiếc) không phải vậy. Trong thực tế, để làm cho nó giống hệt nhau, named return value optimization được sử dụng trong trình biên dịch C++. Một cái gì đó tương tự có thể được thực hiện trong các phiên bản tương lai của JIT để cải thiện hiệu năng trả về các cấu trúc lớn (tuy nhiên, vì các cấu trúc lớn khá hiếm trong .NET. Đây có thể là một tối ưu hóa không cần thiết).

Tuy nhiên, (và với kiến ​​thức rất hạn chế của tôi về lắp ráp x86), trả lại đối tượng từ cuộc gọi hàm thường đòi hỏi phân bổ đủ không gian tại trang cuộc gọi, đẩy địa chỉ vào ngăn xếp và điền nó bằng cách sao chép giá trị trả về nó. Điều này về cơ bản giống như out, chỉ bỏ qua một bản sao tạm thời không cần thiết của giá trị vì vị trí bộ nhớ đích có thể được truy cập trực tiếp.

+0

Tôi nghĩ rằng tôi không đồng ý mạnh mẽ với tuyên bố cuối cùng ... quan tâm để sao lưu nó? Viết một ứng dụng thử nghiệm nhỏ ... –

+0

Jon: Tôi không chắc chắn ý bạn là gì. Ở mức mã máy (trong assembly X86), bạn trả về các giá trị bằng cách đặt chúng trên stack, giống như các tham số. Đây là tất cả những gì tôi có ý nghĩa. Tôi không ám chỉ đến bất kỳ sự tương đương cấp cao hơn nào trong CIL. –

+0

Konrad: IIRC, trên cấp mã máy x86, bạn trả về giá trị với eax –

2

Lý do chính để tránh các tham số là khả năng đọc mã, thay vì hiệu suất.

Đối với các loại giá trị, không có sự khác biệt thực sự nào (chúng luôn luôn sao chép) và đối với các kiểu tham chiếu về cơ bản, nó giống như việc chuyển bởi ref. Bạn có thể dễ dàng đọc và hiểu khi bạn quay lại đoạn mã sau này.

+0

Chăm sóc để giải thích lý do tại sao? –

+0

Có thể hữu dụng trong các chức năng Thử .. như Từ điển <>. TryGetValue và int.TryParse ... – thinkbeforecoding

+0

Vâng, tôi không hoàn toàn thích mô hình chính xác cho cùng một lý do nhưng trong trường hợp này nó thực sự dễ đọc hơn so với sử dụng một số lớp container. –

1

Tham số ngoài được chuyển bởi ref. Vì vậy, chỉ có một con trỏ được truyền trên stack.

Nếu loại giá trị của bạn lớn, có ít bản sao hơn, nhưng sau đó bạn phải dereference con trỏ trên mỗi biến sử dụng.

+0

Tôi nghĩ bạn đang nghĩ đến một ngôn ngữ khác với C# :) –

+0

@Giovanni - chăm sóc để đủ điều kiện? Nghe có vẻ tốt với tôi - điểm nhỏ duy nhất là trình biên dịch ** không tham chiếu, không phải bạn ... –

+0

Không, giá trị không được sao chép trên ngăn xếp, vì vậy khi giá trị cần thiết để đọc (sau khi được khởi tạo ... các biến có thể biến đổi ok không được khuyến khích), bạn phải lấy giá trị từ tham chiếu. – thinkbeforecoding

0

Sử dụng tham số ngoài không làm ảnh hưởng đến hiệu suất. Tham số An Out về cơ bản là tham số tham chiếu, do đó cả người gọi và điểm callee đến cùng một bộ nhớ.

5

Không phải vấn đề về hiệu suất, nhưng có vấn đề xảy ra trước đó - you can't use them with variance in C# 4.0.

Cá nhân, tôi có xu hướng sử dụng out thông số một số tiền hợp lý trong tin mã của tôi (tức là bên trong một lớp học, có một phương thức trả về nhiều giá trị mà không sử dụng một loại riêng biệt) - nhưng tôi có xu hướng tránh chúng trên cộng đồng API, ngoại trừ mẫu bool Try{Something}(out result).