Hàm nội tuyến trong C #?


276

Làm thế nào để bạn thực hiện "chức năng nội tuyến" trong C #? Tôi không nghĩ rằng tôi hiểu khái niệm này. Có phải họ thích phương pháp ẩn danh? Giống như chức năng lambda?

Lưu ý : Các câu trả lời gần như hoàn toàn liên quan đến khả năng của các hàm nội tuyến , tức là "tối ưu hóa thủ công hoặc trình biên dịch thay thế một trang gọi hàm với phần thân của callee." Nếu bạn quan tâm đến các hàm ẩn danh (còn gọi là lambda) , hãy xem câu trả lời của @ jalf hoặc đây là 'Lambda' mà mọi người cứ nói đến? .


11
Cuối cùng cũng có thể - xem câu trả lời của tôi.
konrad.kruczynski

1
Đối với điều gần nhất trước .NET 4.5, hãy xem câu hỏi Làm thế nào để xác định biến là hàm lambda . Nó KHÔNG thực sự biên dịch dưới dạng nội tuyến nhưng nó là một khai báo hàm ngắn như khai báo nội tuyến. Phụ thuộc vào những gì bạn đang cố gắng đạt được bằng cách sử dụng nội tuyến.
AppFzx

Đối với những người tò mò, hãy xem phần mở rộng VS này .
TripleAccemony

Câu trả lời:


384

Cuối cùng, trong .NET 4.5, CLR cho phép một người gợi ý / gợi ý 1 phương thức nội tuyến bằng cách sử dụng MethodImplOptions.AggressiveInlininggiá trị. Nó cũng có sẵn trong thân cây của Mono (cam kết ngày hôm nay).

// The full attribute usage is in mscorlib.dll,
// so should not need to include extra references
using System.Runtime.CompilerServices; 

...

[MethodImpl(MethodImplOptions.AggressiveInlining)]
void MyMethod(...)

1 . Trước đây "lực lượng" đã được sử dụng ở đây. Vì có một vài downvote, tôi sẽ cố gắng làm rõ thuật ngữ này. Như trong các bình luận và tài liệu, The method should be inlined if possible.Đặc biệt là xem xét Mono (đang mở), có một số hạn chế kỹ thuật dành riêng cho đơn nhân khi xem xét nội tuyến hoặc tổng quát hơn (như các hàm ảo). Nhìn chung, vâng, đây là một gợi ý cho trình biên dịch, nhưng tôi đoán đó là những gì được yêu cầu.


17
+1 - Cập nhật câu trả lời của bạn để cụ thể hơn về các yêu cầu phiên bản khung.
M.Babcock

4
Nó có thể vẫn không phải là một nội lực bắt buộc , nhưng ghi đè các heuristic của JITters chắc chắn là đủ trong hầu hết các tình huống.
CodeInChaos

7
Một cách tiếp cận khác nhau có thể hoạt động với tất cả các phiên bản .NET là chia một phương thức hơi quá lớn thành hai phương thức, một phương thức gọi phương thức kia, không phương thức nào vượt quá 32 byte IL. Hiệu ứng ròng là như thể ban đầu được nội tuyến.
Rick Sladkey

4
Nó không bắt buộc "Nội tuyến", chỉ cần cố gắng nói chuyện với JIT và nói với nó rằng lập trình viên thực sự muốn sử dụng Inlining ở đây, nhưng JIT có từ cuối cùng trên đó. Do đó MSDN: Phương pháp nên được nội tuyến nếu có thể.
Orel Eraki

11
Khi so sánh, các đề xuất nội tuyến của C ++, ngay cả các đề xuất cụ thể của trình biên dịch, cũng không thực sự bắt buộc một nội tuyến: không phải tất cả các hàm đều có thể được nội tuyến (những điều cơ bản như các hàm đệ quy đều khó, nhưng cũng có những trường hợp khác). Vì vậy, yeah, lực lượng "không hoàn toàn" này là điển hình.
Eamon Nerbonne

87

Các phương thức nội tuyến chỉ đơn giản là một tối ưu hóa trình biên dịch trong đó mã của hàm được đưa vào trình gọi.

Không có cơ chế để thực hiện điều này trong C # và chúng sẽ được sử dụng một cách tiết kiệm trong các ngôn ngữ mà chúng được hỗ trợ - nếu bạn không biết tại sao chúng nên được sử dụng ở đâu đó, thì chúng không nên sử dụng.

