2017-05-04 4 views
2
class Base 
{ 
public: 
    virtual void fnc(size_t nm) 
    { 
     // do some work here 
    } 

    void process() 
    { 
     for(size_t i = 0; i < 1000; i++) 
     { 
      fnc(i); 
     } 
    } 
} 

C++ 컴파일러는 프로세스 기능에서 fnc 함수에 대한 호출을 최적화 할 수 있습니까? 루프 내에서 호출 될 때마다 동일한 함수로 간주 할 수 있습니까? 또는 함수가 호출 될 때마다 vtable에서 함수 주소를 가져 오는 것입니까?가상 함수 컴파일러 최적화 C++

+4

이 질문에 대한 답은 대부분 컴파일러, 컴파일러 버전 및 컴파일 플래그에 따라 다릅니다. 최적화는 정의 된 동작이 변경되지 않는 한 대부분 구현의 재량에 달려 있습니다. –

+1

간단히 말하자면, 당신의 걱정거리라면 그것을 최적화 할 수 있습니다. 그렇지 않다면 컴파일러 제공자에게 버그 보고서를 보내주십시오. – KABoissonneault

+0

방금 ​​opitimize해야한다는 규칙이있을 거라고 생각했습니다. 나에게 상식처럼 보입니다. –

답변

1

내가 godbolt.org에 example를 확인했습니다. 결과가 NO, 없음이다 컴파일러는이를 최적화합니다.

class Base 
{ 
public: 
// made it pure virtual to decrease clutter 
    virtual void fnc(int nm) =0; 
    void process() 
    { 
     for(int i = 0; i < 1000; i++) 
     { 
      fnc(i); 
     } 
    } 
}; 

void test(Base* b) { 
    return b->process(); 
} 

및 생성 된 ASM :

test(Base*): 
     push rbp  ; setup function call 
     push rbx 
     mov  rbp, rdi ; Base* rbp 
     xor  ebx, ebx ; int ebx=0; 
     sub  rsp, 8 ; advance stack ptr 
.L2: 
     mov  rax, QWORD PTR [rbp+0] ; read 8 bytes from our Base* 
             ; rax now contains vtable ptr 
     mov  esi, ebx    ; int parameter for fnc 
     add  ebx, 1     ; i++ 
     mov  rdi, rbp    ; (Base*) this parameter for fnc 
     call [QWORD PTR [rax]]  ; read vtable and call fnc 
     cmp  ebx, 1000    ; back to the top of the loop 
     jne  .L2 
     add  rsp, 8     ; reset stack ptr and return 
     pop  rbx 
     pop  rbp 
     ret 

당신이 각 통화에 VTABLE을 읽어 볼 수

다음은 테스트 소스입니다. 필자는 컴파일러가 함수 호출 내에서 vtable을 변경하지 않는다는 것을 증명할 수 없기 때문에 (예 : 배치 호출을 새로 호출하거나 바보 같이 호출하는 경우) 기술적으로 가상 함수 호출이 반복간에 변경 될 수 있기 때문입니다.

+0

나는 실제로 놀랐습니다. 약간. –

+0

글쎄 그건 파머 야. –

0

아주 작은 구현을 작성하고 g++ --save-temps opt.cpp을 사용하여 컴파일했습니다. 이 플래그는 임시 전처리 된 파일 인 어셈블리 파일 & 오브젝트 파일을 유지합니다. virtual 키워드를 사용하여 한 번만 실행했습니다. 여기에 프로그램이 있습니다. virtual 리눅스이었다 x86_64의에 결과 어셈블리를 키워드 내가와 를 실행

class Base 
{ 
    public: 
     virtual int fnc(int nm) 
     { 
      int i = 0; 
      i += 3; 
      return i; 
     } 

     void process() 
     { 
      int x = 9; 
      for(int i = 0; i < 1000; i++) 
      { 
       x += i; 
      } 
     } 
    }; 

    int main(int argc, char* argv[]) { 
     Base b; 

     return 0; 
    } 

다음 virtual 키워드없이

.file "opt.cpp" 
    .section .text._ZN4Base3fncEi,"axG",@progbits,_ZN4Base3fncEi,comdat 
    .align 2 
    .weak _ZN4Base3fncEi 
    .type _ZN4Base3fncEi, @function 
_ZN4Base3fncEi: 
.LFB0: 
    .cfi_startproc 
    pushq %rbp 
    .cfi_def_cfa_offset 16 
    .cfi_offset 6, -16 
    movq %rsp, %rbp 
    .cfi_def_cfa_register 6 
    movq %rdi, -24(%rbp) 
    movl %esi, -28(%rbp) 
    movl $0, -4(%rbp) 
    addl $3, -4(%rbp) 
    movl -4(%rbp), %eax 
    popq %rbp 
    .cfi_def_cfa 7, 8 
    ret 
    .cfi_endproc 
