* Gọi * = (hoặc * = gọi *) chậm hơn so với viết các hàm riêng biệt (đối với thư viện toán học)? [đóng cửa]


15

Tôi có một số lớp vectơ trong đó các hàm số học trông như thế này:

template<typename T, typename U>
auto operator*(const Vector3<T>& lhs, const Vector3<U>& rhs)
{
    return Vector3<decltype(lhs.x*rhs.x)>(
        lhs.x + rhs.x,
        lhs.y + rhs.y,
        lhs.z + rhs.z
        );
}

template<typename T, typename U>
Vector3<T>& operator*=(Vector3<T>& lhs, const Vector3<U>& rhs)
{
    lhs.x *= rhs.x;
    lhs.y *= rhs.y;
    lhs.z *= rhs.z;

    return lhs;
}

Tôi muốn làm một chút dọn dẹp để loại bỏ mã trùng lặp. Về cơ bản, tôi muốn chuyển đổi tất cả các operator*chức năng để gọi các operator*=chức năng như thế này:

template<typename T, typename U>
auto operator*(const Vector3<T>& lhs, const Vector3<U>& rhs)
{
    Vector3<decltype(lhs.x*rhs.x)> result = lhs;
    result *= rhs;
    return result;
}

Nhưng tôi lo ngại liệu nó có phải chịu thêm bất kỳ chi phí nào từ cuộc gọi chức năng bổ sung hay không.

Nó là một ý tưởng tốt? Ý tưởng tồi?


2
Điều này có thể khác nhau từ trình biên dịch để trình biên dịch. Bạn đã thử nó chưa? Viết chương trình tối giản bằng thao tác đó. Sau đó so sánh mã lắp ráp kết quả.
Mario

1
Uh, tôi không biết nhiều về C / C ++ nhưng ... có vẻ như **=đang làm hai việc khác nhau - cái trước thêm các giá trị riêng lẻ, cái sau nhân chúng. Họ cũng xuất hiện để có chữ ký loại khác nhau.
Đồng hồ-Muse

3
Đây có vẻ như là một câu hỏi lập trình C ++ thuần túy không có gì cụ thể để phát triển trò chơi. Có lẽ nó nên được di chuyển sang Stack Overflow ?
Ilmari Karonen

Nếu bạn lo lắng về hiệu suất, bạn nên xem hướng dẫn của SIMD: en.wikipedia.org/wiki/Streaming_SIMD_Extensions
Peter - Unban Robert Harvey

1
Xin đừng viết thư viện toán của riêng bạn vì ít nhất hai lý do. Đầu tiên, có lẽ bạn không phải là chuyên gia về nội tại SSE, vì vậy nó sẽ không nhanh. Thứ hai, sử dụng GPU hiệu quả hơn nhiều cho mục đích tính toán đại số bởi vì nó được tạo ra chỉ vì điều đó. Hãy nhìn vào phần "Liên quan" ở bên phải: gamedev.stackexchange.com/questions/9924/
mẹo

Câu trả lời:


18

Trong thực tế, không có chi phí bổ sung sẽ được phát sinh . Trong C ++, các hàm nhỏ thường được trình biên dịch đưa vào dưới dạng tối ưu hóa, do đó, tập hợp kết quả sẽ có tất cả các hoạt động tại Callite. Các hàm sẽ không gọi nhau, vì các hàm sẽ không tồn tại trong mã cuối cùng, chỉ các phép toán.

Tùy thuộc vào trình biên dịch, bạn có thể thấy một trong các hàm này gọi hàm kia không có hoặc tối ưu hóa thấp (như với các bản dựng gỡ lỗi). Ở mức độ tối ưu hóa cao hơn (bản phát hành phát hành), chúng sẽ được tối ưu hóa thành toán học.

Nếu bạn vẫn muốn mô tả về nó (giả sử bạn đang tạo thư viện), việc thêm inlinetừ khóa vào operator*()(và các hàm bao bọc tương tự) có thể gợi ý trình biên dịch của bạn thực hiện nội tuyến hoặc sử dụng các cờ / cú pháp cụ thể của trình biên dịch như: -finline-small-functions, -finline-functions, -findirect-inlining, __attribute__((always_inline)) (tín dụng để thông tin hữu ích @Stephane Hockenhull của trong các ý kiến) . Cá nhân, tôi có xu hướng làm theo những gì khuôn khổ / lib tôi đang sử dụng làm gì nếu tôi sử dụng thư viện toán học của GLKit, tôi cũng sẽ chỉ sử dụng GLK_INLINEmacro mà nó cung cấp.


Kiểm tra hai lần bằng Clang (Apple LLVM phiên bản 7.0.2 / clang-700.1.81) của Xcode 7.2 , main()chức năng sau (kết hợp với các chức năng của bạn và Vector3<T>triển khai ngây thơ ):

