क्या * कॉलिंग * (या * = कॉलिंग *) सेपरेट फ़ंक्शंस लिखने से धीमी है (गणित पुस्तकालय के लिए)? [बन्द है]


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
उह, मैं सी / सी ++ का बहुत कुछ नहीं जानता लेकिन ... ऐसा लगता है *और *=दो अलग-अलग चीजें कर रहे हैं - पूर्व व्यक्तिगत मूल्यों को जोड़ता है, बाद वाला उन्हें गुणा करता है। वे विभिन्न प्रकार के हस्ताक्षर भी करते दिखाई देते हैं।
क्लॉकवर्क-

3
यह खेल के विकास के लिए कुछ खास नहीं के साथ एक शुद्ध C ++ प्रोग्रामिंग प्रश्न की तरह लगता है। शायद इसे स्टैक ओवरफ्लो में स्थानांतरित किया जाना चाहिए ?
इल्मरी करोनें

यदि आप प्रदर्शन के बारे में चिंतित हैं, तो आपको SIMD निर्देशों को देखना चाहिए: en.wikipedia.org/wiki/Streaming_SIMD_Extensions
Peter

1
कृपया कम से कम दो कारणों से अपनी स्वयं की गणित लाइब्रेरी न लिखें। सबसे पहले, आप शायद SSE इंट्रेंसिक्स के विशेषज्ञ नहीं हैं, इसलिए यह जल्दी नहीं होगा। दूसरा, यह बीजीय संगणनाओं के लिए GPU का उपयोग करने के लिए बहुत अधिक कुशल है क्योंकि यह सिर्फ उसी के लिए बनाया गया है। "संबंधित" अनुभाग को दाईं ओर देखें: gamedev.stackexchange.com/questions/9924/…
polkovnikov.ph

जवाबों:


18

व्यवहार में, कोई अतिरिक्त उपरि नहीं लगाया जाएगा । C ++ में, छोटे कार्यों को आमतौर पर कंपाइलर द्वारा ऑप्टिमाइज़ेशन के रूप में इनबिल्ट किया जाता है, इसलिए परिणामस्वरूप असेंबली में सभी ऑपरेशंस कॉलिसाइट पर होंगे- फ़ंक्शन एक दूसरे को कॉल नहीं करेंगे, क्योंकि फ़ंक्शंस अंतिम कोड में मौजूद नहीं होंगे, केवल गणितीय कार्य।

संकलक के आधार पर, आप इनमें से किसी एक फ़ंक्शन को दूसरे को कम या कम अनुकूलन (जैसा कि डीबग बिल्ड के साथ) कहते हैं। हालांकि उच्च अनुकूलन स्तर पर (रिलीज़ बिल्ड), उन्हें केवल गणित के लिए अनुकूलित किया जाएगा।

यदि आप अभी भी इसके बारे में पांडित्यपूर्ण होना चाहते हैं (जैसे कि आप एक पुस्तकालय बना रहे हैं), inlineकीवर्ड को operator*()(और इसी तरह के आवरण कार्यों को) जोड़ना आपके इनलाइन को इनलाइन प्रदर्शन करने के लिए संकेत दे सकता है, या संकलक-विशिष्ट झंडे / वाक्यविन्यास का उपयोग कर सकता है: -finline-small-functions, -finline-functions, -findirect-inlining, __attribute__((always_inline)) (टिप्पणी में @Stephane Hockenhull के बारे में उपयोगी जानकारी के लिए क्रेडिट) । व्यक्तिगत रूप से, मैं यह अनुसरण करता हूं कि मैं जो फ्रेमवर्क / लिबास का उपयोग कर रहा हूं, यदि मैं GLKit की गणित लाइब्रेरी का उपयोग कर रहा हूं, तो मैं केवल उस GLK_INLINEमैक्रो का उपयोग करूंगा जो यह भी प्रदान करता है।


क्लैंग (Xcode 7.2 का Apple LLVM वर्जन 7.0.2 / clang-700.1.81) का उपयोग करके डबल-चेकिंग , निम्न 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, callqकरने के लिए है __…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सब नज़र समान।


हम्म। मैं यहाँ मेमोरी से दूर जा रहा हूँ, लेकिन मुझे याद है कि उनका इरादा हमेशा भाषा के डिज़ाइन में अंतर्निर्मित होना होता है, और डीबगिंग में सहायता के लिए नॉन-ऑप्टिमाइज़्ड बिल्ड में केवल इन-बिल्ट होता है। शायद मैं एक विशिष्ट संकलक के बारे में सोच रहा हूं जो मैंने अतीत में उपयोग किया है।
स्लिप डी। थॉम्पसन

@Peter विकिपीडिया आपसे सहमत प्रतीत होता है। Ugg। हाँ, मुझे लगता है कि मैं एक विशिष्ट टूलकिन को याद कर रहा हूँ। कृपया बेहतर उत्तर दें?
स्लिप डी। थॉम्पसन

@ पेटर राइट। मुझे लगता है कि मैं अस्थायी पहलू पर पकड़ा गया था। चीयर्स!
स्लिप डी। थॉम्पसन

यदि आप इनलाइन कीवर्ड को टेम्प्लेट फ़ंक्शंस कंपाइलर्स में जोड़ते हैं, तो ऑप्टिमाइज़ेशन के पहले स्तर (-O1) में इनलाइन की अधिक संभावना है। GCC के मामले में आप -O0 को -finline-small-functions -finline-functions -findirect -lining के साथ या नॉन पोर्टेबल ऑल्विन_लाइन विशेषता ( inline void foo (const char) __attribute__((always_inline));) का उपयोग करने में सक्षम कर सकते हैं । यदि आप चाहते हैं कि वेक्टर-हैवी चीजें उचित गति से चलें, जबकि अभी भी डीबग करें।
स्टीफन होकेनहुल

1
इसका कारण यह है कि केवल एक ही गुणा निर्देश उत्पन्न होता है जो आपके द्वारा गुणा किए जाने वाले स्थिरांक पर होता है। 1 से गुणा करने पर कुछ नहीं होता है, और 2 से गुणा करने पर इसका अनुकूलन होता है addl %edx, %edx(अर्थात मूल्य को स्वयं में जोड़ें)।
आदम
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.