如何在我的C代码中使用PREFETCHT0指令?

ANT*_*ONY 1 c linux x86 assembly inline-assembly

我想在我的C程序中预取某些地址(这是大型数组的某些元素的地址),并看到它对时间的影响.

关于PREFETCH的指令我在这里找到了PREFETCH0.但我不知道如何使用内联汇编在C中使用它.如果某个机构能够在C程序中如何使用该指令和地址作为参数,那将是非常有帮助的.

Fle*_*exo 6

不要使用内联汇编来编写它,这会使编译器的工作更难.GCC有一个内置的扩展(请参阅gcc builtins docs以获取更多详细信息),您应该使用预取:

__builtin_prefetch(const void*)
Run Code Online (Sandbox Code Playgroud)

这将使用目标的预取指令生成代码,但编译器可以更好地了解它.

作为内联ASM和gcc内置函数之间差异的一个简单示例,请考虑以下两个文件test1.c:

void foo(double *d, unsigned len) {
  for (unsigned i = 0; i < len; ++i) {
    __builtin_prefetch(&d[i]);
    d[i] = d[i] * d[i];
  }
}
Run Code Online (Sandbox Code Playgroud)

和test2.c:

void foo(double *d, unsigned len) {
  for (unsigned i = 0; i < len; ++i) {
    asm("prefetcht0 (%0)" 
        : /**/
        : "g"(&d[i])
        : /**/
    );
    d[i] = d[i] * d[i];
  }
}
Run Code Online (Sandbox Code Playgroud)

(注意,如果您进行基准测试,我99%确定没有预取的第三个版本比上述两个版本更快,因为您有可预测的访问模式,因此它真正实现的唯一事情是添加更多字节说明和几个周期)

如果我们在x86_64上使用-O3编译两者并对生成的输出进行diff,我们会看到:

        .file   "test1.c"                                       |          .file   "test2.c"
        .text                                                              .text
        .p2align 4,,15                                                     .p2align 4,,15
        .globl  foo                                                        .globl  foo
        .type   foo, @function                                             .type   foo, @function
foo:                                                               foo:
.LFB0:                                                             .LFB0:
        .cfi_startproc                                                     .cfi_startproc
        testl   %esi, %esi      # len                                      testl   %esi, %esi      # len
        je      .L1     #,                                                 je      .L1     #,
        leal    -1(%rsi), %eax  #, D.1749                       |          leal    -1(%rsi), %eax  #, D.1745
        leaq    8(%rdi,%rax,8), %rax    #, D.1749               |          leaq    8(%rdi,%rax,8), %rax    #, D.1745
        .p2align 4,,10                                                     .p2align 4,,10
        .p2align 3                                                         .p2align 3
.L4:                                                               .L4:
        movsd   (%rdi), %xmm0   # MEM[base: _8, offset: 0B], D. |  #APP
        prefetcht0      (%rdi)  # ivtmp.6                       |  # 3 "test2.c" 1
                                                                >          prefetcht0 (%rdi)       # ivtmp.6
                                                                >  # 0 "" 2
                                                                >  #NO_APP
                                                                >          movsd   (%rdi), %xmm0   # MEM[base: _8, offset: 0B], D.
        addq    $8, %rdi        #, ivtmp.6                                 addq    $8, %rdi        #, ivtmp.6
        mulsd   %xmm0, %xmm0    # D.1748, D.1748                |          mulsd   %xmm0, %xmm0    # D.1747, D.1747
        movsd   %xmm0, -8(%rdi) # D.1748, MEM[base: _8, offset: |          movsd   %xmm0, -8(%rdi) # D.1747, MEM[base: _8, offset:
        cmpq    %rax, %rdi      # D.1749, ivtmp.6               |          cmpq    %rax, %rdi      # D.1745, ivtmp.6
        jne     .L4     #,                                                 jne     .L4     #,
.L1:                                                               .L1:
        rep ret                                                            rep ret
        .cfi_endproc                                                       .cfi_endproc
.LFE0:                                                             .LFE0:
        .size   foo, .-foo                                                 .size   foo, .-foo
        .ident  "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4"               .ident  "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4"
        .section        .note.GNU-stack,"",@progbits                       .section        .note.GNU-stack,"",@progbits
Run Code Online (Sandbox Code Playgroud)

即使在这个简单的情况下,所讨论的编译器(GCC 4.8.4)也利用了这样一个事实:它允许对事物进行重新排序并且可能基于目标处理器的内部模型进行选择,以便在初始化之后移动预取.负载已经发生.如果我不得不猜测在某些情况下按顺序执行加载和预取会稍快一点.据推测,这个命令对未命中和命中的惩罚较低.或者这样的排序对于分支预测更有效.编译器为什么选择这样做并不重要,重点是完全理解在实际应用中现代处理器上生成的代码甚至微不足道的变化的影响是非常复杂的.通过使用内置函数而不是内联汇编,您可以从编译器今天的知识以及将来出现的任何改进中受益.即使你花了两周时间研究这个简单案例并对其进行基准测试,你也不会打败未来的编译器,甚至可能最终得到一个无法从未来的改进中获益的代码库.

这些问题在我们开始讨论代码的可移植性之前 - 内置函数通常在架构上时属于两类,而不支持优雅降级或启用仿真.有大量x86内联汇编的应用程序在出现时很难移植到x86_64.