我正在修改AVX-2指令,我正在寻找一种快速计算__m256i单词中前导零数(具有256位)的方法.
到目前为止,我已经找到了以下方法:
// Computes the number of leading zero bits.
// Here, avx_word is of type _m256i.
if (!_mm256_testz_si256(avx_word, avx_word)) {
uint64_t word = _mm256_extract_epi64(avx_word, 0);
if (word > 0)
return (__builtin_clzll(word));
word = _mm256_extract_epi64(avx_word, 1);
if (word > 0)
return (__builtin_clzll(word) + 64);
word = _mm256_extract_epi64(avx_word, 2);
if (word > 0)
return (__builtin_clzll(word) + 128);
word = _mm256_extract_epi64(avx_word, 3);
return (__builtin_clzll(word) + 192);
} else
return 256; // word is entirely zero
Run Code Online (Sandbox Code Playgroud)
但是,我发现在256位寄存器中找出确切的非零字是相当笨拙的.
有人知道是否有更优雅(或更快)的方法吗?
正如附加信息:我实际上想要计算由逻辑AND创建的任意长向量的第一个设置位的索引,并且我将标准64位操作的性能与SSE和AVX-2代码进行比较.这是我的整个测试代码:
#include <stdio.h> …Run Code Online (Sandbox Code Playgroud) 在Intel和AMD x86_64处理器上,SIMD矢量化寄存器具有特定的融合乘法 - 加法功能,但通用(标量,整数)寄存器不具备 - 您基本上需要相乘,然后添加(除非您可以适应lea) .
这是为什么?我的意思是,它是否无用,以至于不值得开销?
Haswell及更早版本的ADC通常为2 uops,有2个周期延迟,因为Intel uops传统上只能有2个输入(https://agner.org/optimize/).在Haswell为FMA引入3输入微指令和某些情况下的索引寻址模式的微融合之后,Broadwell/Skylake及其后来都有单uop ADC/SBB/CMOV .
(但不适用于adc al, imm8短格式编码,或其他al/ax/eax/rax,imm8/16/32/32短格式,没有ModRM.我的答案中有更详细的说明.)
但是adc,即时0是特殊的Haswell解码为只有一个uop. @BeeOnRope测试了这个,并在他的uarch-bench中包含了对这个性能怪癖的检查:https://github.com/travisdowns/uarch-bench.从输出样本CI一个的Haswell服务器上示出之间的差adc reg,0和adc reg,1或adc reg,zeroed-reg.
(对于SBB也是如此.就我所见,在任何CPU上具有相同立即数的等效编码,ADC和SBB性能之间从来没有任何差别.)
这个优化何时adc bl,0推出?
我测试了Core 2 1,发现imm=0延迟是2个周期,相同adc eax,0.同时,也是循环计数是与吞吐量测试一些变化相同的adc eax,3对比0,所以第一代的Core 2(Conroe处理器/ Merom处理器)并没有这样做优化.
回答这个问题的最简单方法可能是在Sandybridge系统上使用我的测试程序,看看是否3比它快adc eax,0.但基于可靠文档的答案也可以.
(顺便说一句,如果有人可以访问Sandybridge上的perf计数器,你还可以通过运行@ BeeOnRope的测试代码来清除在执行uop计数不是处理器宽度倍数的循环时性能降低的谜团.或者是性能我在不再工作的SnB上观察到的只是因为未分层与正常的uops有什么不同?)
脚注1:我在运行Linux的Core 2 E6600(Conroe/Merom)上使用了这个测试程序.
;; NASM / YASM
;; assemble / link this …Run Code Online (Sandbox Code Playgroud) 众所周知,Intel x86_64 处理器不仅是流水线架构,而且还是超标量架构。
这意味着 CPU 可以:
流水线- 在一个时钟下,执行一个操作的某些阶段。例如,与阶段转移并行的两个 ADD:
超标量- 在一个时钟上,执行一些不同的操作。例如,在同一阶段并行 ADD 和 MUL:

这是可能的,因为处理器有多个指令调度程序(英特尔酷睿有 4 个简单解码器)。
但是只有调度程序(4个简单解码器)的副本,还是算术单元的副本?
例如,我们可以在相同的阶段执行两个 ADD,但是在同一个 CPU 内核上的独立算术单元(例如,端口 0 上的 ALU 和端口 1上的ALU)?
是否有任何执行单元的副本,可以在同一个时钟执行两条相同的指令?
假设我有以下主循环
.L2:
vmulps ymm1, ymm2, [rdi+rax]
vaddps ymm1, ymm1, [rsi+rax]
vmovaps [rdx+rax], ymm1
add rax, 32
jne .L2
Run Code Online (Sandbox Code Playgroud)
我想时间的方式是把它放在另一个像这样的长循环中
;align 32
.L1:
mov rax, rcx
neg rax
align 32
.L2:
vmulps ymm1, ymm2, [rdi+rax]
vaddps ymm1, ymm1, [rsi+rax]
vmovaps [rdx+rax], ymm1
add rax, 32
jne .L2
sub r8d, 1 ; r8 contains a large integer
jnz .L1
Run Code Online (Sandbox Code Playgroud)
我发现的是我选择的对齐方式会对时序产生重大影响(最高可达+ -10%).我不清楚如何选择代码对齐方式.我可以想到三个地方,我可能想要对齐代码
triad_fma_asm_repeat下面的代码中).L1上面)重复我的主循环.L2上图). 我发现的另一件事是,如果我在源文件中放入另一个例程,即更改一条指令(例如删除指令),即使它们是独立函数,也会对下一个函数的时序产生重大影响.我甚至在过去看到过影响另一个目标文件中的例程.
我在Agner Fog的优化装配手册中阅读了第11.5节"代码对齐",但我仍然不清楚调整代码以测试性能的最佳方法.他给出了一个例子,11.5,计时内循环,我并没有真正遵循.
目前,从我的代码中获得最高性能是一种猜测不同值和对齐位置的游戏.
我想知道是否有一种智能方法可以选择对齐方式?我应该对齐内圈和外圈吗?只是内循环?该功能的入口?使用短期或长期NOP是否重要?
我最感兴趣的是Haswell,其次是SNB/IVB,然后是Core2.
我尝试了NASM和YASM,并发现这是一个显着不同的领域.NASM仅插入一个字节的NOP指令,其中YASM插入多字节NOP.例如,通过将上面的内部和外部循环对齐到32字节,NASM插入20条NOP(0x90)指令,其中YASM插入以下内容(来自objdump)
2c: 66 …Run Code Online (Sandbox Code Playgroud) vpclmulqdq指令有四个操作数,pclmulqdq有三个操作数,所以我认为vpclmulqdq可以用 代替movdqa + pclmulqdq,但实验结果变慢了。
但是当我使用vpaddd而不是movdqa + paddd,我得到更快的结果。所以我对这个问题感到困惑。代码使用如下paddd指令:
movdqa %xmm0, %xmm8 # slower
movdqa %xmm0, %xmm9
movdqa %xmm0, %xmm10
movdqa %xmm0, %xmm11
paddd (ONE), %xmm8
paddd (TWO), %xmm9
paddd (THREE), %xmm10
paddd (FOUR), %xmm11
vpaddd (ONE), %xmm0, %xmm8 # faster
vpaddd (TWO), %xmm0, %xmm9
vpaddd (THREE), %xmm0, %xmm10
vpaddd (FOUR), %xmm0, %xmm11
Run Code Online (Sandbox Code Playgroud)
代码使用 pclmulqdq 指令,如:
movdqa %xmm15, %xmm1 # faster
pclmulqdq $0x00, (%rbp), %xmm1
aesenc 16(%r15), %xmm8
aesenc …Run Code Online (Sandbox Code Playgroud) 我想知道是否有任何英特尔专家可以告诉我STD和STA在英特尔Skylake内核方面的区别。
在英特尔优化指南中,有一张图片描述了英特尔酷睿的“超标量端口”。
这是PDF。图片在第40页上。
。
这是第78页的另一张图片,该图片描述了“存储地址”和“存储数据”:
使用存储的数据地址准备存储转发和存储退出逻辑。
准备存储转发和存储退出逻辑以及要存储的数据。
考虑到Skylake可以在每个时钟周期执行一次#1 3x,但是在每个时钟周期只能执行一次#2,我很好奇这两者之间的区别。
在我看来,将存储转发到数据地址是“自然的”。但是我无法理解何时进行数据存储转发(又名:STD /端口4)。是否有任何组装/优化专家可以帮助我准确了解STD和STA之间的区别?
我熟悉这种形式的内存引用:
XXX ptr [base + index * size + displacement]
Run Code Online (Sandbox Code Playgroud)
其中XXX是一些尺寸(字节/字/ DWORD /等),两者base和index是寄存器,size为二的小功率,并且displacement是带符号的值。
amd64 引入了 rip 相对寻址。据我了解,我应该可以rip用作基址寄存器。但是,当我使用 clang-900.0.39.2 尝试此操作时:
mov r8b, byte ptr [rip + rdi * 1 + Lsomething]
Run Code Online (Sandbox Code Playgroud)
我得到:
错误:无效的基数+索引表达式
Run Code Online (Sandbox Code Playgroud)mov r8b, byte ptr [rip + rdi * 1 + Lsomething]
rip用作基址寄存器时是否不能使用索引寄存器?我是否必须使用lea计算rip + Lsomething然后抵消它?
考虑以下代码:
void foo(int* __restrict__ a)
{
int i; int val = 0;
for (i = 0; i < 100; i++) {
val = 2 * i;
a[i] = val;
}
}
Run Code Online (Sandbox Code Playgroud)
这符合(具有最大优化但没有展开或矢量化)...
海湾合作委员会 7.2:
foo(int*):
xor eax, eax
.L2:
mov DWORD PTR [rdi], eax
add eax, 2
add rdi, 4
cmp eax, 200
jne .L2
rep ret
Run Code Online (Sandbox Code Playgroud)
铿锵5.0:
foo(int*): # @foo(int*)
xor eax, eax
.LBB0_1: # =>This Inner Loop Header: Depth=1
mov dword ptr [rdi + 2*rax], eax …Run Code Online (Sandbox Code Playgroud) 我尝试编写一些函数来使用单个矩阵和源向量数组来执行矩阵向量乘法。我曾经用 C++ 编写过这些函数,并在 x86 AVX512 汇编中编写过一次,以将性能与英特尔 VTune Profiler 进行比较。当使用源向量数组作为目标数组时,汇编变体的执行速度比 C++ 对应版本快 3.5 倍到 10x\xc2\xa0,但是当使用不同的源和目标数组时,汇编变体的性能几乎不比 C++ 对应版本更好,实现几乎相同的性能...有时甚至更糟。
\n我无法理解的另一件事是,为什么在使用不同的源和目标数组时,C++ 对应项甚至可以达到与汇编变体接近相同或更好的性能水平,即使汇编代码要短得多并且也根据静态分析工具 uica 和 llvm-mca 速度提高数倍。uica.uops.info
\n我不想让这篇文章变得太长,所以我只发布执行 mat4-vec4 乘法的函数的代码。
\n这是汇编变体的代码,它假设矩阵要转置:
\nalignas(64) uint32_t mat4_mul_vec4_avx512_vpermps_index[64]{ 0, 0, 0, 0, 4, 4, 4, 4, 8, 8, 8, 8, 12, 12, 12, 12,\n 1, 1, 1, 1, 5, 5, 5, 5, 9, 9, 9, 9, 13, 13, 13, 13,\n 2, 2, 2, 2, 6, 6, 6, 6, 10, 10, 10, 10, 14, 14, …Run Code Online (Sandbox Code Playgroud)