2009-02-24 8 views
15

Một số người thích sử dụng inline từ khóa trong C và đặt các chức năng lớn trong các tiêu đề tiêu đề. Khi nào bạn xem điều này là không hiệu quả? Tôi xem xét nó đôi khi thậm chí gây phiền nhiễu, bởi vì nó là bất thường.Khi nào "nội tuyến" không hiệu quả? (trong C)

Nguyên tắc của tôi là inline nên được sử dụng cho các chức năng nhỏ được truy cập rất thường xuyên hoặc để kiểm tra loại thực. Nhưng dù sao, hương vị của tôi hướng dẫn tôi, nhưng tôi không chắc chắn làm thế nào để giải thích tốt nhất lý do tại sao inline không phải là rất hữu ích cho các chức năng lớn.

Trong this question mọi người cho rằng trình biên dịch có thể thực hiện công việc tốt hơn khi đoán đúng điều cần làm. Đó cũng là giả định của tôi. Khi tôi cố gắng sử dụng đối số này, mọi người trả lời nó không hoạt động với các chức năng đến từ các đối tượng khác nhau. Vâng, tôi không biết (ví dụ, sử dụng GCC).

Cảm ơn câu trả lời của bạn!

+0

Tôi nghi ngờ rằng giống như "đăng ký" trở nên lỗi thời vào cuối những năm 1980, "nội tuyến" đã lỗi thời trong một số năm. –

+0

\ o /, chết "nội dòng"! – elmarco

+0

Inline chắc chắn không lỗi thời. Bạn đã bao giờ đọc mã nguồn cho một hạt nhân hiện đại chưa? Nội tuyến là không thích hợp với những người viết các ứng dụng, nhưng những người đó không nên sử dụng C ngày nay, và nó cũng có liên quan đến việc lập trình hệ thống như trước đây. – nosatalian

Trả lời

26

inline thực hiện hai điều:

  1. mang đến cho bạn một miễn từ "một quy tắc định nghĩa" (xem dưới đây). luôn áp dụng.
  2. Cung cấp cho trình biên dịch một gợi ý để tránh cuộc gọi hàm. Trình biên dịch là miễn phí để bỏ qua điều này.

# 1 Có thể rất hữu ích (ví dụ: đặt định nghĩa trong tiêu đề nếu ngắn) ngay cả khi # 2 bị tắt.

Trong các trình biên dịch thực hành thường làm một công việc tốt hơn để làm việc ra những gì để nội tuyến chính mình (đặc biệt là nếu tối ưu hóa hướng dẫn hồ sơ có sẵn).


[EDIT: Tài liệu tham khảo đầy đủ và văn bản có liên quan]

Hai điểm trên cả hai theo từ tiêu chuẩn ISO/ANSI (ISO/IEC 9899: 1999 (E), thường được gọi là "C99") .

Trong §6.9 "Định nghĩa bên ngoài", đoạn 5:

Một định nghĩa bên ngoài là một tuyên bố bên ngoài mà còn là một định nghĩa của một hàm (trừ một định nghĩa inline) hoặc một đối tượng. Nếu một định danh được khai báo với liên kết bên ngoài được sử dụng trong một biểu thức (không phải là một phần của toán hạng của toán tử sizeof có kết quả là hằng số nguyên), một nơi nào đó trong toàn bộ chương trình sẽ có chính xác một định nghĩa bên ngoài cho định danh; nếu không, sẽ không có nhiều hơn một.

Trong khi định nghĩa tương đương trong C++ được khai thác có tên là Quy tắc một định nghĩa (ODR), nó phục vụ cùng một mục đích. Các phần tử bên ngoài (tức là không "tĩnh" và do đó cục bộ cho một Đơn vị dịch - thường là một tệp nguồn duy nhất) chỉ có thể được xác định khi chỉ trừ khi nó là một hàm nội tuyến.

Trong §6.7.4, "Các chỉ số chức năng", từ khóa nội tuyến được xác định:

