Khi nào nên sử dụng chức năng nội tuyến và khi nào không sử dụng nó?


184

Tôi biết rằng nội tuyến là một gợi ý hoặc yêu cầu trình biên dịch và nó được sử dụng để tránh các chi phí gọi hàm.

Vì vậy, trên cơ sở nào người ta có thể xác định liệu một chức năng là một ứng cử viên cho nội tuyến hay không? Trong trường hợp nào người ta nên tránh nội tuyến?


11
inlinedành cho người mới sử dụng C ++, người mới CFLAGSđến Gentoo: không, biên dịch với -O3 -funroll-loops -finline-functionssẽ không làm cho Pentium cũ của bạn bay lên;)
Gregory Pakosz

1
Một lý do không sử dụng nội tuyến là một số trình gỡ lỗi sẽ không cho phép bạn đặt điểm dừng hoặc bước vào một hàm nội tuyến.
Rob deFriesse


5
Bạn không nên xác định liệu một chức năng nên được nội tuyến hay không. Hãy để trình biên dịch làm điều đó; nó tốt hơn bạn (và có thể các chức năng nội tuyến có chọn lọc dựa trên môi trường của mỗi cuộc gọi).
David Thornley

@DavidThornley Đôi khi, ngay cả với cờ O3 được đặt, trình biên dịch không nội tuyến hàm nếu định nghĩa nằm trong tệp cpp. Vì vậy, quy tắc ngón tay cái mà tôi tuân theo là nội tuyến một lớp và cả các hàm đó mà không có bất kỳ vòng lặp nào.
talekeDskobeDa

Câu trả lời:


209

Tránh chi phí của một cuộc gọi chức năng chỉ là một nửa câu chuyện.

làm:

  • sử dụng inlinethay vì#define
  • các hàm rất nhỏ là ứng cử viên tốt cho inline: mã nhanh hơn và các tệp thực thi nhỏ hơn (nhiều cơ hội hơn trong bộ đệm mã)
  • chức năng này nhỏ được gọi rất thường xuyên

không:

  • các hàm lớn: dẫn đến các tệp thực thi lớn hơn, làm suy giảm đáng kể hiệu năng bất kể việc thực thi nhanh hơn có kết quả từ chi phí gọi
  • các hàm nội tuyến bị ràng buộc I / O
  • chức năng hiếm khi được sử dụng
  • hàm tạo và hàm hủy: ngay cả khi trống, trình biên dịch tạo mã cho chúng
  • phá vỡ tính tương thích nhị phân khi phát triển thư viện:
    • nội tuyến một chức năng hiện có
    • thay đổi một hàm nội tuyến hoặc làm cho một hàm nội tuyến không phải là nội tuyến: phiên bản trước của thư viện gọi việc thực hiện cũ

khi phát triển thư viện, để tạo một lớp mở rộng trong tương lai, bạn nên:

  • thêm hàm hủy ảo không nội tuyến ngay cả khi phần thân trống
  • làm cho tất cả các hàm tạo không nội tuyến
  • viết các triển khai phi tuyến của hàm tạo sao chép và toán tử gán trừ khi lớp không thể được sao chép theo giá trị

Hãy nhớ rằng inlinetừ khóa là một gợi ý cho trình biên dịch: trình biên dịch có thể quyết định không nội tuyến một chức năng và nó có thể quyết định các chức năng nội tuyến không được đánh dấu inlineở vị trí đầu tiên. Tôi thường tránh đánh dấu chức năng inline(có thể ngoài việc viết các hàm rất rất nhỏ).

Về hiệu năng, cách tiếp cận khôn ngoan là (như mọi khi) để lập hồ sơ cho ứng dụng, sau đó cuối cùng inlinelà một tập hợp các hàm đại diện cho nút cổ chai.

Người giới thiệu:


EDIT: Bjarne Stroustrup, Ngôn ngữ lập trình C ++:

Một chức năng có thể được xác định là inline. Ví dụ:

inline int fac(int n)
{
  return (n < 2) ? 1 : n * fac(n-1);
}