Chỉnh sửa: Để làm rõ, có hai lý do chính mà chúng cần được sử dụng một cách tiết kiệm:

  1. Thật dễ dàng để tạo các nhị phân lớn bằng cách sử dụng nội tuyến trong trường hợp không cần thiết
  2. Trình biên dịch có xu hướng biết rõ hơn bạn làm khi một cái gì đó, từ quan điểm hiệu suất, được nội tuyến

Tốt nhất là để mọi thứ một mình và để trình biên dịch thực hiện công việc của nó, sau đó lập hồ sơ và tìm hiểu xem nội tuyến có phải là giải pháp tốt nhất cho bạn không. Tất nhiên, một số điều chỉ có ý nghĩa để được nội tuyến (đặc biệt là toán tử toán học), nhưng để trình biên dịch xử lý nó thường là cách thực hành tốt nhất.


35
Thông thường tôi nghĩ rằng nó ổn khi trình biên dịch xử lý nội tuyến. Nhưng có những tình huống tôi muốn ghi đè quyết định của trình biên dịch và nội tuyến hoặc không nội tuyến một phương thức.
mmmmmmmm

7
@Joel Coehoorn: Đây sẽ là một thực tiễn tồi vì nó sẽ phá vỡ mô đun hóa và bảo vệ truy cập. (Nghĩ về một phương thức được nội tuyến trong một lớp truy cập các thành viên riêng và được gọi từ điểm khác nhau trong mã!)
mmmmmmmm

9
@Poma Đó là một lý do kỳ lạ để sử dụng nội tuyến. Tôi rất nghi ngờ nó sẽ chứng minh là có hiệu quả.
Chris hét lên

56
Các đối số về trình biên dịch biết tốt nhất chỉ là sai. Nó không nội tuyến bất kỳ phương thức nào lớn hơn 32 byte IL, dấu chấm. Điều này thật kinh khủng đối với các dự án (như của tôi, và cả của Egor ở trên) có các điểm nóng được xác định rõ ràng mà chúng tôi không thể làm được gì. Không có gì, đó là, ngoại trừ việc cắt và dán mã và theo cách thủ công. Nói một cách dễ hiểu, đây là một tình trạng khủng khiếp khi bạn thực sự lái hiệu suất.
sáng

6
Nội tuyến tinh tế hơn mà nó xuất hiện. Bộ nhớ có bộ nhớ nhanh để truy cập và phần hiện tại của mã được lưu trữ trong các bộ đệm đó giống như các biến. Tải các dòng hướng dẫn bộ đệm tiếp theo là lỗi bộ nhớ cache và chi phí có thể gấp 10 lần hoặc nhiều hơn so với một lệnh đơn. Cuối cùng, nó phải tải vào bộ đệm L2, thậm chí còn đắt hơn. Do đó, mã cồng kềnh có thể khiến bộ nhớ cache bị mất nhiều hơn, trong đó một hàm được đặt trong cùng một dòng bộ đệm L1 trong toàn bộ thời gian có thể dẫn đến việc bỏ lỡ bộ nhớ cache ít hơn và có khả năng mã nhanh hơn. Với .Net thậm chí còn phức tạp hơn.

56

Cập nhật: Câu trả lời của mỗi konrad.kruczynski , điều sau đây là đúng đối với các phiên bản .NET lên đến và bao gồm 4.0.

Bạn có thể sử dụng lớp MethodImplAttribution để ngăn phương thức được nội tuyến ...

[MethodImpl(MethodImplOptions.NoInlining)]
void SomeMethod()
{
    // ...
}

... nhưng không có cách nào để làm điều ngược lại và buộc nó phải được nội tuyến.


2
Thật thú vị khi biết điều đó, nhưng tại sao quái vật ngăn cản một phương pháp được nội tuyến? Tôi đã dành một chút thời gian nhìn chằm chằm vào màn hình nhưng tôi không thể đưa ra lý do tại sao nội tuyến có thể gây hại gì.
Camilo Martin

3
Nếu bạn nhận được một ngăn xếp cuộc gọi (tức là NullReferenceException) và rõ ràng không có cách nào mà phương thức trên đầu ngăn xếp đã ném nó. Tất nhiên, một trong những cuộc gọi của nó có thể có. Nhưng cái nào?
dzendras

