2017-09-26 13 views
2

약 10 억 번 테스트 한 후 imm64이 0.1 나노 초인 경우 m64보다 빠릅니다. m64은 더 빠를 것 같지만 실제로 이해할 수는 없습니다. 다음 코드의 val_ptr의 주소가 바로 값이 아닌가?x86-64의 경우보다 빠른, imm64 또는 m64 중 어느 것입니까?

# Text section 
.section __TEXT,__text,regular,pure_instructions 
# 64-bit code 
.code64 
# Intel syntax 
.intel_syntax noprefix 
# Target macOS High Sierra 
.macosx_version_min 10,13,0 

# Make those two test functions global for the C measurer 
.globl _test1 
.globl _test2 

# Test 1, imm64 
_test1: 
    # Move the immediate value 0xDEADBEEFFEEDFACE to RAX (return value) 
    movabs rax, 0xDEADBEEFFEEDFACE 
    ret 
# Test 2, m64 
_test2: 
    # Move from the RAM (val_ptr) to RAX (return value) 
    mov rax, qword ptr [rip + val_ptr] 
    ret 
# Data section 
.section __DATA,__data 
val_ptr: 
    .quad 0xDEADBEEFFEEDFACE 

측정 코드는 다음과 같습니다 그래서

#include <stdio.h>   // For printf 
#include <stdlib.h>   // For EXIT_SUCCESS 
#include <math.h>    // For fabs 
#include <stdint.h>   // For uint64_t 
#include <stddef.h>   // For size_t 
#include <string.h>   // For memset 
#include <mach/mach_time.h> // For time stuff 

#define FUNCTION_COUNT 2  // Number of functions to test 
#define TEST_COUNT  0x10000000 // Number of times to test each function 

// Type aliases 
typedef uint64_t rettype_t; 
typedef rettype_t(*function_t)(); 

// External test functions (defined in Assembly) 
rettype_t test1(); 
rettype_t test2(); 

// Program entry point 
int main() { 

    // Time measurement stuff 
    mach_timebase_info_data_t info; 
    mach_timebase_info(&info); 

    // Sums to divide by the test count to get average 
    double sums[FUNCTION_COUNT]; 

    // Initialize sums to 0 
    memset(&sums, 0, FUNCTION_COUNT * sizeof (double)); 

    // Functions to test 
    function_t functions[FUNCTION_COUNT] = {test1, test2}; 

    // Useless results (should be 0xDEADBEEFFEEDFACE), but good to have 
    rettype_t results[FUNCTION_COUNT]; 

    // Function loop, may get unrolled based on optimization level 
    for (size_t test_fn = 0; test_fn < FUNCTION_COUNT; test_fn++) { 
    // Test this MANY times 
    for (size_t test_num = 0; test_num < TEST_COUNT; test_num++) { 
     // Get the nanoseconds before the action 
     double nanoseconds = mach_absolute_time(); 
     // Do the action 
     results[test_fn] = functions[test_fn](); 
     // Measure the time it took 
     nanoseconds = mach_absolute_time() - nanoseconds; 

     // Convert it to nanoseconds 
     nanoseconds *= info.numer; 
     nanoseconds /= info.denom; 

     // Add the nanosecond count to the sum 
     sums[test_fn] += nanoseconds; 
    } 
    } 
    // Compute the average 
    for (size_t i = 0; i < FUNCTION_COUNT; i++) { 
    sums[i] /= TEST_COUNT; 
    } 

    if (FUNCTION_COUNT == 2) { 
    // Print some fancy information 
    printf("Test 1 took %f nanoseconds average.\n", sums[0]); 
    printf("Test 2 took %f nanoseconds average.\n", sums[1]); 
    printf("Test %d was faster, with %f nanoseconds difference\n", sums[0] < sums[1] ? 1 : 2, fabs(sums[0] - sums[1])); 
    } else { 
    // Else, just print something 
    for (size_t fn_i = 0; fn_i < FUNCTION_COUNT; fn_i++) { 
     printf("Test %zu took %f clock ticks average.\n", fn_i + 1, sums[fn_i]); 
    } 
    } 

    // Everything went fine! 
    return EXIT_SUCCESS; 
} 

, 정말 빠른 인 m64 또는 imm64?

그런데 인텔 코어 i7 아이비 브리지와 DDR3 RAM을 사용하고 있습니다. 나는 macOS High Sierra를 실행 중입니다.

편집 : 나는 ret 지침을 삽입하고, 지금 imm64 빨리 밝혀졌다.

+0

정확히 어떻게 테스트 했습니까? 당신의'_test1'은 리턴하지 않습니다 : 그것은'_test2'로 넘어갑니다. (또한 넘어짐). 전혀 풀어 봤니? –

+0

테스트 함수의 호출마다'mach_absolute_time()'을 두 번 호출합니다!?!?! ???? ???? 적어도 RDTSC 명령 (21 uops 및 IvB에서 27c 당 하나의 처리량)을 실행해야하기 때문에 오랜 시간이 걸립니다. 아마도 프론트 엔드에서 병목 현상을 일으키지 않을 것입니다. –

