* Çağrı * = (veya * = çağrı *) ayrı işlevler yazmaktan daha yavaş mı (matematik kütüphanesi için)? [kapalı]


15

Burada aritmetik fonksiyonların şöyle göründüğü bazı vektör sınıfları var:

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

Yinelenen kodu kaldırmak için biraz temizleme yapmak istiyorum. Temel olarak, tüm işlevleri bu gibi operator*işlevleri çağırmak operator*=istiyorum:

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

Ancak, ekstra işlev çağrısından ek yük getirip getirmeyeceği konusunda endişeliyim.

İyi bir fikir mi? Kötü bir fikir?


2
Bu derleyiciden derleyiciye farklı olabilir. Kendiniz denediniz mi? Bu işlemi kullanarak minimalist bir program yazın. Sonra ortaya çıkan montaj kodunu karşılaştırın.
Mario

1
Uh, çok fazla C / C ++ bilmiyorum ama ... iki farklı şey gibi görünüyor *ve *=yapıyorlar - birincisi bireysel değerleri, ikincisi onları çarpar. Ayrıca farklı tip imzalara sahip gibi görünüyorlar.
Clockwork-Muse

3
Bu, oyun geliştirmeye özgü hiçbir şeye sahip olmayan saf bir C ++ programlama sorusu gibi görünüyor. Belki de Stack Overflow'a taşınmalıdır ?
Ilmari Karonen

Performans konusunda endişeleriniz varsa, SIMD talimatlarına bakmalısınız: en.wikipedia.org/wiki/Streaming_SIMD_Extensions
Peter

1
Lütfen en az iki nedenden ötürü kendi matematik kütüphanenizi yazmayın. İlk olarak, muhtemelen SSE intrinsics konusunda uzman değilsiniz, bu yüzden hızlı olmayacak. İkincisi, cebirsel hesaplamalar için GPU kullanmak çok daha verimli çünkü sadece bunun için yapılmış. Sağdaki "İlgili" bölümüne bir göz atın: gamedev.stackexchange.com/questions/9924/…
polkovnikov.ph

Yanıtlar:


18

Uygulamada, ilave ek masraf oluşmayacaktır . C ++ 'da, küçük işlevler genellikle derleyici tarafından bir optimizasyon olarak satır içine alınır, bu nedenle sonuçta oluşan derleme çağrı sitesinde tüm işlemlere sahip olur - işlevler yalnızca çağrılamaz, çünkü işlevler yalnızca son kodda bulunmaz, matematiksel işlemler.

Derleyiciye bağlı olarak, bu işlevlerden birinin diğerini çağıran veya düşük optimizasyonla (hata ayıklama derlemelerinde olduğu gibi) çağırdığını görebilirsiniz. Yine de daha yüksek optimizasyon düzeyinde (sürüm oluşturmaları), sadece matematiğe göre optimize edilecekler.

Hala bu konuda bilgiçlik istiyorsanız (bir kütüphane oluşturduğunuzu varsayalım), inlineanahtar kelimeyi operator*()(ve benzer sarmalayıcı işlevlerini) eklemek, derleyicinize satır içi işlem yapmasını veya derleyiciye özgü bayrakları / sözdizimini kullanma gibi ipucu verebilir: -finline-small-functions, -finline-functions, -findirect-inlining, __attribute__((always_inline)) (yorumlarda @Stephane Hockenhull en yararlı bilgi için kredi) . Şahsen, kullandığım çerçevenin / kütüphanelerin ne yaptığını takip etme eğilimindeyim - GLKit'in matematik kütüphanesini kullanıyorsanız, sadece sağladığı GLK_INLINEmakroyu da kullanacağım .


Clang (Xcode 7.2'nin Apple LLVM sürüm 7.0.2 / clang-700.1.81) , aşağıdaki main()işlevi (işlevleriniz ve naif bir Vector3<T>uygulama ile birlikte) kullanarak çift kontrol :

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

optimizasyon bayrağını kullanarak bu montajı derler -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

Yukarıda, __ZmlIiiE7Vector3IDTmldtfp_1xdtfp0_1xEERKS0_IT_ERKS0_IT0_Esenin olan operator*()fonksiyon ve yukarı uçları callqbaşka ing __…Vector3…fonksiyonu. Oldukça fazla montaj anlamına geliyor. İle derlemek -O1neredeyse aynıdır, yine de __…Vector3…işlevlere seslenir.

Biz onu yükseltmek Ancak, -O2, callqiçin s __…Vector3…kaybolur, bir ile değiştirilir imulltalimatı ( * a.z* 3), bir addltalimat ( * a.y* 2) ve sadece kullanan b.xdeğeri düz-up (çünkü * 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

Bu kodu için montaj de -O2, -O3, -Os, ve -Ofasttüm göz aynıdır.


Hmm. Burada bellekten çıkıyorum, ancak her zaman dilin tasarımında satır içi olmayı amaçladıklarını ve hata ayıklamaya yardımcı olmak için yalnızca optimize edilmemiş yapılarda satır içi olmadığını hatırlıyorum. Belki de geçmişte kullandığım belirli bir derleyiciyi düşünüyorum.
Slipp D.Thompson

@Peter Wikipedia sizinle aynı fikirde. Ugg. Evet, sanırım belirli bir takım zincirini hatırlıyorum. Daha iyi bir cevap gönderin lütfen?
Slipp D.Thompson

@Peter Sağ. Sanırım şablonda yakalandım. Şerefe!
Slipp D.Thompson

Satır içi anahtar kelimeyi şablon işlevlerine eklerseniz, derleyicilerin ilk optimizasyon düzeyinde (-O1) satır içinde olma olasılığı daha yüksektir. GCC durumunda -O0'da -finline-small-function -finline-function -findirect-inlining ile satır içi etkinleştirmeyi etkinleştirebilir veya taşınabilir olmayan always_inline özniteliğini ( inline void foo (const char) __attribute__((always_inline));) kullanabilirsiniz. Vektör ağır şeylerin hala hata ayıklanabilirken makul bir hızda çalışmasını istiyorsanız.
Stephane Hockenhull

1
Yalnızca tek bir çarpma talimatı oluşturmasının nedeni, çarptığınız sabitlere bağlıdır. 1 ile çarpma hiçbir şey yapmaz ve 2 ile çarpma işlemi için optimize edilir addl %edx, %edx(yani değeri kendisine ekleyin).
Adam
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.