19
GetExecutingAssemblyGetCallingAssemblycó thể cho các kết quả khác nhau tùy thuộc vào việc phương pháp được nội tuyến. Buộc một phương thức không nội tuyến sẽ loại bỏ bất kỳ sự không chắc chắn nào.
stusmith

1
@Downvoter: nếu bạn không đồng ý với những gì tôi đã viết, bạn nên đăng bình luận giải thích lý do. Không chỉ là phép lịch sự phổ biến, bạn cũng không thực hiện được nhiều bằng cách đưa ra tổng số phiếu bầu của hơn 20 câu trả lời.
BACON

2
Ước gì tôi có thể +2 vì chỉ riêng tên của bạn đã xứng đáng +1: D.
retrodrone

33

Bạn đang trộn lẫn hai khái niệm riêng biệt. Hàm nội tuyến là một tối ưu hóa trình biên dịch không có tác động đến ngữ nghĩa. Một hàm hoạt động giống nhau cho dù nó có nội tuyến hay không.

Mặt khác, các hàm lambda hoàn toàn là một khái niệm ngữ nghĩa. Không có yêu cầu về cách chúng nên được thực hiện hoặc thực thi, miễn là chúng tuân theo hành vi được quy định trong thông số ngôn ngữ. Chúng có thể được nội tuyến nếu trình biên dịch JIT cảm thấy như vậy hoặc không.

Không có từ khóa nội tuyến trong C #, vì đó là một tối ưu hóa thường có thể để lại cho trình biên dịch, đặc biệt là trong các ngôn ngữ của JIT. Trình biên dịch JIT có quyền truy cập vào số liệu thống kê thời gian chạy cho phép nó quyết định nội tuyến nào hiệu quả hơn nhiều so với khi bạn viết mã. Một hàm sẽ được nội tuyến nếu trình biên dịch quyết định và bạn không thể làm gì về nó. :)


20
"Một hàm hoạt động giống nhau cho dù nó có nội tuyến hay không." Có một số trường hợp hiếm hoi khi điều này không đúng: cụ thể là các chức năng ghi nhật ký muốn biết về theo dõi ngăn xếp. Nhưng tôi không muốn làm suy yếu tuyên bố cơ sở của bạn quá nhiều: nó thường đúng.
Joel Coehoorn

"Và không có gì bạn có thể làm về nó theo cách nào." - không đúng. Ngay cả khi chúng tôi không tính "đề xuất mạnh" cho trình biên dịch là làm bất cứ điều gì, bạn vẫn có thể ngăn các hàm bị nội tuyến.
BartoszKP

21

Bạn có nghĩa là các hàm nội tuyến theo nghĩa C ++? Trong đó các nội dung của một chức năng bình thường được tự động sao chép nội tuyến vào các cuộc gọi? Hiệu quả cuối cùng là không có cuộc gọi chức năng nào thực sự xảy ra khi gọi một chức năng.

Thí dụ:

inline int Add(int left, int right) { return left + right; }

Nếu vậy thì không, không có C # tương đương với điều này.

Hoặc bạn có nghĩa là các chức năng được khai báo trong một chức năng khác? Nếu vậy thì có, C # hỗ trợ điều này thông qua các phương thức ẩn danh hoặc biểu thức lambda.

Thí dụ:

static void Example() {
  Func<int,int,int> add = (x,y) => x + y;
  var result = add(4,6);  // 10
}

21

Cody có quyền, nhưng tôi muốn cung cấp một ví dụ về chức năng nội tuyến là gì.

Giả sử bạn có mã này:

private void OutputItem(string x)
{
    Console.WriteLine(x);

    //maybe encapsulate additional logic to decide 
    // whether to also write the message to Trace or a log file
}

public IList<string> BuildListAndOutput(IEnumerable<string> x)
{  // let's pretend IEnumerable<T>.ToList() doesn't exist for the moment
    IList<string> result = new List<string>();

    foreach(string y in x)
    {
        result.Add(y);
        OutputItem(y);
    }
    return result;
}