int main(int argc, const char * argv[])
{
    Vector3<int> a = { 1, 2, 3 };
    Vector3<int> b;
    scanf("%d", &b.x);
    scanf("%d", &b.y);
    scanf("%d", &b.z);

    Vector3<int> c = a * b;

    printf("%d, %d, %d\n", c.x, c.y, c.z);

    return 0;
}

biên dịch để lắp ráp này bằng cờ tối ưu hóa -O0:

    .section    __TEXT,__text,regular,pure_instructions
    .globl  _main
    .align  4, 0x90
_main:                                  ## @main
Lfunc_begin0:
    .loc    6 30 0                  ## main.cpp:30:0
    .cfi_startproc
## BB#0:
    pushq   %rbp
Ltmp0:
    .cfi_def_cfa_offset 16
Ltmp1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp2:
    .cfi_def_cfa_register %rbp
    subq    $128, %rsp
    leaq    L_.str1(%rip), %rax
    ##DEBUG_VALUE: main:argc <- undef
    ##DEBUG_VALUE: main:argv <- undef
    movl    $0, -4(%rbp)
    movl    %edi, -8(%rbp)
    movq    %rsi, -16(%rbp)
    .loc    6 31 15 prologue_end    ## main.cpp:31:15
Ltmp3:
    movl    l__ZZ4mainE1a+8(%rip), %edi
    movl    %edi, -24(%rbp)
    movq    l__ZZ4mainE1a(%rip), %rsi
    movq    %rsi, -32(%rbp)
    .loc    6 33 2                  ## main.cpp:33:2
    leaq    L_.str(%rip), %rsi
    xorl    %edi, %edi
    movb    %dil, %cl
    leaq    -48(%rbp), %rdx
    movq    %rsi, %rdi
    movq    %rsi, -88(%rbp)         ## 8-byte Spill
    movq    %rdx, %rsi
    movq    %rax, -96(%rbp)         ## 8-byte Spill
    movb    %cl, %al
    movb    %cl, -97(%rbp)          ## 1-byte Spill
    movq    %rdx, -112(%rbp)        ## 8-byte Spill
    callq   _scanf
    .loc    6 34 17                 ## main.cpp:34:17
    leaq    -44(%rbp), %rsi
    .loc    6 34 2 is_stmt 0        ## main.cpp:34:2
    movq    -88(%rbp), %rdi         ## 8-byte Reload
    movb    -97(%rbp), %cl          ## 1-byte Reload
    movl    %eax, -116(%rbp)        ## 4-byte Spill
    movb    %cl, %al
    callq   _scanf
    .loc    6 35 17 is_stmt 1       ## main.cpp:35:17
    leaq    -40(%rbp), %rsi
    .loc    6 35 2 is_stmt 0        ## main.cpp:35:2
    movq    -88(%rbp), %rdi         ## 8-byte Reload
    movb    -97(%rbp), %cl          ## 1-byte Reload
    movl    %eax, -120(%rbp)        ## 4-byte Spill
    movb    %cl, %al
    callq   _scanf
    leaq    -32(%rbp), %rdi
    .loc    6 37 21 is_stmt 1       ## main.cpp:37:21
    movq    -112(%rbp), %rsi        ## 8-byte Reload
    movl    %eax, -124(%rbp)        ## 4-byte Spill
    callq   __ZmlIiiE7Vector3IDTmldtfp_1xdtfp0_1xEERKS0_IT_ERKS0_IT0_E
    movl    %edx, -72(%rbp)
    movq    %rax, -80(%rbp)
    movq    -80(%rbp), %rax
    movq    %rax, -64(%rbp)
    movl    -72(%rbp), %edx
    movl    %edx, -56(%rbp)
    .loc    6 39 27                 ## main.cpp:39:27
    movl    -64(%rbp), %esi
    .loc    6 39 32 is_stmt 0       ## main.cpp:39:32
    movl    -60(%rbp), %edx
    .loc    6 39 37                 ## main.cpp:39:37
    movl    -56(%rbp), %ecx
    .loc    6 39 2                  ## main.cpp:39:2
    movq    -96(%rbp), %rdi         ## 8-byte Reload
    movb    $0, %al
    callq   _printf
    xorl    %ecx, %ecx
    .loc    6 41 5 is_stmt 1        ## main.cpp:41:5
    movl    %eax, -128(%rbp)        ## 4-byte Spill
    movl    %ecx, %eax
    addq    $128, %rsp
    popq    %rbp
    retq
Ltmp4:
Lfunc_end0:
    .cfi_endproc

Ở trên, __ZmlIiiE7Vector3IDTmldtfp_1xdtfp0_1xEERKS0_IT_ERKS0_IT0_Eoperator*()chức năng của bạn và kết thúc callqing một __…Vector3…chức năng khác . Nó lên đến khá nhiều lắp ráp. Biên dịch với -O1gần như giống nhau, vẫn gọi ra các __…Vector3…chức năng.

