我为Project Euler Q14编写了这两个解决方案,在汇编和C++中.它们是用于测试Collatz猜想的相同蛮力方法.装配解决方案与组装
nasm -felf64 p14.asm && gcc p14.o -o p14
Run Code Online (Sandbox Code Playgroud)
C++是用.编译的
g++ p14.cpp -o p14
Run Code Online (Sandbox Code Playgroud)
部件, p14.asm
section .data
fmt db "%d", 10, 0
global main
extern printf
section .text
main:
mov rcx, 1000000
xor rdi, rdi ; max i
xor rsi, rsi ; i
l1:
dec rcx
xor r10, r10 ; count
mov rax, rcx
l2:
test rax, 1
jpe even
mov rbx, 3
mul rbx
inc rax
jmp c1
even:
mov rbx, 2 …Run Code Online (Sandbox Code Playgroud) xor eax, eax将永远设置eax为零,对吗?那么,为什么MSVC++有时会把它放在我的可执行代码中呢?这样效率更高mov eax, 0吗?
012B1002 in al,dx
012B1003 push ecx
int i = 5;
012B1004 mov dword ptr [i],5
return 0;
012B100B xor eax,eax
Run Code Online (Sandbox Code Playgroud)
另外,这意味着什么in al, dx?
我正在寻找一种有效的方法来确定在整数中设置的最低有效位的位置,例如对于0x0FF0,它将是4.
这是一个简单的实现:
unsigned GetLowestBitPos(unsigned value)
{
assert(value != 0); // handled separately
unsigned pos = 0;
while (!(value & 1))
{
value >>= 1;
++pos;
}
return pos;
}
Run Code Online (Sandbox Code Playgroud)
任何想法如何挤出一些周期?
(注意:这个问题适合喜欢这类事情的人,而不是人们告诉我xyzoptimization是邪恶的.)
[编辑] 感谢大家的想法!我也学到了其他一些东西.凉!
在x86-64 Tour of Intel Manuals中,我读到了
也许最令人惊讶的事实是,诸如
MOV EAX, EBX自动将指令的高32位归零的指令RAX.
同一来源引用的英特尔文档(3.4.1.1 64位手动基本架构中的通用寄存器)告诉我们:
- 64位操作数在目标通用寄存器中生成64位结果.
- 32位操作数生成32位结果,在目标通用寄存器中零扩展为64位结果.
- 8位和16位操作数生成8位或16位结果.目标通用寄存器的高56位或48位(分别)不会被操作修改.如果8位或16位操作的结果用于64位地址计算,则将寄存器显式符号扩展为完整的64位.
在x86-32和x86-64汇编中,16位指令如
mov ax, bx
Run Code Online (Sandbox Code Playgroud)
不要表现出这种"奇怪"的行为,即eax的上层词被归零.
因此:引入这种行为的原因是什么?乍一看似乎不合逻辑(但原因可能是我习惯了x86-32汇编的怪癖).
鉴于此代码:
#include <string.h>
int equal4(const char* a, const char* b)
{
return memcmp(a, b, 4) == 0;
}
int less4(const char* a, const char* b)
{
return memcmp(a, b, 4) < 0;
}
Run Code Online (Sandbox Code Playgroud)
x86_64上的GCC 7引入了第一种情况的优化(Clang已经做了很长时间):
mov eax, DWORD PTR [rsi]
cmp DWORD PTR [rdi], eax
sete al
movzx eax, al
Run Code Online (Sandbox Code Playgroud)
但第二种情况仍然是memcmp():
sub rsp, 8
mov edx, 4
call memcmp
add rsp, 8
shr eax, 31
Run Code Online (Sandbox Code Playgroud)
是否可以对第二种情况应用类似的优化?什么是最好的装配,有没有明确的理由为什么它没有完成(由GCC或Clang)?
在Godbolt的Compiler Explorer上看到它:https://godbolt.org/g/jv8fcf
我知道这movzx可以用于打破依赖关系,但我偶然发现了movzxClang 和 GCC 的一些用途,我真的看不出它们有什么用处。这是我在 Godbolt 编译器浏览器上尝试的一个简单示例:
#include <stdint.h>
int add2bytes(uint8_t* a, uint8_t* b) {
return uint8_t(*a + *b);
}
Run Code Online (Sandbox Code Playgroud)
与海湾合作委员会 12 -O3:
add2bytes(unsigned char*, unsigned char*):
movzx eax, BYTE PTR [rsi]
add al, BYTE PTR [rdi]
movzx eax, al
ret
Run Code Online (Sandbox Code Playgroud)
如果我理解正确的话,这里的第一个movzx打破了对先前eax值的依赖,但第二个是什么movzx做什么?我认为它不会破坏任何依赖关系,也不应该影响结果。
使用 clang 14 -O3,情况更加奇怪:
add2bytes(unsigned char*, unsigned char*): # @add2bytes(unsigned char*, unsigned char*)
mov al, byte ptr [rsi]
add al, byte ptr [rdi]
movzx eax, al
ret …Run Code Online (Sandbox Code Playgroud) 在x86上有两种众所周知的方法可以将整数寄存器设置为零值.
或
mov reg, 0
Run Code Online (Sandbox Code Playgroud)
要么
xor reg, reg
Run Code Online (Sandbox Code Playgroud)
有一种观点认为第二种变体更好,因为值0没有存储在代码中并且节省了几个字节的生成的机器代码.这绝对是好的 - 使用较少的指令缓存,这有时可以实现更快的代码执行.许多编译器生成这样的代码.
然而,在xor指令和改变相同寄存器的早期指令之间正式存在指令间依赖性.由于存在依赖性,后一条指令需要等到前者完成,这可能会减少处理器单元的负载并损害性能.
add reg, 17
;do something else with reg here
xor reg, reg
Run Code Online (Sandbox Code Playgroud)
很明显,无论初始寄存器值如何,xor的结果都将完全相同.但是处理器能够识别出这个吗?
我在VC++ 7中尝试了以下测试:
const int Count = 10 * 1000 * 1000 * 1000;
int _tmain(int argc, _TCHAR* argv[])
{
int i;
DWORD start = GetTickCount();
for( i = 0; i < Count ; i++ ) {
__asm {
mov eax, 10
xor eax, eax
};
}
DWORD diff = GetTickCount() - start;
start = …Run Code Online (Sandbox Code Playgroud) 我承认这个问题的答案可能是“一些非常具体的魔法”,但我对在这里观察到的情况感到有点震惊。我想知道是否有人了解这些类型的优化是如何工作的。我发现编译器的设计非常有趣,我真的无法想象它是如何工作的。我确信答案就在 clang 源代码中的某个地方,但我什至不知道该在哪里查找。
我是大学课程的助教,最近有人要求我帮助解决一个简单的家庭作业问题。这让我走上了一条有趣的道路......
问题很简单:在 x86_64 汇编中,编写一个给定(正)整数 n 返回的函数1^2 + 2^2 + 3^2 + ... + n^2。
我决定尝试一下,在帮助他们在 x86_64 汇编中编写此代码后,我(拥有一台 M1 macbook)决定看看是否可以在 arm64 汇编中创建一个不错的解决方案。我想出了一个相对简单直接的解决方案:
_sum_squares:
mov x1, x0 ; Do multiplication from x1
mov x0, xzr ; Clear x0
Lloop:
; x0 <-- (x1 * x1) + x0
madd x0, x1, x1, x0
; Loop until x1 == 0
subs x1, x1, #1
bne Lloop
ret
Run Code Online (Sandbox Code Playgroud)
(我希望有某种很好的方法可以--x1 == 0在一条指令中进行分支,但我想不出任何方法)
注意:任何基础数论课程中都有一个简单的公式,即[n(n + 1)(2n + 1)] / …
在过去的3到5年里,我一直在C和CPython工作.考虑一下我的知识基础.
如果我要使用汇编指令(如MOV AL, 61h支持它的处理器),处理器内部究竟是什么解释此代码并将其作为电压信号发送?如何进行如此简单的指导?
当我试着想到包含MOV AL, 61h甚至包含的众多步骤时,大会甚至感觉像是一种高级语言XOR EAX, EBX.
编辑:我读了一些评论,询问为什么我把它作为嵌入式,当x86系列在嵌入式系统中不常见时.欢迎来到我自己的无知.现在我想,如果我对此一无所知,也有可能其他人也对此一无所知.
考虑到你们在答案中付出的努力,我很难选择一个最喜欢的答案,但我觉得有必要做出决定.没有伤害感情,伙计们.
我经常发现,我对计算机的了解越多,我就越不会意识到我真正知道的.感谢您对微码和晶体管逻辑的开放态度!
编辑#2:感谢这个帖子,我刚刚理解了为什么XOR EAX, EAX比这更快MOV EAX, 0h.:)