Tại sao người ta lại sử dụng công cụ sửa đổi tham số “in” trong C #?


84

Vì vậy, tôi (nghĩ rằng tôi) hiểu những gì công cụ insửa đổi tham số làm. Nhưng những gì nó làm có vẻ là khá dư thừa.

Thông thường, tôi nghĩ rằng lý do duy nhất để sử dụng a refsẽ là sửa đổi biến đang gọi, biến bị cấm rõ ràng in. Vì vậy, việc chuyển bằng intham chiếu có vẻ tương đương về mặt logic với việc truyền theo giá trị.

Có một số loại lợi thế hiệu suất? Tôi tin rằng ở mặt sau của mọi thứ, một reftham số ít nhất phải sao chép địa chỉ vật lý của biến, địa chỉ này phải có cùng kích thước với bất kỳ tham chiếu đối tượng điển hình nào.

Vì vậy, lợi thế chỉ là ở các cấu trúc lớn hơn, hay có một số tối ưu hóa trình biên dịch hậu trường khiến nó trở nên hấp dẫn ở những nơi khác? Nếu sau này, tại sao tôi không nên đặt mọi tham số là an in?


1
Có, có một lợi thế về hiệu suất. refđược sử dụng để chuyển cấu trúc bằng tham chiếu thay vì sao chép chúng. incó nghĩa là cấu trúc không nên được sửa đổi.
Panagiotis Kanavos

@dbc không, điều này không có gì để làm với interop
Panagiotis Kanavos

4
Hiệu suất cho các loại giá trị. Từ khóa “trong” mới cho C # 7.2
Silvermind

1
Thảo luận chi tiết tại đây . Lưu ý cảnh báo cuối cùng:It means that you should never pass a non-readonly struct as in parameter.
Panagiotis Kanavos

Tôi có thể đã thề rằng đây là một bản sao, nhưng tôi không thể tìm thấy nó nữa
JAD

Câu trả lời:


80

in gần đây đã được giới thiệu với ngôn ngữ C #.

inthực sự là một ref readonly. Nói chung, chỉ có một trường hợp sử dụng incó thể hữu ích: các ứng dụng hiệu suất cao xử lý rất nhiều ứng dụng lớn readonly struct.

Giả sử bạn có:

readonly struct VeryLarge
{
    public readonly long Value1;   
    public readonly long Value2;

    public long Compute() { }
    // etc
}

void Process(in VeryLarge value) { }

Trong trường hợp đó, VeryLargecấu trúc sẽ được chuyển qua tham chiếu mà không tạo các bản sao phòng thủ khi sử dụng cấu trúc này trong Processphương thức (ví dụ: khi gọi value.Compute()) và tính bất biến của cấu trúc được đảm bảo bởi trình biên dịch.

Lưu ý rằng việc chuyển một hàm not-readonly structvới một công cụ insửa đổi sẽ khiến trình biên dịch tạo ra một bản sao phòng thủ khi gọi các phương thức của struct và truy cập các thuộc tính trong Processphương thức trên, điều này sẽ ảnh hưởng tiêu cực đến hiệu suất!

Có một mục blog MSDN thực sự tốt mà tôi khuyên bạn nên đọc kỹ.

Nếu bạn muốn biết thêm một số nền tảng lịch sử của-giới inthiệu, bạn có thể đọc cuộc thảo luận này trong kho lưu trữ GitHub của ngôn ngữ C #.

Nói chung, hầu hết các nhà phát triển đồng ý rằng việc giới thiệu của incó thể được coi là một sai lầm. Đó là một tính năng ngôn ngữ khá kỳ lạ và chỉ có thể hữu ích trong các trường hợp có độ hoàn thiện cao.


Nó có ngăn chặn các bản sao phòng thủ do trình biên dịch tạo ra, hay chỉ đơn thuần là loại bỏ sự cần thiết của các lập trình viên để tạo các bản sao phòng thủ theo cách thủ công trong các ngữ cảnh sẽ sử dụng ref, ví dụ bằng cách cho phép someProc(in thing.someProperty);so với propType myProp = thing.someProperty; someProc(ref myProp);? là inmột khái niệm C # -chỉ như thế nào out, hay nó đã được thêm vào .NET Framework?
supercat

