2017-11-12 3 views
5

사람들이 다양한 라이브러리에서 엄격한 벤치 마크를 수행하려고 할 때, 가끔 다음과 같은 코드를 참조하십시오`static_cast <volatile void>`는 옵티 마이저의 의미는 무엇입니까?

auto std_start = std::chrono::steady_clock::now(); 
for (int i = 0; i < 10000; ++i) 
    for (int j = 0; j < 10000; ++j) 
    volatile const auto __attribute__((unused)) c = std_set.count(i + j); 
auto std_stop = std::chrono::steady_clock::now(); 

테스트중인 코드의 결과가 폐기되는 것을 알아 차리지에서 최적화를 방지하기 위해 여기에 사용되는 volatile, 그리고 전체 계산을 버린다. 테스트중인 코드 값을 반환하지 않는 경우

, 다음 가끔 다음과 같은 코드를 볼 수, 그것은 void do_something(int)라고 :

auto std_start = std::chrono::steady_clock::now(); 
for (int i = 0; i < 10000; ++i) 
    for (int j = 0; j < 10000; ++j) 
    static_cast<volatile void> (do_something(i + j)); 
auto std_stop = std::chrono::steady_clock::now(); 

volatile의 올바른 사용법인가요? volatile void은 무엇입니까? 컴파일러와 표준의 관점에서 무엇을 의미합니까? [dcl.type.cv]에서 표준 (N4296)에서

는 말한다 :

7 [참고 : 휘발성이 개체의 값에 의해 변경 될 수 있기 때문에 구현에 대한 힌트가 개체 을 포함하는 공격적인 최적화를 방지하는 것입니다 구현에서 감지 할 수 없음을 의미합니다. 또한, 일부 구현에서는 일 때 휘발성은 특정 하드웨어 지침이 객체에 액세스해야 함을 나타낼 수 있습니다. 자세한 의미에 대해서는 1.9를 참조하십시오. 일반적으로, 휘발성의 의미는 그들이 C.에로 C++에서 동일하게하기위한 것입니다 - 엔드 노트] 섹션 1.9에서

그것을 실행 모델에 대한 지침을 많이 지정하지만 같은 휘발성 문제는 "volatile 개체에 액세스"에 관한 것입니다. volatile void으로 형 변환 된 문을 실행하면 코드를 올바르게 이해한다고 가정하고 최적화 장벽이 생성되면 무엇을 의미하는지 명확하지 않습니다.

+0

실제로 이것은 제대로 작성되지 않은 테스트 인 것 같습니다. 테스트는 실제 코드와 비교하여 테스트 결과의 잠재적 인 변경을 막기 위해 컴파일러를 혼동시키기 위해 '휘발성'또는 다른 트릭을 사용하지 않아야합니다. – VTT

+0

정확히 마이크로 벤치 마크에서 '휘발성'이하는 일은 꽤 컴파일러에 의존적입니다. 'volatile void'에 캐스팅하면 특정 컴파일러가 값을 계산하지만 실제로 메모리에 저장하는 명령을 보내지 않습니다. 반복 루프에서 테스트하려는 함수의 처리량을 테스트하는 것일 수 있습니다. . –

+0