Trình inlinexác định là một gợi ý cho trình biên dịch rằng nó nên cố gắng tạo mã cho một cuộc gọi fac()nội tuyến thay vì đặt mã cho hàm một lần và sau đó gọi qua cơ chế gọi hàm thông thường. Một trình biên dịch thông minh có thể tạo ra hằng số 720cho một cuộc gọi fac(6). Khả năng các hàm nội tuyến đệ quy lẫn nhau, các hàm nội tuyến có lặp lại hay không phụ thuộc vào đầu vào, v.v., khiến cho không thể đảm bảo rằng mọi cuộc gọi của một inlinehàm thực sự được nội tuyến. Mức độ thông minh của trình biên dịch không thể được quy định, do đó, một trình biên dịch có thể tạo ra 720, một trình biên dịch khác 6 * fac(5)và một cuộc gọi khác không được thực hiện fac(6).

Để làm cho nội tuyến có thể xảy ra trong trường hợp không có các phương tiện biên dịch và liên kết thông minh khác thường, định nghĩa và không chỉ là khai báo của một hàm nội tuyến phải nằm trong phạm vi (§9.2). Một inlineđặc vụ không ảnh hưởng đến ngữ nghĩa của hàm. Cụ thể, một hàm nội tuyến vẫn có một địa chỉ duy nhất và staticcác biến (§7.1.2) của hàm nội tuyến cũng vậy.

EDIT2: ISO-IEC 14882-1998, 7.1.2 Bộ chỉ định chức năng

Một khai báo hàm (8.3.5, 9.3, 11.4) với một bộ inlinexác định khai báo một hàm nội tuyến. Trình xác định nội tuyến chỉ ra cho việc thực hiện rằng việc thay thế nội tuyến của thân hàm tại điểm gọi sẽ được ưu tiên hơn đối với cơ chế gọi hàm thông thường. Việc triển khai là không bắt buộc để thực hiện thay thế nội tuyến này tại điểm gọi; tuy nhiên, ngay cả khi thay thế nội tuyến này bị bỏ qua, các quy tắc khác cho các hàm nội tuyến được xác định bởi 7.1.2 vẫn sẽ được tuân thủ.


34
inlinelà nhiều hơn một gợi ý cho trình biên dịch. Nó thay đổi các quy tắc ngôn ngữ về nhiều định nghĩa. Ngoài ra, có dữ liệu tĩnh không phải là lý do gang để tránh nội tuyến. Việc thực hiện có nghĩa vụ phân bổ một đối tượng tĩnh cho mỗi hàm tĩnh cho dù hàm đó có được khai báo inlinehay không. Các lớp vẫn có thể mở rộng nếu chúng có các hàm tạo nội tuyến và các hàm hủy ảo. Và hàm hủy nẹp rỗng là một hàm ảo mà đôi khi nên để nội tuyến.
CB Bailey

2
Đó là một gợi ý theo nghĩa hàm không nhất thiết phải kết thúc nội tuyến (nhưng tiếng Anh không phải là tiếng mẹ đẻ của tôi). Về số liệu thống kê trong các hàm được đánh dấu inline, kết quả là hàm không được nội tuyến: bạn trả giá cho cuộc gọi và mỗi đơn vị dịch bao gồm và gọi hàm sẽ có bản sao mã và biến tĩnh riêng. Lý do không nội tuyến các hàm tạo và hàm hủy khi phát triển thư viện là khả năng tương thích nhị phân với các phiên bản tương lai của thư viện của bạn
Gregory Pakosz

14
Thật không chính xác khi gọi nó là "gợi ý cho trình biên dịch". Trong thực tế, các inlinehàm không có thể được nội tuyến nếu trình biên dịch cảm thấy thích nó. Và các inlinehàm sẽ không được nội tuyến nếu trình biên dịch quyết định không nội tuyến chúng. Như Charles Bailey đã nói, nó thay đổi các quy tắc ngôn ngữ. Thay vì nghĩ về nó như một gợi ý tối ưu hóa, sẽ chính xác hơn khi nghĩ về nó như một khái niệm hoàn toàn khác. Các inlinetừ khóa cho trình biên dịch cho phép nhiều định nghĩa, và không có gì khác. Tối ưu hóa "nội tuyến" có thể được áp dụng cho hầu hết mọi chức năng, cho dù nó có được đánh dấu hay không inline.
jalf