Tuy nhiên, khi chúng tôi bump nó lên đến -O2, các callqs để __…Vector3…biến mất, thay thế bằng một imullhướng dẫn (các * a.z* 3), một addlhướng dẫn (các * a.y* 2), và chỉ sử dụng các b.xgiá trị thẳng lên (vì * a.x* 1).

    .section    __TEXT,__text,regular,pure_instructions
    .globl  _main
    .align  4, 0x90
_main:                                  ## @main
Lfunc_begin0:
    .loc    6 30 0                  ## main.cpp:30:0
    .cfi_startproc
## BB#0:
    pushq   %rbp
Ltmp0:
    .cfi_def_cfa_offset 16
Ltmp1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp2:
    .cfi_def_cfa_register %rbp
    .loc    6 33 2 prologue_end     ## main.cpp:33:2
Ltmp3:
    pushq   %rbx
    subq    $24, %rsp
Ltmp4:
    .cfi_offset %rbx, -24
    ##DEBUG_VALUE: main:argc <- EDI
    ##DEBUG_VALUE: main:argv <- RSI
    leaq    L_.str(%rip), %rbx
    leaq    -24(%rbp), %rsi
Ltmp5:
    ##DEBUG_VALUE: operator*=<int, int>:rhs <- [RSI+0]
    ##DEBUG_VALUE: operator*<int, int>:rhs <- [RSI+0]
    ##DEBUG_VALUE: main:b <- [RSI+0]
    xorl    %eax, %eax
    movq    %rbx, %rdi
Ltmp6:
    callq   _scanf
    .loc    6 34 17                 ## main.cpp:34:17
    leaq    -20(%rbp), %rsi
Ltmp7:
    xorl    %eax, %eax
    .loc    6 34 2 is_stmt 0        ## main.cpp:34:2
    movq    %rbx, %rdi
    callq   _scanf
    .loc    6 35 17 is_stmt 1       ## main.cpp:35:17
    leaq    -16(%rbp), %rsi
    xorl    %eax, %eax
    .loc    6 35 2 is_stmt 0        ## main.cpp:35:2
    movq    %rbx, %rdi
    callq   _scanf
    .loc    6 22 18 is_stmt 1       ## main.cpp:22:18
Ltmp8:
    movl    -24(%rbp), %esi
    .loc    6 23 18                 ## main.cpp:23:18
    movl    -20(%rbp), %edx
    .loc    6 23 11 is_stmt 0       ## main.cpp:23:11
    addl    %edx, %edx
    .loc    6 24 11 is_stmt 1       ## main.cpp:24:11
    imull   $3, -16(%rbp), %ecx
Ltmp9:
    ##DEBUG_VALUE: main:c [bit_piece offset=64 size=32] <- ECX
    .loc    6 39 2                  ## main.cpp:39:2
    leaq    L_.str1(%rip), %rdi
    xorl    %eax, %eax
    callq   _printf
    xorl    %eax, %eax
    .loc    6 41 5                  ## main.cpp:41:5
    addq    $24, %rsp
    popq    %rbx
    popq    %rbp
    retq
Ltmp10:
Lfunc_end0:
    .cfi_endproc

Đối với mã này, lắp ráp tại -O2, -O3, -Os, & -Ofasttất cả nhìn giống hệt nhau.


Hừm. Tôi sẽ hết bộ nhớ ở đây, nhưng tôi nhớ rằng họ dự định luôn luôn được thiết kế ngôn ngữ và chỉ không được in trong các bản dựng không được tối ưu hóa để hỗ trợ gỡ lỗi. Có lẽ tôi đang nghĩ về một trình biên dịch cụ thể mà tôi đã sử dụng trong quá khứ.
Slipp D. Thompson

@Peter Wikipedia dường như đồng ý với bạn. Ừ. Vâng, tôi nghĩ rằng tôi đang nhớ lại một chuỗi công cụ cụ thể. Gửi một câu trả lời tốt hơn xin vui lòng?
Slipp D. Thompson

@Peter Phải. Tôi đoán rằng tôi đã bị cuốn vào khía cạnh templated. Chúc mừng!
Slipp D. Thompson

Nếu bạn thêm từ khóa nội tuyến vào các trình biên dịch hàm mẫu có nhiều khả năng nội tuyến ở mức tối ưu hóa đầu tiên (-O1). Trong trường hợp của GCC, bạn cũng có thể kích hoạt nội tuyến tại -O0 với -finline-small- tests -finline- tests -findirect-inlining hoặc sử dụng thuộc tính always_inline không di động ( inline void foo (const char) __attribute__((always_inline));). Nếu bạn muốn những thứ nặng vector chạy ở tốc độ hợp lý trong khi vẫn có thể gỡ lỗi.
Stephane Hockenhull

1
Lý do nó chỉ tạo ra một lệnh nhân duy nhất là tùy thuộc vào các hằng số mà bạn nhân với. Nhân với 1 không làm gì cả, và nhân với 2 được tối ưu hóa thành addl %edx, %edx(tức là thêm giá trị cho chính nó).
Adam
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.