한 반복의 출력을 다음 반복의 입력으로 보내면 대기 시간을 테스트 할 수 있습니다. * 다른 주변 코드에 미치는 영향을 계산하는 것은 또 다른 별개의 일입니다. (예를 들어'do_something()'이 FP 나 SQRT를 포함하고 있다면, 처리량이 떨어질 수 있지만 주변의 코드에 영향을 미치지는 않습니다. (https://stackoverflow.com/questions/) 4125033/부동 소수점 - 부동 소수점 곱셈/45899202 # 45899202). –

답변

1

static_cast<volatile void> (foo())은 컴파일러가 실제로 최적화를 사용하여 gcc/clang/MSVC/ICC에서 foo()을 계산하도록 요구하는 방식으로 작동하지 않습니다.

#include <bitset> 

void foo() { 
    for (int i = 0; i < 10000; ++i) 
     for (int j = 0; j < 10000; ++j) { 
     std::bitset<64> std_set(i + j); 
     //volatile const auto c = std_set.count();  // real work happens 
     static_cast<volatile void> (std_set.count()); // optimizes away 
     } 
} 

모든 4 개 86 컴파일러와 단지 ret에 컴파일합니다. (MSVC는 std::bitset::count() 또는 무언가의 독립형 정의에 대한 ASM을 방출하지만, foo()의 그 사소한 정의를 아래로 스크롤합니다.

(소스 +의 ASM의 이것에 대한 출력 Matt Godbolt's compiler explorer에 다음 예제)


static_cast<volatile void>()이 어떤 일을하는 컴파일러가 있을지도 모릅니다. 결과를 메모리에 저장하는 지침을 쓰지 않고 반복 계산을 수행하는 가벼운 방법 일 수도 있습니다. 마이크로 벤치 마크에서 원하는 것)

결과를 tmp += foo() (또는 tmp |=)으로 누적하고 main()에서 반환하거나 printf과 함께 인쇄하면 volatile 변수에 저장하는 대신 유용 할 수 있습니다.또는 빈 인라인 asm 문을 사용하여 실제로 지시를 추가하지 않고 컴파일러의 기능을 최적화하는 것과 같은 다양한 컴파일러 관련 사항. 그가 optimizer-escape function for GNU C를 도시


Chandler Carruth's CppCon2015 talk on using perf to investigate compiler optimizations를 참조하십시오. 그러나 그의 escape() 함수는 값이 메모리에 있어야하도록 작성되었습니다 (asm에 void*을 전달하고 "memory" 불투명도로). 필요하지 않습니다. 레지스터 나 메모리에 값을 넣거나 심지어는 상수로만 컴파일러를 사용하면됩니다. (는 asm 문 제로 지침 것을 알고하지 않기 때문에 그것은 완전히 우리의 루프를 풀다 않을 수 있습니다.)


이 코드는 GCC단지 여분의 저장없이 popcnt에 컴파일합니다.

// just force the value to be in memory, register, or even immediate 
// instead of empty inline asm, use the operand in a comment so we can see what the compiler chose. Absolutely no effect on optimization. 
static void escape_integer(int a) { 
    asm volatile("# value = %0" : : "g"(a)); 
} 

// simplified with just one inner loop 
void test1() { 
    for (int i = 0; i < 10000; ++i) { 
     std::bitset<64> std_set(i); 
     int count = std_set.count(); 
     escape_integer(count); 
    } 
} 

#gcc8.0 20171110 nightly -O3 -march=nehalem (for popcnt instruction): 

test1(): 
     # value = 0    # it peels the first iteration with an immediate 0 for the inline asm. 
     mov  eax, 1 
.L4: 
     popcnt rdx, rax 
     # value = edx   # the inline-asm comment has the %0 filled in to show where gcc put the value 
     add  rax, 1 
     cmp  rax, 10000 
     jne  .L4 
     ret 

는 연타 꽤 벙어리 "g" 인 제약 조건을 만족하는 메모리 값을 넣어 선택한다. 그러나 clang은 메모리를 옵션으로 포함하는 인라인 asm 제약 조건을 줄 때이를 수행하는 경향이 있습니다. 따라서 Chandler의 escape 기능보다 낫지 않습니다. -march=haswell

# clang5.0 -O3 -march=nehalem 
test1(): 
    xor  eax, eax 
    #DEBUG_VALUE: i <- 0 
.LBB1_1:        # =>This Inner Loop Header: Depth=1 
    popcnt rcx, rax 
    mov  dword ptr [rsp - 4], ecx 
    # value = -4(%rsp)    # inline asm gets a value in memory 
    inc  rax 
    cmp  rax, 10000 
    jne  .LBB1_1 
    ret 

ICC18이 작업을 수행합니다 : 이상한

test1(): 
    xor  eax, eax          #30.16 
..B2.2:       # Preds ..B2.2 ..B2.1 
      # optimization report 
      # %s was not vectorized: ASM code cannot be vectorized 
    xor  rdx, rdx    # breaks popcnt's false dep on the destination 
    popcnt rdx, rax          #475.16 
    inc  rax           #30.34 
    # value = edx 
    cmp  rax, 10000         #30.25 
    jl  ..B2.2  # Prob 99%      #30.25 
    ret              #35.1 

는, ICC는 xor rdx,rdx 대신 xor eax,eax 사용. 이는 REX 접두사를 낭비하고 Silvermont/KNL에서 종속성을 깨뜨리는 것으로 인식되지 않습니다.

관련 문제