在高级向量扩展(AVX)中,比较指令如_m256_cmp_ps,最后一个参数是比较谓词.谓词的选择压倒了我.它们似乎是类型,排序,信号的三重奏.例如_CMP_LE_OS是'小于或等于,有序,信令.
对于初学者来说,是否存在选择信令或非信令的性能原因,同样,有序或无序的速度比另一个更快?
什么'非信令'甚至意味着什么?我根本无法在文档中找到这个.任何关于何时选择什么的经验法则?
以下是avxintrin.h的谓词选择:
/* Compare */
#define _CMP_EQ_OQ 0x00 /* Equal (ordered, non-signaling) */
#define _CMP_LT_OS 0x01 /* Less-than (ordered, signaling) */
#define _CMP_LE_OS 0x02 /* Less-than-or-equal (ordered, signaling) */
#define _CMP_UNORD_Q 0x03 /* Unordered (non-signaling) */
#define _CMP_NEQ_UQ 0x04 /* Not-equal (unordered, non-signaling) */
#define _CMP_NLT_US 0x05 /* Not-less-than (unordered, signaling) */
#define _CMP_NLE_US 0x06 /* Not-less-than-or-equal (unordered, signaling) */
#define _CMP_ORD_Q 0x07 /* Ordered (nonsignaling) */
#define _CMP_EQ_UQ 0x08 /* Equal (unordered, non-signaling) */
#define …Run Code Online (Sandbox Code Playgroud) 从我的大学课程中,我听说,按照惯例,最好放置更多可能的条件if而不是in else,这可能有助于静态分支预测器.例如:
if (check_collision(player, enemy)) { // very unlikely to be true
doA();
} else {
doB();
}
Run Code Online (Sandbox Code Playgroud)
可以改写为:
if (!check_collision(player, enemy)) {
doB();
} else {
doA();
}
Run Code Online (Sandbox Code Playgroud)
我发现了一篇博客文章分支模式,使用GCC,它更详细地解释了这种现象:
为if语句生成前向分支.使它们不可能被采用的基本原理是处理器可以利用分支指令之后的指令可能已经被放置在指令单元内的指令缓冲器中的事实.
旁边,它说(强调我的):
在编写if-else语句时,总是使"then"块比else块更可能被执行,因此处理器可以利用已经放在指令获取缓冲区中的指令.
最终,有一篇由英特尔,分支和循环重组编写的文章,以防止错误预测,其中总结了两个规则:
当微处理器遇到分支时没有收集数据时使用静态分支预测,这通常是第一次遇到分支.规则很简单:
- 正向分支默认不采用
- 向后分支默认采用
为了有效地编写代码以利用这些规则,在编写if-else或switch语句时,首先检查最常见的情况,然后逐步处理最不常见的情况.
据我所知,这个想法是流水线CPU可以遵循指令缓存中的指令,而不会通过跳转到代码段内的另一个地址来破坏它.但是,我知道,在现代CPU微体系结构的情况下,这可能会过于简单化.
但是,看起来GCC不尊重这些规则.鉴于代码:
extern void foo();
extern void bar();
int some_func(int n)
{
if (n) {
foo();
}
else {
bar();
}
return 0; …Run Code Online (Sandbox Code Playgroud) 此循环在英特尔Conroe/Merom上每3个周期运行一次,imul按预期方式在吞吐量方面存在瓶颈.但是在Haswell/Skylake上,它每11个循环运行一次,显然是因为setnz al它依赖于最后一个循环imul.
; synthetic micro-benchmark to test partial-register renaming
mov ecx, 1000000000
.loop: ; do{
imul eax, eax ; a dep chain with high latency but also high throughput
imul eax, eax
imul eax, eax
dec ecx ; set ZF, independent of old ZF. (Use sub ecx,1 on Silvermont/KNL or P4)
setnz al ; ****** Does this depend on RAX as well as ZF?
movzx eax, al
jnz .loop ; }while(ecx);
Run Code Online (Sandbox Code Playgroud)
如果setnz al …
我很好奇有多少种方法可以在x86汇编中将寄存器设置为零.使用一条指令.有人告诉我,他设法找到了至少10种方法.
我能想到的是:
xor ax,ax
mov ax, 0
and ax, 0
Run Code Online (Sandbox Code Playgroud) 我遇到了一个没有 libc 的最小 HTTP 服务器:https : //github.com/Francesco149/nolibc-httpd
我可以看到定义了基本的字符串处理函数,导致write系统调用:
#define fprint(fd, s) write(fd, s, strlen(s))
#define fprintn(fd, s, n) write(fd, s, n)
#define fprintl(fd, s) fprintn(fd, s, sizeof(s) - 1)
#define fprintln(fd, s) fprintl(fd, s "\n")
#define print(s) fprint(1, s)
#define printn(s, n) fprintn(1, s, n)
#define printl(s) fprintl(1, s)
#define println(s) fprintln(1, s)
Run Code Online (Sandbox Code Playgroud)
基本的系统调用在 C 文件中声明:
size_t read(int fd, void *buf, size_t nbyte);
ssize_t write(int fd, const void *buf, size_t nbyte);
int open(const char *path, int flags); …Run Code Online (Sandbox Code Playgroud) 我现在正在参加一个装配课程,那个检查我们家庭作业的人是一个非常迂腐的老派优化狂.例如,如果他看到,他会扣除10%:
mov ax, 0
Run Code Online (Sandbox Code Playgroud)
代替:
xor ax,ax
Run Code Online (Sandbox Code Playgroud)
即使它只使用一次.
我不是一个完整的汇编程序初学者,但我不是一个优化专家,所以我需要你的帮助(可能是一个非常愚蠢的问题,但无论如何我都会问):如果我需要将寄存器值设置为1或(-1)最好使用:
mov ax, 1
Run Code Online (Sandbox Code Playgroud)
或做类似的事情:
xor ax,ax
inc ax
Run Code Online (Sandbox Code Playgroud)
我真的需要一个好成绩,所以我试图让它尽可能优化.(我需要优化时间和代码大小)
要清除所有位,您经常会看到一个独占或在XOR eax, eax.反过来也有这样的伎俩吗?
我能想到的是用额外的指令反转零.
有人在几年前向我展示了以下命令将变量归零.
xor i,i
Run Code Online (Sandbox Code Playgroud)
他告诉我,这比为它分配零要快.这是真的吗?编译器是否进行优化以使代码执行此类操作?
我对使用Linux上的系统调用进行汇编(非常)有基本的了解(我使用GNU汇编程序as).在Windows 7上,我使用GCC编译器套件的MinGW(32位)端口来生成汇编程序.在Linux上我经常使用C库在我的汇编程序中进行一些操作系统交互,在我的Windows平台上,使用MinGW也可以很好地工作.但是,有时我想使用低级系统调用 - 主要是为了使我的可执行文件尽可能小.在Linux上我知道如何做到这一点:
movl $0, %ebx
movl $1, %eax
int $0x80 ; exit with code 0
Run Code Online (Sandbox Code Playgroud)
我还使用这些系统调用来读取/写入终端的字符(例如,在EAX中编写带有4的系统调用).我想知道如何在Windows NT平台上执行此操作.可能吗?我查看了这个表,但我真的不明白系统调用的名称.欢迎任何帮助.
我在一篇博客文章中读到,最近的X86微体系结构也能够在寄存器重命名器中处理常见的寄存器归零习语(例如将寄存器与自身对齐); 用作者的话来说:
"寄存器重命名器也知道如何执行这些指令 - 它可以将寄存器本身归零."
有人知道这在实践中是如何运作的吗?我知道有些ISA,如MIPS,包含一个在硬件中始终设置为零的架构寄存器; 这是否意味着在内部,X86微体系结构内部具有类似的"零"寄存器,以便在方便时映射到寄存器?或者我的心智模型对于这些东西如何在微体系结构上工作不太正确?
我之所以要问的原因是(从一些观察中)看来mov,在一个循环中,从一个包含零的寄存器到一个目的地,仍然比在循环内通过xor将寄存器归零要快得多.
基本上它发生的是我想根据条件将循环内的寄存器归零; 这可以通过提前分配架构寄存器来存储零(%xmm3在这种情况下),在整个循环期间不进行修改,并在其中执行以下内容来完成:
Run Code Online (Sandbox Code Playgroud)movapd %xmm3, %xmm0
或者用xor技巧代替:
Run Code Online (Sandbox Code Playgroud)xorpd %xmm0, %xmm0
(AT&T语法).
换句话说,选择是在循环之外提升常数零或在每次迭代中将其重新物化在其中.后者将实时架构寄存器的数量减少一个,并且通过处理器假设的特殊情况感知和处理xor成语,它似乎应该像前者一样快(特别是因为这些机器具有更多的物理无论如何,寄存器都比体系结构寄存器更重要,所以它应该能够在内部完成与我在程序集中所做的相同的工作,通过在内部提升常数零甚至更好,完全意识和控制自己的资源).但它似乎不是,所以我很好奇是否有任何具有CPU架构知识的人可以解释是否有一个很好的理论原因.
在这种情况下,寄存器由SSE寄存器发生,机器恰好是Ivy Bridge; 我不确定这些因素有多重要.