Các trình biên dịch Just-In-Time ưu có thể chọn để thay đổi mã để tránh lặp đi lặp lại cách đặt một cuộc gọi đến OutputItem () trên stack, do đó nó sẽ là như nếu bạn đã viết đoạn code như thay vì điều này:

public IList<string> BuildListAndOutput(IEnumerable<string> x)
{
    IList<string> result = new List<string>();

    foreach(string y in x)
    {
        result.Add(y);

        // full OutputItem() implementation is placed here
        Console.WriteLine(y);   
    }

    return result;
}

Trong trường hợp này, chúng ta sẽ nói hàm OutputItem () đã được nội tuyến. Lưu ý rằng nó có thể làm điều này ngay cả khi OutputItem () được gọi từ các nơi khác.

Đã chỉnh sửa để hiển thị một kịch bản nhiều khả năng sẽ được nội tuyến.


9
Chỉ cần làm rõ mặc dù; đó là JIT thực hiện nội tuyến; không phải trình biên dịch C #.
Marc Gravell

Cũng lưu ý rằng, ban đầu ít nhất, JIT sẽ 'thích' hơn các phương thức tĩnh nội tuyến, thậm chí qua các ranh giới lắp ráp. Do đó, một kỹ thuật trường học cũ để tối ưu hóa là đánh dấu các phương thức của bạn là tĩnh. Điều này đã được cộng đồng cau mày nhưng cho đến ngày nay tôi vẫn sẽ chọn các phương thức nội tuyến có tính chất nội bộ, không đa hình và thường tốn ít chi phí để thực hiện hơn so với đổ chồng + liên kết.
Shaun Wilson

7

Đúng Chính xác, sự khác biệt duy nhất là thực tế nó trả về một giá trị.

Đơn giản hóa (không sử dụng biểu thức):

List<T>.ForEach Thực hiện một hành động, nó không mong đợi một kết quả trả lại.

Vì vậy, một Action<T>đại biểu sẽ đủ .. nói:

List<T>.ForEach(param => Console.WriteLine(param));

cũng giống như nói:

List<T>.ForEach(delegate(T param) { Console.WriteLine(param); });

sự khác biệt là kiểu param và decler đại biểu được suy ra bằng cách sử dụng và không cần niềng răng trên một phương thức nội tuyến đơn giản.

Trong khi

List<T>.Where Có một chức năng, mong đợi một kết quả.

Vì vậy, một Function<T, bool>mong đợi:

List<T>.Where(param => param.Value == SomeExpectedComparison);

tương tự như:

List<T>.Where(delegate(T param) { return param.Value == SomeExpectedComparison; });

Bạn cũng có thể khai báo các phương thức này nội tuyến và gán chúng cho các biến IE:

Action myAction = () => Console.WriteLine("I'm doing something Nifty!");

myAction();

hoặc là

Function<object, string> myFunction = theObject => theObject.ToString();

string myString = myFunction(someObject);

Tôi hi vọng cái này giúp được.


2

Có những lúc tôi muốn buộc mã được xếp hàng.

Ví dụ: nếu tôi có một thói quen phức tạp, trong đó có một số lượng lớn các quyết định được đưa ra trong một khối lặp rất cao và những quyết định đó dẫn đến các hành động tương tự nhưng hơi khác nhau sẽ được thực hiện. Ví dụ, hãy xem xét một trình so sánh sắp xếp phức tạp (không điều khiển DB) trong đó đại số sắp xếp sắp xếp các yếu tố theo một số tiêu chí không liên quan khác nhau như người ta có thể làm nếu chúng sắp xếp các từ theo tiêu chí ngữ pháp cũng như ngữ nghĩa cho ngôn ngữ nhanh hệ thống công nhận. Tôi có xu hướng viết các hàm trợ giúp để xử lý các hành động đó để duy trì khả năng đọc và mô đun hóa của mã nguồn.

Tôi biết rằng các hàm trợ giúp đó phải được xếp hàng vì đó là cách mà mã sẽ được viết nếu con người không bao giờ phải hiểu. Tôi chắc chắn sẽ muốn đảm bảo trong trường hợp này là không có chức năng gọi qua đầu.


2