@supercat, bạn không thể chuyển một thuộc tính bằng cách sử dụng in, vì innó có hiệu quả refvới một thuộc tính đặc biệt. Vì vậy, đoạn mã đầu tiên của bạn sẽ không được biên dịch. @VisualMelon, đúng vậy, việc sao chép phòng thủ xảy ra khi gọi các phương thức hoặc truy cập các thuộc tính của cấu trúc từ bên trong phương thức lấy cấu trúc làm đối số.
dymanoid

@dymanoid xin lỗi vì spam bạn, nó đã cho tôi khoảng 10 cố gắng hiểu những gì bạn đã viết (và tôi không nghĩ rằng đó là lỗi của bạn!)
VisualMelon

4
@dymanoid: Cả hai inoutđều là những khái niệm đáng lẽ phải có trong Framework ngay từ đầu (cùng với một phương tiện mà các phương thức và thuộc tính có thể cho biết liệu chúng có sửa đổi hay không this). Một trình biên dịch có thể cho phép một thuộc tính được chuyển tới một inđối số bằng cách chuyển một tham chiếu đến một thuộc tính tạm thời giữ nó; nếu một hàm được viết bằng ngôn ngữ khác sửa đổi tạm thời đó, ngữ nghĩa sẽ hơi khó hiểu, nhưng đó sẽ là lỗi do hành vi của hàm không khớp với chữ ký của nó.
supercat

1
@supercat, vui lòng không mở cuộc thảo luận ở đây (Dù sao thì tôi cũng không thuộc nhóm khái niệm .NET Framework). Có một cuộc thảo luận dài được tham chiếu trong câu trả lời và cuộc thảo luận trên GitHub tham khảo một số cuộc thảo luận khác - bài đọc thú vị. BTW, bạn có thể chuyển các thuộc tính by-ref trong VB.NET - trình biên dịch tạo ra những khoảng thời gian tạm thời đó cho bạn (tất nhiên có thể dẫn đến các vấn đề khó hiểu).
dymanoid

41

chuyển qua intham chiếu có vẻ tương đương về mặt logic với việc truyền theo giá trị.

Chính xác.

Có một số loại lợi thế hiệu suất?

Đúng.

Tôi tin rằng ở mặt sau của mọi thứ, một reftham số ít nhất phải sao chép địa chỉ vật lý của biến, địa chỉ này phải có cùng kích thước với bất kỳ tham chiếu đối tượng điển hình nào.

Không có yêu cầu rằng một tham chiếu đến một đối tượng và một tham chiếu đến một biến đều có cùng kích thước và không có yêu cầu rằng cả hai đều phải là kích thước của một từ máy, nhưng có, trên thực tế cả hai đều là 32 bit trên 32 máy bit và 64 bit trên máy 64 bit.

Tôi không rõ bạn nghĩ "địa chỉ thực" có liên quan gì đến nó. Trên Windows, chúng tôi sử dụng địa chỉ ảo , không phải địa chỉ thực trong mã chế độ người dùng. Trong những trường hợp có thể, bạn sẽ tưởng tượng rằng một địa chỉ thực có ý nghĩa trong chương trình C #, tôi rất tò mò muốn biết.

Cũng không có yêu cầu rằng một tham chiếu thuộc bất kỳ loại nào phải được triển khai làm địa chỉ ảo của bộ nhớ. Các tham chiếu có thể là các chốt không rõ ràng vào các bảng GC trong việc triển khai phù hợp đặc điểm kỹ thuật CLI.

là lợi thế chỉ trong cấu trúc lớn hơn?

Giảm chi phí chuyển các cấu trúc lớn hơn là kịch bản thúc đẩy tính năng.

Lưu ý rằng không có gì đảm bảo inlàm cho bất kỳ chương trình nào thực sự nhanh hơn và nó có thể làm cho các chương trình chậm hơn. Tất cả các câu hỏi về hiệu suất phải được trả lời bằng nghiên cứu thực nghiệm . Có rất ít tối ưu hóa luôn luôn thắng ; đây không phải là tối ưu hóa "luôn thắng".

có một số tối ưu hóa trình biên dịch hậu trường khiến nó trở nên hấp dẫn ở những nơi khác không?

