2011-08-17 14 views
9

Như trong, nói tập tin tiêu đề của tôi là:Làm các hàm trình biên dịch C++ hiện đại có được gọi chính xác một lần không?

class A 
{ 
    void Complicated(); 
} 

Và tập tin nguồn của tôi

void A::Complicated() 
{ 
    ...really long function... 
} 

Tôi có thể chia tập tin nguồn thành

void DoInitialStuff(pass necessary vars by ref or value) 
{ 
    ... 
} 
void HandleCaseA(pass necessary vars by ref or value) 
{ 
    ... 
} 
void HandleCaseB(pass necessary vars by ref or value) 
{ 
    ... 
} 
void FinishUp(pass necessary vars by ref or value) 
{ 
    ... 
} 
void A::Complicated() 
{ 
    ... 
    DoInitialStuff(...); 
    switch ... 
     HandleCaseA(...) 
     HandleCaseB(...) 
    ... 
    FinishUp(...) 
} 

Hoàn toàn để có thể đọc và không có bất kỳ sợ hãi tác động về mặt hiệu suất?

+1

Có thể, có thể không. Trình lập trình trình biên dịch có thể là đặt cược tốt nhất của bạn, dựa vào trình biên dịch bạn đang sử dụng. – DumbCoder

+0

Tôi tự hỏi điểm của nội tuyến sẽ là gì ... các cuộc gọi khá rẻ. –

+2

Không điều nào trong số này xảy ra trong vòng lặp? Chính xác bao nhiêu thời gian bạn có hy vọng để đạt được từ tránh các chi phí của một vài cuộc gọi chức năng? Một nano giây? – UncleBens

Trả lời

10

Bạn nên đánh dấu các chức năng static để trình biên dịch biết chúng là cục bộ đối với đơn vị dịch đó.

Nếu không có static trình biên dịch không thể giả định (chặn LTO/WPA) mà chức năng chỉ được gọi một lần, vì vậy ít có khả năng nội tuyến nó.

Trình diễn bằng trang LLVM Try Out.

Điều đó nói rằng, mã để dễ đọc trước tiên, tối ưu hóa vi mô (và tinh chỉnh như vậy tối ưu hóa vi mô) chỉ nên đến sau các biện pháp hiệu suất.


Ví dụ:

#include <cstdio> 

static void foo(int i) { 
    int m = i % 3; 
    printf("%d %d", i, m); 
} 

int main(int argc, char* argv[]) { 
    for (int i = 0; i != argc; ++i) { 
    foo(i); 
    } 
} 

Tạo với static:

; ModuleID = '/tmp/webcompile/_27689_0.bc' 
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64" 
target triple = "x86_64-unknown-linux-gnu" 

