gpt4 book ai didi

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

转载 作者:塔克拉玛干 更新时间:2023-11-02 23:26:54 25 4
gpt4 key购买 nike

我想在我的 C 程序中预取某些地址(这是一个巨大数组的某些元素的地址),并查看它对所用时间的影响。

我找到的关于PREFETCH的说明here PREFETCH0 .但我不知道如何使用内联汇编在 C 中使用它。如果有人能给出一些想法,我应该如何在 C 程序中使用这条以地址作为参数的指令,那将会很有帮助。

最佳答案

不要使用内联汇编编写它,这会使编译器的工作变得更加困难。 GCC 有一个用于预取的内置扩展(请参阅 gcc builtins docs 了解更多详细信息),您应该改用:

__builtin_prefetch(const void*)

这将使用目标的预取指令生成代码,但编译器的智能范围更大。

作为内联 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];
}
}

和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];
}
}

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

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

        .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

即使在这种简单的情况下,所讨论的编译器 (GCC 4.8.4) 也利用了允许重新排序的事实,并可能根据目标处理器的内部模型选择移动预取 初始加载发生后。如果我不得不猜测在某些情况下按该顺序进行加载和预取会稍微快一些。据推测,此顺序对未命中和命中的惩罚较低。或者像这样的排序更适合分支预测。不过,编译器选择这样做的原因并不重要,关键是要完全理解在实际应用程序中生成的代码即使是微小的更改对现代处理器的影响也极其复杂。通过使用内置函数而不是内联汇编,您可以从今天的编译器知识和将来出现的任何改进中获益。即使您花费两周的时间研究这个简单的案例并对其进行基准测试,您也很有可能无法击败 future 的编译器,并且您甚至可能最终得到一个无法从 future 的改进中受益的代码库。

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

关于c - 如何在我的 C 代码中使用 PREFETCHT0 指令?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40435640/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com