Thực hiện chức năng một chức năng nội tuyến cho phép gọi hàm là nhanh nhất có thể. [118] Mức độ đề xuất như vậy có hiệu lực là được xác định thực hiện.

Và chú thích (không quy chuẩn), nhưng cung cấp làm rõ:

Bằng cách sử dụng, ví dụ, một thay thế cho cơ chế chức năng cuộc gọi thông thường, chẳng hạn như ‘‘inline thay’’. Thay thế nội tuyến không phải là thay thế văn bản, cũng không tạo ra một hàm mới. Do đó, ví dụ, việc mở rộng một macro được sử dụng bên trong phần thân của hàm sử dụng định nghĩa mà nó có tại điểm mà phần tử hàm xuất hiện, và không phải là nơi hàm được gọi; và số nhận dạng tham chiếu đến các khai báo trong phạm vi nơi cơ thể xảy ra. Tương tự như vậy, hàm có một địa chỉ duy nhất, bất kể số định nghĩa nội tuyến xảy ra ngoài định nghĩa bên ngoài.

Tóm tắt: hầu hết người dùng C và C++ mong đợi từ nội tuyến không phải là những gì họ nhận được. Mục đích chính của nó rõ ràng, để tránh các cuộc gọi chức năng trên không, là hoàn toàn tùy chọn. Nhưng để cho phép biên dịch riêng biệt, cần phải có sự thư giãn của định nghĩa đơn.

(Tất cả nhấn mạnh trong các trích dẫn từ tiêu chuẩn.)


EDIT 2: Một vài lưu ý:

  • Có những hạn chế khác nhau về chức năng inline bên ngoài. Bạn không thể có một biến tĩnh trong hàm, và bạn không thể tham chiếu các đối tượng/hàm phạm vi TU tĩnh.
  • Chỉ cần nhìn thấy điều này trên VC++ "whole program optimisation", là một ví dụ về trình biên dịch thực hiện nội tuyến riêng của nó, chứ không phải là tác giả.
+0

Tôi thậm chí không nghĩ đến # 1, nhưng bạn nói đúng - rất hữu ích! Cảm ơn vì tiền hỗ trợ. – Mike

+0

-1: # 1 không đúng - tại leat cho gcc, bạn phải thêm 'static' nữa! – Christoph

+1

@Christoph: đó chỉ là gcc. Chỉ cần kiểm tra trong tài liệu chuẩn C99. – Richard

1
  1. inline chỉ hoạt động như một gợi ý.
  2. Chỉ được thêm gần đây. Vì vậy, chỉ làm việc với các trình biên dịch tuân thủ tiêu chuẩn mới nhất.
+0

Đã thêm rất gần đây?Tôi khá chắc chắn nội tuyến đã được khoảng ít nhất một chục năm (kể từ khi tôi bắt đầu mã hóa trong C) –

+0

nội tuyến đã được trong C + + trong một thời gian dài. Trong C nó đã được trong tiêu chuẩn kể từ C99 (nhưng không quá nhiều trình biên dịch hỗ trợ đầy đủ C99), nhưng đã được trong trình biên dịch khác nhau như là một phần mở rộng cho một thời gian. Vì vậy, đối với các chương trình C, sử dụng nội tuyến có thể là một chút nhức đầu về tính di động. –

+0

10yrs. Tuy nhiên, có được MS để thực hiện một trình biên dịch C phong nha chỉ. Dù sao, đó là gần đây trong C-thời gian;) Thêm vào C99. Xem: http://en.wikipedia.org/wiki/C99 – dirkgently

4

Nội tuyến không hiệu quả khi bạn sử dụng con trỏ để hoạt động.

+0

chắc chắn, đó không phải là trường hợp trong dự án của tôi. cảm ơn – elmarco

+0

+1 quan sát thú vị.! –

5

