代码之家  ›  专栏  ›  技术社区  ›  SpilledMango

对于x86-64,哪个更快,imm64还是m64?

  •  2
  • SpilledMango  · 技术社区  · 7 年前

    在测试大约100亿次后,如果 imm64 比0.1纳秒快 m64 m64 似乎更快,但我真的不明白。不是的地址吗 val_ptr 在下面的代码中,立即数本身?

    # 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;
    }
    

    那么,哪一个真的是最快的, imm64 ?

    编辑 ret 说明,现在 imm64

    1 回复  |  直到 7 年前
        1
  •  5
  •   Peter Cordes    7 年前

    你没有展示你测试的实际循环,也没有提及你是如何测量时间的。显然,您测量的是挂钟时间,而不是核心时钟周期(使用性能计数器)。因此,测量噪声的来源包括涡轮/节能以及与另一个逻辑线程(在i7上)共享一个物理内核。


    movabs rax, 0xDEADBEEFFEEDFACE

    • 取10个字节的代码大小(取决于周围的代码,这可能重要,也可能无关紧要)。
    • 对任何ALU端口(p0、p1或p5)解码为1 uop。(最大吞吐量=每个时钟3)
    • 在uop缓存中获取2个条目(因为64位立即数),并从uop缓存中读取2个周期。(因此,如果前端吞吐量是包含循环缓冲区的代码中的瓶颈,那么从循环缓冲区运行是一个显著的优势)。

    mov rax, [RIP + val_ptr] 是一个负载

    • 对任一装载端口(p2或p3)解码为1 uop。(最大吞吐量=每个时钟2)
    • 适合uop缓存中的1个条目(无立即和32或32小地址偏移)。
    • 如果负载跨页面边界分割,即使在Skylake上,运行速度也会慢得多。

    资料来源: Agner Fog's microarch pdf and instruction tables . uop缓存内容见表9.1。另请参阅中的其他性能链接


    编译器通常选择使用 mov r64, imm64 . (相关: 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 and rax, [constant] movabs r8, imm64 / and rax, r8 .

    If your 64-bit constant is an address lea 相反,如果可能的话。 lea rax, [rel my_symbol] lea my_symbol(%rip), %rax


    在考虑asm的微小序列时,周围的代码非常重要