我最近一直在写x86程序集(为了好玩),并且想知道rep前缀字符串指令是否实际上在现代处理器上具有性能优势,或者它们是否刚刚实现了后向兼容性.
我理解为什么当处理器一次只运行一条指令时,英特尔最初会实现代表指令,但现在使用它们有什么好处?
通过循环可以编译更多指令,还有更多要填充管道和/或无序发布.现代处理器是为优化这些重复前缀指令而构建的,还是在现代代码中很少使用的rep指令,它们对制造商来说并不重要?
英特尔内存模型保证:
http://bartoszmilewski.com/2008/11/05/who-ordered-memory-fences-on-an-x86/
我已经看到声称由于Intel内存模型,SFENCE在x86-64上是多余的,但从来没有LFENCE.上述内存模型规则是否使指令冗余?
该内部函数导说,只有这么多关于void _mm_prefetch (char const* p, int i):
从包含地址p的内存中获取数据行到由locality hint i指定的缓存层次结构中的位置.
你能列出int i参数的可能值并解释它们的含义吗?
我发现_MM_HINT_T0,_MM_HINT_T1,_MM_HINT_T2,_MM_HINT_NTA和_MM_HINT_ENTA,但我不知道这是否是一个详尽的列表和它们的含义.
如果特定于处理器,我想知道他们在Ryzen和最新的英特尔酷睿处理器上做了什么.
我阅读了"英特尔架构的英特尔优化指南指南".
但是,我仍然不知道何时应该使用
_mm_sfence()
_mm_lfence()
_mm_mfence()
Run Code Online (Sandbox Code Playgroud)
任何人都可以解释在编写多线程代码时何时应该使用它们?
指令如何rep stosb比这段代码执行得更快?
Clear: mov byte [edi],AL ; Write the value in AL to memory
inc edi ; Bump EDI to next byte in the buffer
dec ecx ; Decrement ECX by one position
jnz Clear ; And loop again until ECX is 0
Run Code Online (Sandbox Code Playgroud)
在所有现代CPU上都能保证这一点吗?我是否应该总是喜欢使用rep stosb而不是手动编写循环?
引用英特尔 ®64 和IA-32架构优化参考手册,§2.4.6"REP String Enhancement":
使用REP字符串的性能特征可归因于两个组件: 启动开销和数据传输吞吐量.
[...]
对于较大粒度数据传输的REP字符串,随着ECX值的增加,REP String的启动开销呈逐步增加:
- 短串(ECX <= 12):REP MOVSW/MOVSD/MOVSQ的延迟约为20个周期,
快速字符串(ECX> = 76:不包括REP MOVSB):处理器实现通过移动尽可能多的16字节数据来提供硬件优化.如果其中一个16字节数据传输跨越缓存行边界,则REP字符串延迟的延迟会有所不同:
- 无拆分:延迟包括大约40个周期的启动成本,每个64字节的数据增加4个周期,
- 高速缓存拆分:延迟包括大约35个周期的启动成本,每64个字节的数据增加6个周期.
中间字符串长度:REP MOVSW/MOVSD/MOVSQ的延迟具有大约15个周期的启动成本加上word/dword/qword中数据移动的每次迭代的一个周期.
(强调我的)
没有进一步提及这种启动成本.它是什么?它做了什么,为什么总是需要更多的时间?
常见的widsom 在执行相同的操作时rep movsb比rep movsd(或在64位上rep movsq)慢得多.但是,我已经在一些现代机器上进行了测试,并且在大量缓冲区大小(10字节到2兆)之间的运行时间相同(达到测量噪声).到目前为止,我刚刚在2台机器(32位Intel Atom D510和64位AMD FX 8120)上进行了测试.
是否有rep movsb比rep movsd(或rep movsq)更慢的现代x86(32位或64位)机器?
如果没有,那么差异显着的最后一台机器是什么,它有多重要?
我想从这个问题的角度来看这个问题是为了避免货物过多的一系列测试将记忆分解成未对齐的头/尾和对齐中间以便使用rep movsd或者rep movsq如果这样做没有实际的好处......
我们有一个简单的内存吞吐量基准.对于大块内存,它所做的只是重复记忆.
在几台不同的机器上查看结果(针对64位编译),Skylake机器的性能明显优于Broadwell-E,保持OS(Win10-64),处理器速度和RAM速度(DDR4-2133)不变.我们不是说几个百分点,而是大约2个因素.Skylake配置为双通道,Broadwell-E的结果不会因双/三/四通道而异.
任何想法为什么会这样?随后的代码在VS2015的Release中编译,并报告完成每个memcpy的平均时间:
64位:Skylake为2.2ms,Broadwell-E为4.5ms
32位:Skylake为2.2ms,Broadwell-E为3.5ms.
通过利用多个线程,我们可以在四通道Broadwell-E构建上获得更大的内存吞吐量,这很不错,但是看到单线程内存访问的这种巨大差异令人沮丧.为什么差异如此显着的任何想法?
我们还使用了各种基准测试软件,他们验证了这个简单示例所展示的内容 - 单线程内存吞吐量在Skylake上更好.
#include <memory>
#include <Windows.h>
#include <iostream>
//Prevent the memcpy from being optimized out of the for loop
_declspec(noinline) void MemoryCopy(void *destinationMemoryBlock, void *sourceMemoryBlock, size_t size)
{
memcpy(destinationMemoryBlock, sourceMemoryBlock, size);
}
int main()
{
const int SIZE_OF_BLOCKS = 25000000;
const int NUMBER_ITERATIONS = 100;
void* sourceMemoryBlock = malloc(SIZE_OF_BLOCKS);
void* destinationMemoryBlock = malloc(SIZE_OF_BLOCKS);
LARGE_INTEGER Frequency;
QueryPerformanceFrequency(&Frequency);
while (true)
{
LONGLONG total = 0;
LONGLONG max = 0;
LARGE_INTEGER StartingTime, …Run Code Online (Sandbox Code Playgroud) 让我们假设我们有一个函数,每个函数乘以两个1000000双精度数组.在C/C++中,函数如下所示:
void mul_c(double* a, double* b)
{
for (int i = 0; i != 1000000; ++i)
{
a[i] = a[i] * b[i];
}
}
Run Code Online (Sandbox Code Playgroud)
编译器生成以下程序集-O2:
mul_c(double*, double*):
xor eax, eax
.L2:
movsd xmm0, QWORD PTR [rdi+rax]
mulsd xmm0, QWORD PTR [rsi+rax]
movsd QWORD PTR [rdi+rax], xmm0
add rax, 8
cmp rax, 8000000
jne .L2
rep ret
Run Code Online (Sandbox Code Playgroud)
从上面的程序集看来,编译器似乎使用了SIMD指令,但每次迭代只会增加一倍.所以我决定在内联汇编中编写相同的函数,在那里我充分利用xmm0寄存器并一次乘以两个双精度:
void mul_asm(double* a, double* b)
{
asm volatile
(
".intel_syntax noprefix \n\t"
"xor rax, rax \n\t"
"0: \n\t"
"movupd …Run Code Online (Sandbox Code Playgroud) x86 ×7
assembly ×6
performance ×6
optimization ×5
c++ ×4
intrinsics ×2
atomic ×1
benchmarking ×1
cpu-cache ×1
intel ×1
memcpy ×1
pipeline ×1
prefetch ×1
simd ×1
x86-64 ×1