Điều quan trọng về tuyên bố nội tuyến là nó không nhất thiết phải làm bất cứ điều gì. Một trình biên dịch là miễn phí để quyết định, trong nhiều trường hợp, để nội tuyến một hàm không khai báo như vậy, và để liên kết các hàm được khai báo nội tuyến.

+0

ngay cả khi hàm nội tuyến bạn liên kết với từ một đối tượng .o khác? – elmarco

+0

công cụ sửa đổi nội tuyến chỉ là gợi ý. Nếu trình biên dịch chọn, nó có thể quyết định rằng một hàm không thể được gạch chân, nhưng sau đó trình liên kết có thể quyết định viết lại lời gọi hàm dưới dạng nội dòng. Không chỉ đảm bảo hành vi với nội tuyến. – SingleNegationElimination

2

Đúng vậy. Sử dụng nội tuyến cho các chức năng lớn làm tăng thời gian biên dịch và mang lại hiệu suất bổ sung cho ứng dụng. Các hàm nội tuyến được sử dụng để báo cho trình biên dịch biết rằng một hàm sẽ được đưa vào mà không có một cuộc gọi, và như vậy nên mã nhỏ lặp đi lặp lại nhiều lần. Nói cách khác: đối với các chức năng lớn, chi phí thực hiện cuộc gọi so với chi phí thực hiện chức năng của riêng là không đáng kể.

+0

Như đã đề cập trước đó, nội tuyến được sử dụng để _suggest_ đến trình biên dịch mà chức năng nên được bao gồm mà không có một cuộc gọi. Không có gì đảm bảo rằng nó thực sự sẽ làm như vậy. – Peter

2

Có thể sử dụng nội tuyến cho các chức năng nhỏ và thường được sử dụng như phương thức getter hoặc setter. Đối với các hàm lớn, không nên sử dụng nội tuyến vì nó làm tăng kích thước exe. Cũng cho các hàm đệ quy, ngay cả khi bạn tạo nội tuyến, trình biên dịch sẽ bỏ qua nó.

2

Tôi chủ yếu sử dụng các hàm nội dòng làm macro loại an toàn. Đã có nói về việc bổ sung hỗ trợ tối ưu hóa thời gian liên kết cho GCC trong một thời gian, đặc biệt kể từ khi LLVM xuất hiện. Tôi không biết bao nhiêu nó thực sự đã được thực hiện được nêu ra, mặc dù.

3

Nội tuyến có hiệu quả trong một trường hợp: khi bạn gặp vấn đề về hiệu suất, hãy chạy trình thu thập dữ liệu của bạn với dữ liệu thực và tìm thấy chức năng gọi điện cho một số chức năng nhỏ.

Ngoài ra, tôi không thể tưởng tượng lý do bạn sử dụng nó.

+0

Dang. Tôi đã lừa dối câu trả lời của bạn. Vâng, thực tế đó một mình rõ ràng cho thấy bạn đã truyền đạt cái nhìn sâu sắc tuyệt vời, do đó, upvote. :-) –

+0

Tôi muốn nói đôi khi bạn không cần phải chạy profiler để biết rằng chi phí là đáng kể. Ví dụ, nó khá rõ ràng với tôi rằng một hàm tăng nguyên tử phải là nội dòng, và nó cũng sẽ giảm kích thước mã. –

2

Cá nhân tôi không nghĩ rằng bạn nên bao giờ nội tuyến, trừ khi bạn lần đầu tiên chạy profiler trên mã của mình và đã chứng minh rằng có một nút cổ chai đáng kể về thói quen đó có thể được giảm bớt một phần bằng nội tuyến.

Đây là trường hợp khác của Knuth Tối ưu hóa sớm được cảnh báo.

5

Ví dụ minh họa lợi ích của nội tuyến. sinCos.h:

int16 sinLUT[ TWO_PI ]; 

static inline int16_t cos_LUT(int16_t x) { 
    return sin_LUT(x + PI_OVER_TWO) 
} 

static inline int16_t sin_LUT(int16_t x) { 
    return sinLUT[(uint16_t)x]; 
} 