+0

하지만 어떻게 해결할 수 있습니까? – SpilledMango

답변

4

테스트 한 실제 루프를 표시하지 않거나 시간 측정 방법에 대해 언급하지 마십시오. 분명히 핵심 클럭 사이클 (성능 카운터 포함)이 아닌 월 클럭 시간을 측정했습니다. 따라서 측정 소음원에는 터보/절전 기능과 물리적 코어를 다른 논리적 스레드 (i7)와 공유하는 것이 포함됩니다. 인텔 IvyBridge에


:

movabs rax, 0xDEADBEEFFEEDFACE

  • 가 (또는 주변의 코드에 따라 문제가되지 않을 수 있습니다) 코드 크기의 10 바이트으로 데리고 ALU 명령어입니다.
  • 모든 ALU 포트 (p0, p1 또는 p5)에 대해 1 upt로 디코딩합니다. (최대 처리량 = 3/클럭 당)
  • (64 비트 즉각 때문에) uop 캐시에서 2 항목을 취하고 uop 캐시에서 읽는 데 2 ​​사이클이 소요됩니다. (따라서 루프 버퍼에서 실행하면 프런트 엔드 처리량에 큰 이점이 있습니다.이 경우에는이를 포함하는 코드에서 병목 현상이 발생합니다.)

mov rax, [RIP + val_ptr]

  • 로드 포트 (P2 또는 P3) 중 1 UOP로 디코딩 7 바이트 (REX + 오피 + modrm + rel32)에 걸리는 부하이다. (최대 처리량 = 클럭 당 2 개)
  • 은 uop 캐시에 1 개 항목 (즉각 및 32 또는 32 작은 주소 오프셋 없음)에 맞습니다.
  • 은 Skylake에서도 페이지 경계를 넘어 부하가 분산되면 훨씬 느리게 실행됩니다.
  • 은 캐시에서 처음으로 누락 될 수 있습니다.

출처 : Agner Fog's microarch pdf and instruction tables. uop-cache에 대해서는 표 9.1을 참조하십시오. 태그 위키의 다른 성능 링크를 참조하십시오.


컴파일러는 일반적으로 mov r64, imm64 64 비트 상수를 생성하기 위해 선택합니다. (관련 : What are the best instruction sequences to generate vector constants on the fly?, 실제로는 스칼라 정수로 오지 않습니다. 왜냐하면 no short single-instruction way to get a 64-bit -1이 있기 때문입니다.)

캐시가 뜨거워지기를 기대하는 장기 실행 루프에서는 .rodata에서로드하는 것이 좋지만 일반적으로 올바른 선택입니다. 특히 movabs r8, imm64/and rax, r8 대신 and rax, [constant]과 같은 작업을 수행 할 수있는 경우 특히 그렇습니다.

If your 64-bit constant is an address 가능한 경우 RIP 상대 lea을 사용하십시오. 서로 다른 처리량 자원에 대한 경쟁, 특히, ASM의 작은 순서를 고려할 때 AT & T.


주변 코드에서 NASM 구문 lea rax, [rel my_symbol], lea my_symbol(%rip), %rax 많이 중요.

+1

나는'ret' 명령을 잊을 수 있었다. – SpilledMango

+1

@ SpilledMango : 그래서이 함수를 호출하는 루프를 테스트하고 있었습니까 ?? Call/ret는이 지침보다 처리량이 제한적입니다. (Agner Fog에 따르면 IvB에서 2 클록 당 1 개). 아주 작은 기능의 호출/정지로 이상한 효과를 얻을 수 있습니다. Skylake의 경우이 사례를 참조하십시오. https://stackoverflow.com/questions/45442458/loop-with-function-call-faster-than-an-empty-loop/45529487 루프에'call '을 넣는 것은 평균 store-forwarding 대기 시간을 낮춤으로써 실제로 그것을 증가시킵니다. 따라서 옳은 것을 측정하는 데주의하십시오. –

+1

또한 Peter가 마지막에 쓴 내용을 다시 읽은 다음 마지막으로 읽습니다. 특히 작은 처리량을 고려할 때 주변 코드가 중요합니다. 특히 처리량 리소스가 서로 다른 경우에는 더욱 그렇습니다. _ 확실히 확실히 흥미 롭습니다. 어느 명령이나 작은 ASM 관용구가 _ 빠른지는 묻지 만, 종종 문맥에 의존한다는 것을 이해하십시오. 이러한 질문은 XYZ를 원한다면 가장 빠른 XYZ를 얻기 위해 X, Y 및 Z를 수행하는 가장 빠른 방법을 결정하고 함께 문자열링 할 수 있다는 아이디어에서 비롯된 것입니다. 이것은 종종 단순히 사실이 아닙니다. – BeeOnRope