26
Chỉ là, khi Stroustrup viết "công cụ xác định nội tuyến là một gợi ý cho trình biên dịch", tôi ngạc nhiên tôi đã đổ lỗi cho việc trích dẫn anh ta. Dù sao, tôi đã dành đủ thời gian làm hết sức mình để trả lời câu trả lời này với càng nhiều tài liệu tham khảo càng tốt
Gregory Pakosz

2
@GregoryPakosz: Nhưng tất cả chúng ta không sử dụng inlineđể có được chức năng nội tuyến. Đôi khi chúng tôi muốn những lợi ích khác, như nhận được xung quanh ODR.
Cuộc đua nhẹ nhàng trong quỹ đạo

57

inlinecó rất ít để làm với tối ưu hóa. inlinelà một hướng dẫn cho trình biên dịch không tạo ra lỗi nếu hàm được định nghĩa xảy ra nhiều lần trong chương trình và một lời hứa rằng định nghĩa sẽ xảy ra trong mọi bản dịch mà nó được sử dụng và ở mọi nơi nó xuất hiện sẽ có cùng định nghĩa.

Đưa ra các quy tắc trên, inlinephù hợp với các chức năng ngắn mà cơ thể không cần thiết bao gồm cả phụ thuộc thêm vào những gì chỉ cần một tuyên bố. Mỗi lần gặp phải định nghĩa, nó phải được phân tích cú pháp và mã cho phần thân của nó có thể được tạo để nó bao hàm một số chi phí biên dịch trên một hàm chỉ được xác định một lần trong một tệp nguồn.

Một trình biên dịch có thể nội tuyến (tức là thay thế một cuộc gọi đến hàm bằng mã thực hiện hành động đó của chức năng đó) bất kỳ cuộc gọi chức năng nào mà nó chọn. Nó đã từng là trường hợp "rõ ràng" không thể nội tuyến một chức năng không được khai báo trong cùng một đơn vị dịch thuật với cuộc gọi nhưng với việc sử dụng tối ưu hóa thời gian liên kết ngày càng tăng ngay cả bây giờ điều này không đúng. Tương tự đúng là thực tế là các chức năng được đánh dấu inlinecó thể không được nội tuyến.


Tôi có cảm giác rằng đây là một sự trùng hợp vui vẻ hơn là một tính năng có chủ ý của C ++. Ý tưởng này rất giống với các biến toàn cầu 'tĩnh' từ C. Mặc dù đó là một câu trả lời rất thú vị. Tôi ước họ chỉ sử dụng một từ khóa như 'nội bộ' để biểu thị mối liên kết nội bộ.
Rehno Lindeque

+1. @Rehno: Tôi không thực sự chắc chắn những gì bạn đang nói. Liên kết có liên quan gì đến inlinetừ khóa? Và một sự trùng hợp hạnh phúc là gì?
jalf

@jalf: Đọc bình luận của tôi khi nhìn lại tôi nhận ra nó khá mơ hồ và không được suy nghĩ kỹ. Việc xác định cùng một chức năng trong nhiều tệp dẫn đến lỗi liên kết có thể được khắc phục bằng cách khai báo hàm 'tĩnh'. Tuy nhiên, 'nội tuyến' cho phép bạn thực hiện điều tương tự với những khác biệt tinh tế mà chúng không thực sự có được liên kết nội bộ như 'tĩnh'. Tôi nghi ngờ rằng đây thực sự là một sự trùng hợp ngẫu nhiên bởi vì những người thực hiện / thiết kế ngôn ngữ nhận ra rằng họ sẽ cần phải làm một cái gì đó đặc biệt với các hàm được khai báo trong các tệp tiêu đề và được chuyển sang 'nội tuyến'.
Rehno Lindeque

4
Không chắc chắn lý do tại sao bình luận của bạn nhận được nhiều phiếu bầu, vì hiệu suất là lý do chi phối để sử dụng nội tuyến.
gast128

10

Nói trình biên dịch để nội tuyến một chức năng là một tối ưu hóa, và quy tắc tối ưu hóa quan trọng nhất là tối ưu hóa sớm là gốc rễ của mọi tội lỗi. Luôn viết mã rõ ràng (sử dụng thuật toán hiệu quả), sau đó lập hồ sơ chương trình của bạn và chỉ tối ưu hóa các chức năng đang mất quá nhiều thời gian.

