* 호출 * = (또는 * = 호출 *)이 별도의 함수를 쓰는 것보다 느리습니까 (수학 라이브러리의 경우)? [닫은]


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
이것은 컴파일러마다 다를 수 있습니다. 직접 해보셨습니까? 해당 작업을 사용하여 최소한의 프로그램을 작성하십시오. 그런 다음 결과 어셈블리 코드를 비교하십시오.
Mario

1
어, C / C ++를 많이 모르지만 ... 것 같습니다 **=두 개의 서로 다른 일을하고있다 - 전자는이를 곱, 후자의 개별 값을 추가합니다. 또한 서로 다른 유형 서명이있는 것으로 보입니다.
Clockwork-Muse

3
이것은 게임 개발과 관련이없는 순수한 C ++ 프로그래밍 질문처럼 보입니다. 아마도 스택 오버플 로로 마이그레이션해야 합니까?
Ilmari Karonen

성능이 걱정된다면 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하는 경우 제공 하는 매크로도 사용합니다 .


Clang (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_Eoperator*()함수 는 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모든 모양과 동일.


흠. 나는 메모리에서 벗어나고 있지만 언어 디자인에서 항상 인라인되고 디버깅을 돕기 위해 최적화되지 않은 빌드에서만 인라인되지 않는다는 것을 기억합니다. 어쩌면 나는 과거에 사용한 특정 컴파일러에 대해 생각하고 있습니다.
Slipp D. Thompson

@Peter Wikipedia가 당신과 동의하는 것 같습니다. 어그 네, 특정 툴체인을 기억하고 있다고 생각합니다. 더 나은 답변을 올리시겠습니까?
Slipp D. Thompson

@ 피터 오른쪽. 템플릿 측면에 따라 잡힌 것 같습니다. 건배!
Slipp D. Thompson

템플릿 함수에 인라인 키워드를 추가하면 컴파일러는 최적화의 첫 번째 수준 (-O1)에서 인라인 될 가능성이 높습니다. GCC의 경우 -finline-small-functions -finline-functions -findirect-inlining을 사용하여 -O0에서 인라인을 활성화 하거나 이식성이없는 always_inline 속성 ( inline void foo (const char) __attribute__((always_inline));)을 사용할 수도 있습니다. 벡터가 많은 것들을 여전히 디버깅 가능한 상태에서 적당한 속도로 실행하려면.
Stephane Hockenhull

1
단일 곱셈 명령어 만 생성하는 이유는 곱하는 상수에 달려 있습니다. 1을 곱하면 아무것도하지 않으며 2를 곱하면 최적화됩니다 addl %edx, %edx(즉, 값을 자체에 추가).
Adam
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.