Trình biên dịch và thời gian chạy được phép thực hiện bất kỳ tối ưu hóa nào mà họ chọn nếu làm như vậy không vi phạm các quy tắc của đặc tả C #. Theo hiểu biết của tôi, chưa phải là một tối ưu hóa như vậy cho incác tham số, nhưng điều đó không loại trừ những tối ưu hóa như vậy trong tương lai.

tại sao tôi không nên đặt mọi tham số là một trong?

Giả sử bạn đã tạo một inttham số thay vì một in inttham số. Những chi phí nào được áp đặt?

  • trang web cuộc gọi hiện yêu cầu một biến thay vì một giá trị
  • biến không thể được đăng ký. Sơ đồ phân bổ thanh ghi được điều chỉnh cẩn thận của jitter chỉ có một cờ lê được đưa vào nó.
  • mã tại trang web cuộc gọi lớn hơn bởi vì nó phải lấy tham chiếu đến biến và đặt nó vào ngăn xếp, trong khi trước đó nó có thể chỉ cần đẩy giá trị lên ngăn xếp cuộc gọi
  • mã lớn hơn có nghĩa là một số hướng dẫn nhảy ngắn bây giờ có thể đã trở thành hướng dẫn nhảy dài, vì vậy một lần nữa, mã hiện lớn hơn. Điều này có tác động trực tiếp đến tất cả các loại. Bộ nhớ đệm được lấp đầy sớm hơn, bộ nhớ đệm có nhiều việc phải làm hơn, bộ nhớ đệm có thể chọn không thực hiện một số tối ưu hóa nhất định trên kích thước mã lớn hơn, v.v.
  • tại trang web callee, chúng tôi đã chuyển quyền truy cập vào một giá trị trên ngăn xếp (hoặc đăng ký) thành một hướng dẫn thành một con trỏ. Bây giờ, con trỏ đó có nhiều khả năng nằm trong bộ nhớ cache, nhưng chúng tôi vẫn chuyển quyền truy cập một lệnh vào giá trị thành quyền truy cập hai lệnh.
  • Và như thế.

Giả sử đó là một doublevà bạn thay đổi nó thành một in double. Một lần nữa, bây giờ biến không thể được đăng ký vào một thanh ghi dấu phẩy động hiệu suất cao. Điều này không chỉ có ý nghĩa về hiệu suất mà còn có thể thay đổi hành vi của chương trình! C # được phép thực hiện số học float với độ chính xác cao hơn 64-bit và thường chỉ làm như vậy nếu các float có thể được đăng ký.

Đây không phải là một tối ưu hóa miễn phí. Bạn phải đo hiệu suất của nó so với các lựa chọn thay thế. Đặt cược tốt nhất của bạn là không nên tạo cấu trúc lớn ngay từ đầu, như các nguyên tắc thiết kế đề xuất.


"Trong [...] trường hợp có thể, bạn sẽ tưởng tượng rằng một địa chỉ thực có ý nghĩa trong chương trình C #, [...]." . C # thường được sử dụng trên các hệ thống có I / O được ánh xạ bộ nhớ và những thứ như vectơ đặt lại, v.v. Tôi đang nghĩ về các hộp wintel cũ ở đây. Với bộ vi xử lý x86 và bộ đệm khung VGA. Được rồi, thường thì chúng ta không thao tác trực tiếp, nhưng chúng ta có thể, phải không?
OmarL

5
inthực sự đi ngang qua giá trị trong mọi trường hợp không? Nếu chúng ta có f(ref T x, in T y)fsửa đổi x, nó sẽ quan sát sự thay đổi tương tự đối với ykhi được gọi là f(ref a,a). Điều tương tự cũng áp dụng nếu fnhận một in T yvà một đại biểu sẽ sửa đổi ykhi được gọi. Nếu không in, ngữ nghĩa sẽ khác, vì ygiá trị của nó sẽ không bao giờ thay đổi, tôi nghĩ, vì nó sẽ là một bản sao.
chi

2
Tôi nghi ngờ OP có nghĩa là "địa chỉ vật lý" theo cách không chính thức, là "mô hình bit mô tả vị trí bộ nhớ", tương phản với "bất kỳ thứ gì mà chương trình phụ trợ chọn sử dụng để mô tả nơi tìm một đối tượng".
Sneftel