Nếu bạn thấy một chức năng cụ thể rất ngắn và đơn giản và nó được gọi là hàng chục ngàn lần trong một vòng lặp chặt chẽ bên trong, thì đó có thể là một ứng cử viên tốt.

Tuy nhiên, bạn có thể ngạc nhiên - nhiều trình biên dịch C ++ sẽ tự động nội tuyến các hàm nhỏ cho bạn - và họ cũng có thể bỏ qua yêu cầu của bạn để nội tuyến.


Thật vậy, tôi có nghi ngờ rằng một số trình biên dịch nhất định rất lén lút bỏ qua 'nội tuyến' và chỉ trả lời '__inline' hoặc '__force_inline'. Tôi cho rằng điều này là để ngăn chặn lạm dụng!
Rehno Lindeque

Không thường xuyên như vậy. nội tuyến chỉ là một gợi ý, nhưng đó là một gợi ý mà hầu hết các trình biên dịch thực hiện nghiêm túc. Bạn có thể đặt trình biên dịch để phát ra ngôn ngữ hợp ngữ cùng với mã đối tượng ( /FAcstrong Visual Studio, -strong GCC) để xem chính xác những gì nó làm. Theo kinh nghiệm của tôi, cả hai trình biên dịch này đều cân nhắc từ khóa nội tuyến khá nhiều.
Crashworks

1
Điều đó thật thú vị, vì theo kinh nghiệm của tôi, cả g ++ và VC đều không cân nhắc inlinetừ khóa. Đó là, nếu bạn thấy hàm được nội tuyến và xóa inlinespecifier khỏi nó, nó vẫn sẽ được nội tuyến. Nếu bạn có bất kỳ ví dụ cụ thể nào về điều ngược lại, hãy chia sẻ chúng!
Pavel Minaev

4
Làm thế nào để inlinetừ khóa cản trở "xóa mã"? Từ khóa trong "tối ưu hóa sớm" là sớm , không phải tối ưu hóa. Nói rằng bạn nên chủ động * tránh tối ưu hóa chỉ là rác rưởi. Điểm của trích dẫn đó là bạn nên tránh các tối ưu hóa có thể không cần thiết và có tác dụng phụ có hại đối với mã (chẳng hạn như làm cho nó ít được bảo trì hơn). Tôi không thấy inlinetừ khóa sẽ làm cho mã ít được bảo trì hơn hoặc làm thế nào nó có thể gây hại khi thêm nó vào một chức năng.
jalf

3
jalf, đôi khi nội tuyến một hàm sẽ làm cho mã của bạn chậm hơn, không nhanh hơn. Một ví dụ là khi hàm được gọi từ nhiều nơi khác nhau trong mã của bạn; nếu hàm không được nội tuyến, thì nó vẫn có thể nằm trong bộ đệm hướng dẫn khi nó được gọi từ một nơi khác và bộ dự đoán nhánh có thể đã được làm nóng. Có một số mẫu luôn cải thiện hiệu quả, vì vậy không bao giờ đau khi sử dụng chúng. Nội tuyến không phải là một trong số họ. Nó thường không có ảnh hưởng đến hiệu suất, đôi khi nó giúp ích, và đôi khi làm tổn thương. Tôi đứng đằng sau lời khuyên của tôi: hồ sơ đầu tiên, sau đó nội tuyến.
dmazzoni

5

Cách tốt nhất để tìm hiểu là lập hồ sơ chương trình của bạn và đánh dấu các chức năng nhỏ được gọi nhiều lần và ghi thông qua các chu kỳ CPU như inline . Từ khóa ở đây là "nhỏ" - một khi chi phí gọi hàm không đáng kể so với thời gian sử dụng hàm, việc đặt nội tuyến chúng là vô nghĩa.

Cách sử dụng khác mà tôi đề xuất là nếu bạn có các hàm nhỏ được gọi trong mã quan trọng về hiệu năng thường đủ để khiến bộ nhớ cache bị mất liên quan, thì có lẽ bạn cũng nên nội tuyến chúng. Một lần nữa, đó là thứ mà trình hồ sơ có thể nói với bạn.


4

Tối ưu hóa sớm là gốc rễ của mọi tội lỗi!

