* การเรียก * = (หรือ * = การโทร *) ช้ากว่าการเขียนฟังก์ชันแยก (สำหรับไลบรารีคณิตศาสตร์) หรือไม่ [ปิด]


15

ฉันมีคลาสเวกเตอร์บางส่วนที่ฟังก์ชันทางคณิตศาสตร์มีลักษณะดังนี้:

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;
}

ฉันต้องการล้างข้อมูลเล็กน้อยเพื่อลบรหัสที่ซ้ำกัน โดยทั่วไปฉันต้องการแปลงoperator*ฟังก์ชั่นทั้งหมดเพื่อเรียกoperator*=ใช้ฟังก์ชั่นเช่นนี้:

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;
}

แต่ฉันกังวลว่ามันจะมีค่าใช้จ่ายเพิ่มเติมใด ๆ จากการเรียกฟังก์ชั่นพิเศษ

มันเป็นความคิดที่ดีหรือไม่? ความคิดที่ไม่ดี?


2
สิ่งนี้อาจแตกต่างจากคอมไพเลอร์ถึงคอมไพเลอร์ คุณลองด้วยตัวเองหรือไม่? เขียนโปรแกรมแบบเรียบง่ายโดยใช้การดำเนินการนั้น จากนั้นเปรียบเทียบรหัสแอสเซมบลีที่เกิดขึ้น
มาริโอ

1
เอ่อฉันไม่รู้ C / C ++ มากมาย แต่ ... ดูเหมือนว่า*และ*=กำลังทำสองสิ่งที่แตกต่างกัน - อดีตเพิ่มค่าของแต่ละคนแล้วต่อมาก็คูณพวกเขา พวกเขาดูเหมือนจะมีลายเซ็นประเภทที่แตกต่างกัน
Clockwork-Muse

3
ดูเหมือนว่าคำถามการเขียนโปรแกรม C ++ ล้วนๆไม่มีอะไรพิเศษสำหรับการพัฒนาเกม บางทีควรย้ายไปที่Stack Overflowหรือไม่
Ilmari Karonen

หากคุณกังวลเกี่ยวกับประสิทธิภาพคุณควรดูคำแนะนำ SIMD: en.wikipedia.org/wiki/Streaming_SIMD_ ส่วนขยาย
Peter

1
กรุณาอย่าเขียนห้องสมุดคณิตศาสตร์ของคุณด้วยเหตุผลอย่างน้อยสองประการ ก่อนอื่นคุณอาจไม่ใช่ผู้เชี่ยวชาญใน SSE อินทรินดังนั้นมันจะไม่เร็ว ประการที่สองมีประสิทธิภาพมากขึ้นในการใช้ GPU เพื่อประโยชน์ในการคำนวณพีชคณิตเพราะมันทำเพื่อสิ่งนั้น ดูที่ส่วน "ที่เกี่ยวข้อง" ทางด้านขวา: gamedev.stackexchange.com/questions/9924/…
polkovnikov.ph

คำตอบ:


18

ในทางปฏิบัติ ไม่มีค่าใช้จ่ายเพิ่มเติมจะเกิดขึ้น ใน C ++, ฟังก์ชั่นขนาดเล็กมักจะถูกคอมไพเลอร์เป็นตัวเพิ่มประสิทธิภาพดังนั้นแอสเซมบลีที่เกิดขึ้นจะมีการดำเนินการทั้งหมดที่ callsite - ฟังก์ชั่นจะไม่โทรหากันเนื่องจากฟังก์ชั่นจะไม่มีอยู่ในรหัสสุดท้ายเท่านั้น การดำเนินการทางคณิตศาสตร์

ขึ้นอยู่กับคอมไพเลอร์คุณอาจเห็นหนึ่งในฟังก์ชั่นเหล่านี้เรียกอีกฟังก์ชั่นที่ไม่มีการเพิ่มประสิทธิภาพต่ำ (เช่นเดียวกับ debug builds) ที่ระดับการปรับให้เหมาะสมที่สูงขึ้น (การสร้างรุ่น) พวกเขาจะได้รับการปรับให้เหมาะสมกับคณิตศาสตร์