@Wilson: Tôi muốn xem một ví dụ về những gì bạn đang nói; Tôi không biết bất kỳ ai cố gắng làm những điều đó trong C #. Đã có một số cuộc nói chuyện trong nhiều năm về "hệ thống C #" theo đó một ngôn ngữ được quản lý cấp cao hơn có thể được sử dụng để viết các thành phần hệ thống cấp thấp, nhưng tôi chưa bao giờ thực sự nhìn thấy mã như vậy.
Eric Lippert

2
@chi: Đúng vậy. Nếu bạn chơi các trò chơi nguy hiểm với răng cưa biến đổi thì bạn có thể gặp rắc rối. Nếu bạn cảm thấy đau khi làm điều đó, đừng làm vậy. Mục đích của inlà đại diện cho khái niệm "chuyển qua tham chiếu, chỉ đọc", về mặt logic tương đương với việc chuyển theo giá trị khi bạn cư xử hợp lý .
Eric Lippert

6

Có. Khi chuyển một struct, intừ khóa cho phép tối ưu hóa trong đó trình biên dịch chỉ cần chuyển một con trỏ mà không có nguy cơ phương thức thay đổi nội dung. Điều cuối cùng là quan trọng - nó tránh thao tác sao chép. Trên các cấu trúc lớn, điều này có thể tạo ra một thế giới khác biệt.


2
Điều này chỉ lặp lại những gì câu hỏi đã nói, và không trả lời câu hỏi thực tế mà nó yêu cầu.
Servy

Chỉ trong cấu trúc chỉ đọc. Nếu không trình biên dịch vẫn sẽ tạo ra một bản sao phòng thủ
Panagiotis Kanavos

1
Thực ra là không. Nó đang xác nhận rằng có một lợi ích biểu diễn. Cho rằng IN được tạo ra trong phần trình diễn chạy qua langauge, vâng, đó là lý do. Nó cho phép tối ưu hóa (trên cấu trúc chỉ đọc).
TomTom

3
@TomTom Một lần nữa, câu hỏi đã được đề cập . Những gì nó hỏi là, "Vậy, lợi thế chỉ là ở các cấu trúc lớn hơn, hay là có một số tối ưu hóa trình biên dịch hậu trường khiến nó trở nên hấp dẫn ở nơi khác? Nếu sau này, tại sao tôi không nên đặt mọi tham số là một trong?" Lưu ý rằng nó không hỏi liệu nó có thực sự có lợi cho các cấu trúc lớn hơn hay chỉ khi nó có lợi. Nó chỉ hỏi xem nó có lợi cho các cấu trúc nhỏ hơn hay không (và sau đó là theo dõi nếu có). Bạn chưa trả lời câu hỏi nào.
Servy

2

Điều này được thực hiện nhờ cách tiếp cận lập trình chức năng. Một trong những nguyên tắc chính là hàm không được có tác dụng phụ, có nghĩa là nó không được thay đổi giá trị của các tham số và phải trả về một số giá trị. Trong C #, không có cách nào để truyền cấu trúc (và kiểu giá trị) mà không được sao chép chỉ bằng tham chiếu cho phép thay đổi giá trị. Trong nhanh chóng, có một thuật toán hacky sao chép struct (bộ sưu tập của chúng là cấu trúc BTW) miễn là phương thức bắt đầu thay đổi giá trị của nó. Những người sử dụng swift không phải ai cũng biết về những thứ sao chép. Đây là tính năng c # hay vì nó hiệu quả về bộ nhớ và rõ ràng. Nếu bạn nhìn vào những gì mớibạn sẽ thấy rằng ngày càng có nhiều thứ được thực hiện xung quanh cấu trúc và mảng trong ngăn xếp. Và trong tuyên bố là chỉ cần thiết cho các tính năng này. Có những hạn chế được đề cập trong các câu trả lời khác, nhưng điều đó không cần thiết để hiểu .net đang hướng đến đâu.


2

trong đó là tham chiếu chỉ đọc trong c # 7.2

phương tiện này bạn không vượt qua toàn bộ đối tượng đến chức năng ngăn xếp tương tự như ref trường hợp bạn vượt qua chỉ đề cập đến cấu trúc

nhưng cố gắng thay đổi giá trị của đối tượng gây ra lỗi trình biên dịch.

Và có, điều này sẽ cho phép bạn tối ưu hóa hiệu suất mã nếu bạn sử dụng các cấu trúc lớn.

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.