2014-06-08 4 views
4

O (1) 복잡도 기능이 고정 입력 크기 인 경우 memcmp()과 유사해야하므로 내장 함수를 사용하여 약간 놀았습니다. 컴파일,내장 함수를 사용할 때 생성 된 어셈블리가 재정렬되는 이유는 무엇입니까?

#include <stdint.h> 
#include <emmintrin.h> 

int64_t f (int64_t a[4], int64_t b[4]) { 
    __m128i *x = (void *) a, *y = (void *) b, r[2], t; 
    int64_t *ret = (void *) &t; 

    r[0] = _mm_xor_si128(x[0], y[0]); 
    r[1] = _mm_xor_si128(x[1], y[1]); 
    t = _mm_or_si128(r[0], r[1]); 


    return (ret[0] | ret[1]); 
} 

이로 바뀝니다 : :이 작성 결국하지만

f: 
    movdqa xmm0, XMMWORD PTR [rdi] 
    movdqa xmm1, XMMWORD PTR [rdi+16] 
    pxor xmm0, XMMWORD PTR [rsi] 
    pxor xmm1, XMMWORD PTR [rsi+16] 
    por xmm0, xmm1 
    movq rdx, xmm0 
    pextrq rax, xmm0, 1 
    or rax, rdx 
    ret 

http://goo.gl/EtovJa (Godbolt 컴파일러 탐색기) 그 후


을, 나는에 호기심이되었다 내재적 함수를 사용해야하는지 아니면 유형을 필요로하는지 여부와 상관없이 일반 연산자를 사용할 수 있습니다. 나는 그 (정말 만 세 SSE 라인) 위의 코드를 수정이와 함께 결국 :

#include <stdint.h> 
#include <emmintrin.h> 

int64_t f (int64_t a[4], int64_t b[4]) { 
    __m128i *x = (void *) a, *y = (void *) b, r[2], t; 
    int64_t *ret = (void *) &t; 

    r[0] = x[0]^y[0]; 
    r[1] = x[1]^y[1]; 
    t = r[0] | r[1]; 


    return (ret[0] | ret[1]); 
} 

대신에 컴파일한다 :

f: 
    movdqa xmm0, XMMWORD PTR [rdi+16] 
    movdqa xmm1, XMMWORD PTR [rdi] 
    pxor xmm0, XMMWORD PTR [rsi+16] 
    pxor xmm1, XMMWORD PTR [rsi] 
    por xmm0, xmm1 
    movq rdx, xmm0 
    pextrq rax, xmm0, 1 
    or rax, rdx 
    ret 

http://goo.gl/oDHF3z (Godbolt 컴파일러 탐색기)


이제 기능적으로 (AFAICT) 두 컴파일 된 어셈블리 출력이 동일합니다. 사실, 그들은 똑같은 시간과 자원을 필요로하는 것처럼 보입니다. 그들은 똑같이 수행 할 것입니다. 그러나 처음 네 명령어의 피연산자가 왜 움직여 졌는지 궁금합니다. 한 방향으로 다른 방향으로 향하는 이유에 대한 특별한 이유가 있습니까?

주 : 두 기능 모두 동일한 플래그로 GCC로 컴파일되었습니다.

+0

표준은 이러한 경우에 평가 순서를 지정하지 않고 컴파일러에서 변경하거나 임의로 인터리브 할 수 있습니다. – Leeor

+0

아,하지만 그건 질문의 대상이 아니에요.이 질문은 어떤 규칙이 그것을 허락 하는지를 묻지는 않지만, 컴파일러가 정확히 그런 방식으로 행동하도록 동기를 부여합니다. – haneefmubarak

+0

그래, 그게 그냥 코멘트 (그것은 첫 번째 코멘트 btw에 대한 답변 아니 었 downvote),하지만 그것은 극도로 특정 컴파일러 및 버전 (및 플래그 집합)에 대한 로컬 질문을 만듭니다. 컴파일러가 코드를 움직이는 데는 여러 가지 이유가있을 수 있습니다. 이는 완전히 다른 최적화 단계로 인해 발생할 수 있습니다. gcc '-fdump-tree-all을 사용하고 어떤 패스가 다른지 확인할 수 있습니다. – Leeor

답변

3

TL : DR : 컴파일러의 관점에서 볼 때 입력 코드는 서로 다르며 다른 위치를 거쳐 다른 테스트를 통해 출력이 달라질 수 있습니다.

IR (LLVM이 사용하는 코드의 중간 표현)에 도달하면 내장 기능이 사라지고 결국 IR이 지침으로 변환되지만 두 경우 모두 IR이 동일합니다.

clang 또는 다른 버전의 gcc로 코드를 체크 아웃하면 명령어 스케줄링이 약간 변경됩니다. 이러한 변경은 일반적으로 CPU 스케쥴러 또는 레지스터 할당 자의 버전 변경으로 인해 발생합니다.

Try this out과 같은 파일에 두 가지 기능을 제공합니다. 다른 버전의 gcc를 사용해보고 다른 버전의 clang을 사용해보십시오. Clang은 movd 명령의 순서 만 변경하며 llvm 백엔드가 두 경우 모두 동일한 IR을 얻으므로 동일한 명령으로 두 함수를 항상 내 보냅니다.

GCC의 내부 구조에 대해서는 잘 모릅니다. 그러나 함수가 스케쥴러의 코드에서 정확히 동일한 위치에 도달하지 못하고 다른 순서로로드가 방출되는 것으로 보입니다. 이것은 내장 함수에 대한 호출 중 하나가 한 사례에서 중간 표현으로 낮추어지지 않고 내장 함수 (함수가 아닌) 호출로 유지 될 수 있기 때문에 발생할 수 있습니다.