.LFE0: 
    .size _ZN4Base3fncEi, .-_ZN4Base3fncEi 
    .text 
    .globl main 
    .type main, @function 
main: 
.LFB2: 
    .cfi_startproc 
    pushq %rbp 
    .cfi_def_cfa_offset 16 
    .cfi_offset 6, -16 
    movq %rsp, %rbp 
    .cfi_def_cfa_register 6 
    subq $32, %rsp 
    movl %edi, -20(%rbp) 
    movq %rsi, -32(%rbp) 
    movq %fs:40, %rax 
    movq %rax, -8(%rbp) 
    xorl %eax, %eax 
    leaq 16+_ZTV4Base(%rip), %rax 
    movq %rax, -16(%rbp) 
    movl $0, %eax 
    movq -8(%rbp), %rdx 
    xorq %fs:40, %rdx 
    je .L5 
    call [email protected] 
.L5: 
    leave 
    .cfi_def_cfa 7, 8 
    ret 
    .cfi_endproc 
.LFE2: 
    .size main, .-main 
    .weak _ZTV4Base 
    .section .data.rel.ro.local._ZTV4Base,"awG",@progbits,_ZTV4Base,comdat 
    .align 8 
    .type _ZTV4Base, @object 
    .size _ZTV4Base, 24 
_ZTV4Base: 
    .quad 0 
    .quad _ZTI4Base 
    .quad _ZN4Base3fncEi 
    .weak _ZTI4Base 
    .section .data.rel.ro._ZTI4Base,"awG",@progbits,_ZTI4Base,comdat 
    .align 8 
    .type _ZTI4Base, @object 
    .size _ZTI4Base, 16 
_ZTI4Base: 
    .quad _ZTVN10__cxxabiv117__class_type_infoE+16 
    .quad _ZTS4Base 
    .weak _ZTS4Base 
    .section .rodata._ZTS4Base,"aG",@progbits,_ZTS4Base,comdat 
    .type _ZTS4Base, @object 
    .size _ZTS4Base, 6 
_ZTS4Base: 
    .string "4Base" 
    .ident "GCC: (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005" 
    .section .note.GNU-stack,"",@progbits 

, 최종 조립했다 :

 
    .file "opt.cpp" 
    .text 
    .globl main 
    .type main, @function 
main: 
.LFB2: 
    .cfi_startproc 
    pushq %rbp 
    .cfi_def_cfa_offset 16 
    .cfi_offset 6, -16 
    movq %rsp, %rbp 
    .cfi_def_cfa_register 6 
    movl %edi, -20(%rbp) 
    movq %rsi, -32(%rbp) 
    movl $0, %eax 
    popq %rbp 
    .cfi_def_cfa 7, 8 
    ret 
    .cfi_endproc 
.LFE2: 
    .size main, .-main 
    .ident "GCC: (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005" 
    .section .note.GNU-stack,"",@progbits 

이제 게시 된 질문과 달리이 예제는 가상 메서드와 결과 집합을 사용하지 않습니다. 훨씬 더 큽니다. 나는 최적화와 컴파일을 시도하지는 않았지만 그것을 제공합니다.

1

일반적으로 컴파일러는 프로그램의 관찰 가능한 동작을 변경하지 않는 항목을 최적화 할 수 있습니다. 함수에서 돌아 왔을 때 중요하지 않은 복사 생성자를 제거하는 것과 같은 몇 가지 예외가 있지만 C++ Abstract Machine에서 프로그램의 출력이나 부작용을 변경하지 않는 것으로 예상되는 코드 생성의 변경은 컴파일러가 수행해야합니다.

그래서 함수를 가상화하여 관찰 가능한 동작을 변경할 수 있습니까? this article에 따르면 그렇습니다.

관련 통로 :

[...] 최적화 [가상 함수] 통과 물체의 vptr에서 변화 할 수 있다고 가정 할 것이다. [...]

void A::foo() { // virtual 
static_assert(sizeof(A) == sizeof(Derived)); 
new(this) Derived; 
} 

이 배치 새로운 오퍼레이터의 호출 - 그것은 새로운 메모리를 할당하지 않고, 단지 제공된 위치에서 새로운 객체를 생성한다. 그래서, 타입 A의 객체가 살고있는 곳에서 Derived 객체를 생성함으로써, 우리는 vptr을 Derived의 vtable을 가리 키도록 변경합니다. 이 코드가 합법적입니까? C++ 표준은 예 말한다. "

따라서 컴파일러는 가상 함수의 정의에 액세스 할 수있는 (그리고 컴파일 유형에 *this의 구체적인 유형을 알고)하지 않는 경우,이 최적화는 위험합니다.

이 같은 기사에 따르면, 당신은 C++ 표준을 준수 적은 코드를 만들기의 위험이 최적화 할 수 있도록 연타에 -fstrict-vtable-pointers를 사용합니다.

+0

누군가가 포맷팅을 도울 수 있다면 도움을 받으실 수 있습니다. – KABoissonneault