Theo nguyên tắc thông thường, tôi thường chỉ nội tuyến "getters" và "setters". Khi mã đang hoạt động và ổn định, cấu hình có thể hiển thị các hàm nào có thể được hưởng lợi từ nội tuyến.

Mặt khác, hầu hết các trình biên dịch hiện đại đều có các thuật toán tối ưu hóa khá tốt và sẽ nội tuyến những gì bạn nên đưa ra cho bạn.

Reasuming - viết các hàm one-liner nội tuyến, và lo lắng về những người khác sau này.


2

Hàm nội tuyến có thể cải thiện hiệu suất mã của bạn bằng cách loại bỏ nhu cầu đẩy các đối số vào ngăn xếp. nếu hàm được đề cập nằm trong phần quan trọng của mã của bạn, bạn nên đưa ra quyết định không phải là nội tuyến trong phần tối ưu hóa của dự án,

bạn có thể đọc thêm về nội tuyến trong fa ++ c ++


1

Tôi thường sử dụng các hàm nội tuyến không phải là một tối ưu hóa nhưng để làm cho mã dễ đọc hơn. Đôi khi bản thân mã ngắn hơn và dễ hiểu hơn bình luận, tên mô tả, v.v. Ví dụ:

void IncreaseCount() { freeInstancesCnt++; }

Người đọc ngay lập tức biết các ngữ nghĩa đầy đủ của mã.


0

Tôi thường tuân theo quy tắc ngón tay cái trong đó tôi tạo một hàm với 3-4 câu lệnh đơn giản là nội tuyến. Nhưng thật tốt khi nhớ rằng nó chỉ là một gợi ý cho trình biên dịch. Cuộc gọi cuối cùng để làm cho nó nội tuyến hay không chỉ được thực hiện bởi trình biên dịch. Nếu có nhiều hơn những câu lệnh này, tôi sẽ không khai báo nội tuyến vì với trình biên dịch ngu ngốc, nó có thể dẫn đến sự phình to mã.


0

Cách tốt nhất sẽ là kiểm tra và so sánh các hướng dẫn được tạo cho nội tuyến và không nội tuyến. Tuy nhiên, nó luôn luôn an toàn để bỏ qua inline. Sử dụng inlinecó thể dẫn đến rắc rối bạn không muốn.


0

Khi quyết định có nên sử dụng nội tuyến hay không, tôi thường ghi nhớ ý tưởng sau: Trên máy tính hiện đại độ trễ bộ nhớ có thể là một nút cổ chai lớn hơn so với tính toán thô. Các hàm nội tuyến được gọi thường được biết là tăng kích thước thực thi. Hơn nữa, một chức năng như vậy có thể được lưu trữ trong bộ đệm mã của CPU, điều này sẽ làm giảm số lần bỏ lỡ bộ đệm khi mã đó cần được truy cập.

Do đó, bạn phải tự quyết định: Liệu nội tuyến tăng hay giảm kích thước của mã máy được tạo? Làm thế nào có khả năng việc gọi hàm sẽ gây ra lỗi nhớ cache? Nếu nó được tiêu trong suốt mã, thì tôi sẽ nói khả năng là cao. Nếu nó bị giới hạn trong một vòng lặp chặt chẽ thì khả năng rất thấp.

Tôi thường sử dụng nội tuyến trong các trường hợp tôi liệt kê dưới đây. Tuy nhiên, nơi bạn thực sự quan tâm đến hiệu suất, hồ sơ là điều cần thiết. Hơn nữa, bạn có thể muốn kiểm tra xem trình biên dịch có thực sự đưa ra gợi ý hay không.

  • Các thói quen ngắn được gọi trong một vòng lặp chặt chẽ.
  • Các hàm truy cập rất cơ bản (get / set) và các hàm bao bọc.
  • Mã mẫu trong các tệp tiêu đề không may tự động có được gợi ý nội tuyến.
  • Mã ngắn được sử dụng như một macro. (Ví dụ: tối thiểu () / tối đa ())
  • Thói quen toán học ngắn.

0

Ngoài ra, một phương pháp nội tuyến có tác dụng phụ nghiêm trọng khi duy trì các dự án lớn. Khi mã nội tuyến được thay đổi, tất cả các tệp sử dụng nó sẽ được trình biên dịch tự động xây dựng lại (nó là một trình biên dịch tốt). Điều này có thể lãng phí rất nhiều thời gian phát triển của bạn.

