Apakah * panggilan * = (atau * = panggilan *) lebih lambat daripada menulis fungsi terpisah (untuk perpustakaan matematika)? [Tutup]


15

Saya memiliki beberapa kelas vektor di mana fungsi aritmatika terlihat seperti ini:

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

Saya ingin melakukan sedikit pembersihan untuk menghapus kode duplikat. Pada dasarnya, saya ingin mengonversi semua operator*fungsi untuk memanggil operator*=fungsi-fungsi seperti ini:

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

Tapi saya khawatir apakah itu akan menimbulkan overhead tambahan dari panggilan fungsi tambahan.

Apakah itu ide yang bagus? Ide buruk?


2
Ini mungkin berbeda dari kompiler ke kompiler. Sudahkah Anda mencobanya sendiri? Tulis program minimalis menggunakan operasi itu. Kemudian bandingkan kode perakitan yang dihasilkan.
Mario

1
Eh, saya tidak tahu banyak C / C ++ tapi ... sepertinya *dan *=sedang melakukan dua hal yang berbeda - yang pertama menambahkan nilai individu, yang terakhir mengalikannya. Mereka juga tampaknya memiliki tipe tanda tangan yang berbeda.
Clockwork-Muse

3
Ini sepertinya pertanyaan pemrograman C ++ murni dengan tidak ada yang spesifik untuk pengembangan game. Mungkin itu harus dimigrasi ke Stack Overflow ?
Ilmari Karonen

Jika Anda khawatir tentang kinerja, Anda harus melihat instruksi SIMD: en.wikipedia.org/wiki/Streaming_SIMD_Extensions
Peter

1
Tolong jangan menulis perpustakaan matematika Anda sendiri untuk setidaknya dua alasan. Pertama, Anda mungkin bukan ahli dalam SSE intrinsik, jadi itu tidak akan cepat. Kedua, jauh lebih efisien untuk menggunakan GPU demi perhitungan aljabar karena dibuat hanya untuk itu. Lihatlah bagian "Terkait" di sebelah kanan: gamedev.stackexchange.com/questions/9924/…
polkovnikov.ph

Jawaban:


18

Dalam praktiknya, tidak ada biaya tambahan yang akan dikeluarkan . Dalam C ++, fungsi-fungsi kecil biasanya digarisbawahi oleh kompiler sebagai optimisasi, sehingga rakitan yang dihasilkan akan memiliki semua operasi di callsite - fungsi tidak akan saling memanggil, karena fungsi tidak akan ada dalam kode akhir, hanya saja operasi matematika.

Bergantung pada kompilernya, Anda mungkin melihat salah satu dari fungsi-fungsi ini memanggil yang lain tanpa optimasi yang rendah (seperti debug build). Pada tingkat optimisasi yang lebih tinggi (rilis rilis), mereka akan dioptimalkan hingga hanya matematika.

Jika Anda masih ingin bertele-tele tentang hal itu (misalnya Anda membuat perpustakaan), menambahkan inlinekata kunci ke operator*()(dan fungsi pembungkus serupa) dapat mengisyaratkan kompiler Anda untuk melakukan inline, atau menggunakan flag / sintaks khusus kompiler seperti: -finline-small-functions, -finline-functions, -findirect-inlining, __attribute__((always_inline)) (kredit untuk @Stephane Hockenhull ini info berguna di komentar) . Secara pribadi, saya cenderung mengikuti apa yang dilakukan framework / libs yang saya gunakan — jika saya menggunakan perpustakaan matematika GLKit, saya hanya akan menggunakan GLK_INLINEmakro yang disediakannya juga.


Periksa ulang menggunakan Dentang (Xcode 7.2 Apple LLVM versi 7.0.2 / clang-700.1.81) , main()fungsi berikut (dalam kombinasi dengan fungsi Anda dan Vector3<T>implementasi naif ):

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

kompilasi ke majelis ini menggunakan bendera optimisasi -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

Di atas, __ZmlIiiE7Vector3IDTmldtfp_1xdtfp0_1xEERKS0_IT_ERKS0_IT0_Eadalah operator*()fungsi Anda dan akhirnya fungsi callqlain __…Vector3…. Jumlahnya cukup banyak perakitan. Kompilasi dengan -O1hampir sama, masih memanggil __…Vector3…fungsi.

Namun, ketika kita menabraknya -O2, callqs __…Vector3…menghilang, diganti dengan imullinstruksi ( * a.z* 3), addlinstruksi ( * a.y* 2), dan hanya menggunakan b.xnilai straight-up (karena * 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

Untuk kode ini, perakitan di -O2, -O3, -Os, & -Ofastsemua tampilan yang identik.


Hmm. Saya kehabisan memori di sini, tetapi saya ingat bahwa itu dimaksudkan untuk selalu diuraikan dalam desain bahasa, dan hanya non-inline dalam build yang tidak dioptimalkan untuk membantu debugging. Mungkin saya sedang memikirkan kompiler tertentu yang saya gunakan di masa lalu.
Slipp D. Thompson

@Peter Wikipedia tampaknya setuju dengan Anda. Ugg. Ya, saya pikir saya sedang mengingat toolchain tertentu. Posting jawaban yang lebih baik?
Slipp D. Thompson

@Peter Right. Saya kira saya terjebak pada aspek templated. Bersulang!
Slipp D. Thompson

Jika Anda menambahkan kata kunci inline ke kompiler fungsi template lebih cenderung inline di tingkat pertama optimasi (-O1). Dalam kasus GCC, Anda juga dapat mengaktifkan inlining di -O0 dengan -finline-functions -finline-functions -findirect-inlining atau menggunakan atribut always_inline yang tidak portabel ( inline void foo (const char) __attribute__((always_inline));). Jika Anda ingin hal-hal vektor-berat berjalan pada kecepatan yang masuk akal sementara masih bisa ditawar
Stephane Hockenhull

1
Alasan itu hanya menghasilkan instruksi multiplikasi tunggal adalah ke konstanta yang Anda kalikan. Mengalikan dengan 1 tidak menghasilkan apa-apa, dan mengalikan dengan 2 dioptimalkan untuk addl %edx, %edx(yaitu menambahkan nilai ke dirinya sendiri).
Adam
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.