@.str = private constant [6 x i8] c"%d %d\00"  ; <[6 x i8]*> [#uses=1] 

define i32 @main(i32 %argc, i8** nocapture %argv) nounwind { 
entry: 
    %cmp4 = icmp eq i32 %argc, 0     ; <i1> [#uses=1] 
    br i1 %cmp4, label %for.end, label %for.body 

for.body:           ; preds = %for.body, %entry 
    %0 = phi i32 [ %inc, %for.body ], [ 0, %entry ] ; <i32> [#uses=3] 
    %rem.i = srem i32 %0, 3       ; <i32> [#uses=1] 
    %call.i = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([6 x i8]* @.str, i64 0, i64 0), i32 %0, i32 %rem.i) nounwind ; <i32> [#uses=0] 
    %inc = add nsw i32 %0, 1      ; <i32> [#uses=2] 
    %exitcond = icmp eq i32 %inc, %argc    ; <i1> [#uses=1] 
    br i1 %exitcond, label %for.end, label %for.body 

for.end:           ; preds = %for.body, %entry 
    ret i32 0 
} 

declare i32 @printf(i8* nocapture, ...) nounwind 

Without static:

; ModuleID = '/tmp/webcompile/_27859_0.bc' 
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64" 
target triple = "x86_64-unknown-linux-gnu" 

@.str = private constant [6 x i8] c"%d %d\00"  ; <[6 x i8]*> [#uses=1] 

define void @foo(int)(i32 %i) nounwind { 
entry: 
    %rem = srem i32 %i, 3       ; <i32> [#uses=1] 
    %call = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([6 x i8]* @.str, i64 0, i64 0), i32 %i, i32 %rem) ; <i32> [#uses=0] 
    ret void 
} 

declare i32 @printf(i8* nocapture, ...) nounwind 

define i32 @main(i32 %argc, i8** nocapture %argv) nounwind { 
entry: 
    %cmp4 = icmp eq i32 %argc, 0     ; <i1> [#uses=1] 
    br i1 %cmp4, label %for.end, label %for.body 

for.body:           ; preds = %for.body, %entry 
    %0 = phi i32 [ %inc, %for.body ], [ 0, %entry ] ; <i32> [#uses=3] 
    %rem.i = srem i32 %0, 3       ; <i32> [#uses=1] 
    %call.i = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([6 x i8]* @.str, i64 0, i64 0), i32 %0, i32 %rem.i) nounwind ; <i32> [#uses=0] 
    %inc = add nsw i32 %0, 1      ; <i32> [#uses=2] 
    %exitcond = icmp eq i32 %inc, %argc    ; <i1> [#uses=1] 
    br i1 %exitcond, label %for.end, label %for.body 

for.end:           ; preds = %for.body, %entry 
    ret i32 0 
} 
+3

Tôi nghĩ bạn nên đặt chúng trong một không gian tên không tên, theo "nguyên tắc". – GManNickG

+0

Clang đã trình bày nó trong cả hai phiên bản tĩnh và không tĩnh trong ví dụ của bạn ... Tôi không nghĩ rằng có sự khác biệt đáng kể trong việc có hay không nó inline cuộc gọi. Có sự khác biệt trong việc mã của hàm có được phát ra hay không. –

+1

@GMan: Tôi ủng hộ 'tĩnh' bởi vì tôi thấy nó dễ đọc hơn đối với một con người, một không gian tên vô danh yêu cầu con người phải nhớ anh ta trong một, và nó không rõ ràng. 'static' không yêu cầu" ngữ cảnh "này. –

7

Phụ thuộc vào việc răng cưa (con trỏ đến hàm đó) và độ dài chức năng (một hàm lớn được xếp trong nhánh có thể đẩy nhánh khác ra khỏi bộ nhớ cache, do đó làm tổn thương hiệu năng).

Hãy để lo lắng biên dịch về điều đó, bạn lo lắng về mã của bạn :)

+1

Trình biên dịch có thể tự phân chia các chức năng dài hơn thành các hàm nhỏ hơn không? – Cookie

+0

OP hỏi về sự so sánh giữa việc viết mã như trên và viết tất cả các mã theo cách thủ công. Vì vậy, tôi đoán anh/cô ấy muốn một câu trả lời giải thích cho dù phá vỡ một chức năng lên sẽ bao giờ có một nhược điểm. –

+0

Nghe có vẻ ít có khả năng hơn, lợi ích gì? Tôi nghĩ rằng bạn có nhiều khả năng tìm thấy trình biên dịch sao chép khối của bạn hơn và hơn một sau khi khác :) – Blindy

6

Một chức năng phức tạp có khả năng tốc độ của nó đã chi phối bởi các hoạt động trong phạm vi chức năng; các chi phí của một cuộc gọi chức năng sẽ không được chú ý ngay cả khi nó không phải là inlined.

Bạn không có nhiều quyền kiểm soát đối với nội tuyến của hàm, cách tốt nhất để biết là thử và tìm hiểu.

Trình tối ưu hóa của trình biên dịch có thể hiệu quả hơn với các đoạn mã ngắn hơn, vì vậy bạn có thể thấy nó nhanh hơn ngay cả khi nó không được gạch chân.

+0

Và còn một chức năng đơn giản thì sao? –

+0

@Oli, các hàm đơn giản có nhiều khả năng được tự động được gạch chân hơn. –

+0

Bạn không có nhiều quyền kiểm soát? Đó là lý do tại sao có từ khóa "nội tuyến" ... Các hàm đơn giản không thể được gạch chân trừ khi chúng được khai báo là tĩnh. Hoặc cụ thể hơn là một phiên bản không có nội tuyến phải có sẵn cho người gọi bên ngoài. Ngoài ra, nếu một chức năng lớn được chia thành 15 chức năng nhỏ (như OP muốn làm), thì sẽ tốn gấp 15 lần chi phí cuộc gọi. – phkahler

0

Nếu bạn chia mã thành các nhóm logic, trình biên dịch sẽ làm những gì mà nó cho là tốt nhất: Nếu nó ngắn và dễ dàng, trình biên dịch phải đặt nội tuyến và kết quả giống nhau. Tuy nhiên, nếu mã phức tạp, thực hiện cuộc gọi hàm bổ sung có thể thực sự là nhanh hơn hơn là thực hiện tất cả công việc được nêu, do đó, bạn cũng để trình biên dịch tùy chọn thực hiện điều đó. Trên hết, mã phân tách hợp lý có thể dễ dàng hơn cho người bảo trì để dò tìm và tránh các lỗi trong tương lai.

+0

Nếu hàm này chỉ được gọi một lần, tôi không chắc chắn rằng chúng ta sẽ thấy tốc độ tăng lên do không gạch chân mã ... * trừ khi * bạn thực sự muốn bỏ qua cuộc gọi hàm và có chức năng được chèn vào, do đó sẽ xóa bộ nhớ cache của bạn. Là nó hiện tại? –

+0

Suy nghĩ của tôi là bằng cách chia nhỏ mã thành các khối chức năng logic, trình biên dịch có thể sử dụng thanh ghi thông minh hơn, trong số các khả năng khác mà tôi thậm chí không thể tưởng tượng được. –

0

tôi đề nghị bạn tạo ra một lớp helper để phá vỡ complica của bạn chức năng ted vào các cuộc gọi phương thức, giống như bạn đã đề xuất, nhưng không có nhiệm vụ dài, nhàm chán và không thể đọc được khi chuyển các đối số cho mỗi và mọi chức năng nhỏ hơn. Vượt qua các đối số này chỉ một lần bằng cách biến chúng thành các biến thành viên của lớp trợ giúp.

Đừng tập trung vào tối ưu hóa tại thời điểm này, hãy đảm bảo mã của bạn có thể đọc được và bạn sẽ ổn định 99% thời gian.

Các vấn đề liên quan