Khi một inlinephương thức được chuyển đến một tệp nguồn và không được thêm vào nữa, toàn bộ dự án phải được xây dựng lại (ít nhất đây là kinh nghiệm của tôi). Và cũng khi phương thức được chuyển đổi thành nội tuyến.


1
Đó là một vấn đề khác. Bạn nhận được vấn đề xây dựng lại cho mã được đặt trong một tệp tiêu đề. Cho dù nó có được đánh dấu inlinehay không không quan trọng (ngoài việc không có inlinetừ khóa, bạn sẽ gặp lỗi liên kết - nhưng inlinetừ khóa không phải là vấn đề gây ra việc xây dựng lại quá mức.
jalf

Tuy nhiên, thay đổi một phương thức nội tuyến sẽ gây ra các bản dựng quá mức so với thay đổi một phương thức phi nội tuyến trong tệp nguồn.
Thomas Matthews

0

Người ta chỉ nên sử dụng vòng loại chức năng nội tuyến khi mã chức năng nhỏ. Nếu các chức năng lớn hơn, bạn nên ưu tiên các chức năng thông thường vì việc tiết kiệm trong không gian bộ nhớ đáng để hy sinh tương đối nhỏ trong tốc độ thực thi.


0

Khi bạn nghĩ rằng mã của bạn đủ nhỏ để được sử dụng làm nội tuyến và hãy nhớ chức năng nội tuyến sao chép mã của bạn và dán, đó là chức năng được gọi để có thể đủ tốt để tăng thời gian thực hiện của bạn nhưng cũng tăng mức tiêu thụ bộ nhớ. Bạn không thể sử dụng chức năng nội tuyến khi bạn đang sử dụng một vòng lặp / biến tĩnh / đệ quy / switch / goto / Hàm ảo. Ảo có nghĩa là đợi cho đến khi thời gian chạy và phương tiện nội tuyến trong quá trình biên dịch để chúng không thể được sử dụng đồng thời.


-2

Tôi đã đọc một số câu trả lời và thấy rằng có một số thứ còn thiếu.

Quy tắc tôi sử dụng là không sử dụng nội tuyến, trừ khi tôi muốn nó là nội tuyến. Có vẻ ngớ ngẩn, bây giờ giải thích.

Trình biên dịch đủ thông minh và các hàm ngắn luôn tạo nội tuyến. Và không bao giờ làm cho chức năng dài là nội tuyến, trừ khi lập trình viên nói để làm điều đó.

Tôi biết rằng nội tuyến là một gợi ý hoặc yêu cầu trình biên dịch

Trên thực tế inlinelà một thứ tự cho trình biên dịch, nó không có lựa chọn nào và sau khi inlinetừ khóa tạo ra tất cả mã nội tuyến. Vì vậy, bạn không bao giờ có thể sử dụng inlinetừ khóa và trình biên dịch sẽ thiết kế mã ngắn nhất.

Vậy khi nào nên sử dụng inline?

Để sử dụng nếu bạn muốn có một số mã nội tuyến. Tôi chỉ biết một ví dụ, bởi vì tôi chỉ sử dụng nó trong một tình huống. Đó là xác thực người dùng.

Ví dụ tôi có chức năng này:

inline bool ValidUser(const std::string& username, const std::string& password)
{
    //here it is quite long function
}

Cho dù chức năng này lớn đến mức nào tôi cũng muốn có nó dưới dạng nội tuyến vì nó làm cho phần mềm của tôi khó bị bẻ khóa hơn.


2
nội tuyến vẫn là một gợi ý. Trình biên dịch có thể thất bại trong dòng nếu nó thấy rằng chức năng của bạn quá cồng kềnh.
ItPete

Một người nói nội tuyến là một mệnh lệnh ... người kia nói đó là một gợi ý Ai đó sẽ chứng minh tuyên bố của mình để chúng tôi có thể xác định xem cái nào là đúng?

@ user2918461 tôi hỗ trợ câu lệnh nội tuyến chỉ là một gợi ý. Điều này đã được hỗ trợ bởi nhiều trang web và sách
WARhead
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.