Câu lệnh "tốt nhất là để những thứ này một mình và để trình biên dịch thực hiện công việc .." (Cody Brocons) hoàn toàn là rubish. Tôi đã lập trình mã trò chơi hiệu suất cao trong 20 năm và tôi chưa bắt gặp một trình biên dịch 'đủ thông minh' để biết mã nào nên được nội tuyến (hàm) hay không. Sẽ rất hữu ích khi có một câu lệnh "nội tuyến" trong c #, sự thật là trình biên dịch chỉ không có tất cả thông tin cần thiết để xác định hàm nào sẽ luôn được nội tuyến hay không mà không có gợi ý "nội tuyến". Chắc chắn nếu hàm này nhỏ (accessor) thì nó có thể được tự động nội tuyến, nhưng nếu đó là một vài dòng mã thì sao? Nonesense, trình biên dịch không có cách nào để biết, bạn không thể để trình biên dịch đó cho mã được tối ưu hóa (ngoài thuật toán).


3
"Nonesense, trình biên dịch không có cách nào để biết" JITer thực hiện cấu hình thời gian thực của các lệnh gọi hàm ..
Brian Gordon

3
.NET JIT đã được biết là tối ưu hóa vào thời gian chạy tốt hơn so với những gì có thể với phân tích tĩnh 2-pass tại thời gian biên dịch. Điều khó khăn để các lập trình viên "trường học cũ" nắm bắt là JIT, chứ không phải trình biên dịch, chịu trách nhiệm tạo mã gốc. JIT, không phải trình biên dịch, chịu trách nhiệm cho các phương thức nội tuyến.
Shaun Wilson

1
Tôi có thể biên dịch mã mà bạn gọi vào mà không cần mã nguồn của mình và JIT có thể chọn thực hiện cuộc gọi. Thông thường tôi sẽ đồng ý, nhưng tôi sẽ nói là một con người, bạn không còn có thể vượt qua các công cụ.
Shaun Wilson


0

Không, không có cấu trúc như vậy trong C #, nhưng trình biên dịch .NET JIT có thể quyết định thực hiện các cuộc gọi hàm nội tuyến theo thời gian JIT. Nhưng tôi thực sự không biết nếu nó thực sự làm tối ưu hóa như vậy.
(Tôi nghĩ rằng nó nên :-))


0

Trong trường hợp các hội đồng của bạn sẽ là ngen-ed, bạn có thể muốn xem TargetedPatchingOptOut. Điều này sẽ giúp ngen quyết định có phương pháp nội tuyến hay không.Tham chiếu MSDN

Nó vẫn chỉ là một gợi ý khai báo để tối ưu hóa, không phải là một mệnh lệnh bắt buộc.


Và dù sao đi nữa, JIT sẽ biết rõ hơn về việc có nên nội tuyến hay không. Rõ ràng là sẽ không giúp bạn nếu JIT không tối ưu hóa cuộc gọi, nhưng tại sao tôi phải lo lắng về chi phí tối thiểu của một chức năng chỉ được gọi một vài lần?
Voo

-7

Biểu thức Lambda là các hàm nội tuyến! Tôi nghĩ rằng, C # không có thuộc tính bổ sung như nội tuyến hoặc đại loại như thế!


9
Tôi không đánh giá thấp bạn, nhưng vui lòng đọc các câu hỏi và đảm bảo bạn hiểu chúng trước khi đăng câu trả lời ngẫu nhiên. vi.wikipedia.org/wiki/Inline_feft
Camilo Martin

1
Câu trả lời này không tệ như đã được đưa ra - OP không rõ ràng về 'chức năng nội tuyến' so với 'chức năng lambda', và không may MSDN gọi lambdas là "tuyên bố nội tuyến" hoặc "mã nội tuyến" . Tuy nhiên, theo câu trả lời của Konrad, có một thuộc tính để gợi ý cho trình biên dịch về nội tuyến một phương thức.
StuartLC

-7

C # không hỗ trợ các phương thức nội tuyến (hoặc hàm) theo cách các ngôn ngữ động như python làm. Tuy nhiên, các phương thức ẩn danh và lambdas có thể được sử dụng cho các mục đích tương tự bao gồm cả khi bạn cần truy cập vào một biến trong phương thức chứa như trong ví dụ dưới đây.

static void Main(string[] args)
{
    int a = 1;

    Action inline = () => a++;
    inline();
    //here a = 2
}

10
Điều này để làm gì với các chức năng nội tuyến? đây là phương pháp nặc danh.
Tomer W
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.