หากคุณยังคงอยากรู้เกี่ยวกับมัน (พูดว่าคุณกำลังสร้างห้องสมุด) การเพิ่มinlineคำหลักลงในoperator*()(และฟังก์ชั่น wrapper ที่คล้ายกัน) อาจทำให้คอมไพเลอร์ของคุณทำงานแบบอินไลน์หรือใช้แฟล็ก / ไวยากรณ์เฉพาะคอมไพเลอร์เช่น: -finline-small-functions, -finline-functions, -findirect-inlining, __attribute__((always_inline)) (เครดิตให้ข้อมูลที่เป็นประโยชน์ @Stephane Hockenhull ในการแสดงความคิดเห็น) โดยส่วนตัวแล้วฉันมักจะทำตามสิ่งที่เฟรมเวิร์ก / libs ที่ฉันใช้อยู่ - ถ้าฉันใช้ไลบรารีคณิตศาสตร์ของ GLKit ฉันจะใช้GLK_INLINEแมโครที่มันให้มาด้วย


การตรวจสอบซ้ำโดยใช้ Clang (แอปเปิ้ล LLVM เวอร์ชัน 7.0.2 / clang-700.1.81 ของ Xcode 7.2)main()ฟังก์ชั่นต่อไปนี้(ใช้ร่วมกับฟังก์ชั่นของคุณและการVector3<T>ใช้งานแบบไร้เดียงสา):

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;
}

คอมไพล์แอสเซมบลีนี้โดยใช้การตั้งค่าสถานะการปรับให้เหมาะสม-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

ในข้างต้น__ZmlIiiE7Vector3IDTmldtfp_1xdtfp0_1xEERKS0_IT_ERKS0_IT0_Eเป็นoperator*()ฟังก์ชั่นของคุณและจบลงด้วยการทำงานcallqอื่น __…Vector3…มันมีจำนวนการประกอบค่อนข้างมาก การคอมไพล์ด้วย-O1เหมือนกันเกือบจะยังคงเรียกใช้__…Vector3…ฟังก์ชันได้

อย่างไรก็ตาม, เมื่อเราชนมัน-O2, callqs จะ__…Vector3…หายไป, แทนที่ด้วยimullคำสั่ง ( * a.z* 3), addlคำสั่ง ( * a.y* 2), และใช้b.xค่าตรงขึ้น (เพราะ* 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

สำหรับรหัสนี้การชุมนุมที่-O2, -O3, -Osและ-Ofastรูปลักษณ์ที่เหมือนกันทั้งหมด


อืมมม ฉันจะออกจากหน่วยความจำที่นี่ แต่ฉันจำได้ว่าพวกเขาตั้งใจที่จะได้รับการ inline ในการออกแบบของภาษาและเพียงไม่ inline ในการสร้างที่ไม่เหมาะสมเพื่อช่วยในการแก้จุดบกพร่อง บางทีฉันคิดว่าคอมไพเลอร์เฉพาะที่ฉันเคยใช้ในอดีต
Slipp D. Thompson

@Peter Wikipedia ดูเหมือนจะเห็นด้วยกับคุณ Ugg ใช่ฉันคิดว่าฉันกำลังนึกถึง toolchain ที่เฉพาะเจาะจง โพสต์คำตอบที่ดีกว่าโปรด?
Slipp D. Thompson

@ Peter ถูกต้อง ฉันคิดว่าฉันถูกจับในแง่มุมเทมเพลท ไชโย!
Slipp D. Thompson

หากคุณเพิ่มคำสำคัญแบบอินไลน์ลงในคอมไพเลอร์ฟังก์ชันคอมไพเลอร์มีแนวโน้มที่จะอินไลน์ในระดับแรกของการเพิ่มประสิทธิภาพ (-O1) ในกรณีของ GCC คุณสามารถเปิดใช้งานอินไลน์ได้ที่ -O0 พร้อมกับ-finline-small-function -finline-functions -findirect-inliningหรือใช้แบบพกพาที่ไม่ใช่แอตทริบิวต์non_inlineinline void foo (const char) __attribute__((always_inline)); ) หากคุณต้องการให้สิ่งที่เวกเตอร์หนักทำงานด้วยความเร็วที่เหมาะสมในขณะที่ยังคง debuggable
Stephane Hockenhull

1
เหตุผลที่สร้างเพียงคำสั่งคูณทวีคูณนั้นขึ้นอยู่กับค่าคงที่ที่คุณคูณด้วย การคูณด้วย 1 ไม่ทำอะไรเลยและคูณด้วย 2 จะได้รับการปรับให้เหมาะสมaddl %edx, %edx(เช่นเพิ่มมูลค่าให้กับตัวเอง)
อดัม
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.