Khi làm một số số nặng crunching và bạn muốn tránh lãng phí chu kỳ trên máy tính sin/cos bạn thay thế sin/cos với một LUT.

Khi bạn biên dịch mà không inline trình biên dịch sẽ không tối ưu hóa vòng lặp và Asm đầu ra sẽ hiển thị một cái gì đó dọc theo dòng:

;*----------------------------------------------------------------------------* 
;* SOFTWARE PIPELINE INFORMATION 
;*  Disqualified loop: Loop contains a call 
;*----------------------------------------------------------------------------* 

Khi bạn biên dịch với trình biên dịch nội tuyến có kiến ​​thức về những gì xảy ra trong vòng lặp và sẽ tối ưu hóa bởi vì nó biết chính xác những gì đang xảy ra.

Đầu ra .asm sẽ có vòng lặp "pipelined" tối ưu (nghĩa là nó sẽ cố gắng sử dụng đầy đủ tất cả ALU của bộ xử lý và cố gắng giữ cho toàn bộ bộ xử lý không có NOPS).


Trong trường hợp cụ thể này, tôi có thể tăng hiệu suất của mình lên khoảng 2X hoặc 4X giúp tôi đạt được những gì tôi cần cho thời hạn thực của mình.


p.s. Tôi đã làm việc trên một bộ xử lý điểm cố định ... và bất kỳ hoạt động điểm nổi như sin/cos đã giết chết hiệu suất của tôi.

5

Một lý do khác khiến bạn không nên sử dụng nội tuyến cho các chức năng lớn, là trong trường hợp thư viện. Mỗi khi bạn thay đổi các hàm nội tuyến, bạn có thể mất khả năng tương thích ABI vì ứng dụng được biên dịch dựa trên tiêu đề cũ hơn, vẫn đã sắp xếp phiên bản cũ của hàm. Nếu các hàm nội tuyến được sử dụng như một macro kiểu an toàn, rất có thể là chức năng không bao giờ cần phải được thay đổi trong vòng đời của thư viện. Nhưng đối với các chức năng lớn thì khó đảm bảo.

Tất nhiên, đối số này chỉ áp dụng nếu hàm này là một phần của API công khai của bạn.

1

Các chức năng nội tuyến phải dài khoảng 10 dòng hoặc ít hơn, cho hay lấy, tùy thuộc vào trình biên dịch bạn chọn.

Bạn có thể thông báo cho trình biên dịch biết rằng bạn muốn nội dung nào đó được phác thảo .. trình biên dịch của bạn sẽ làm như vậy. Không có tùy chọn-force-inline mà tôi biết trình biên dịch nào không thể bỏ qua.Đó là lý do tại sao bạn nên nhìn vào đầu ra của bộ assembler và xem trình biên dịch của bạn có thực sự là đã làm nội dòng hàm, nếu không, tại sao không? Nhiều trình biên dịch chỉ âm thầm nói 'vít bạn!' về mặt đó.

vì vậy nếu:

inline tĩnh unsigned int foo (const char * thanh)

.. không cải thiện những điều trên int foo tĩnh() thời gian của mình để xem lại tối ưu hóa của bạn (và vòng khả năng) hoặc tranh luận với trình biên dịch của bạn. Hãy đặc biệt quan tâm để tranh luận với trình biên dịch của bạn trước tiên, không phải những người phát triển nó .. hoặc chỉ trong cửa hàng của bạn cho rất nhiều đọc khó chịu khi bạn mở hộp thư của bạn vào ngày hôm sau.

Trong khi đó, khi làm một cái gì đó (hoặc cố gắng làm một cái gì đó) nội tuyến, làm như vậy thực sự biện minh cho sưng lên? Bạn có thực sự muốn chức năng đó được mở rộng mỗi lần gọi là không? Là nhảy quá tốn kém ?, trình biên dịch của bạn thường là chính xác 9/10 lần, kiểm tra đầu ra trung gian (hoặc asm bãi).