我一直在寻找最快的方法来处理popcount大数据.我遇到了一个很奇怪的效果:改变从循环变量unsigned至uint64_t50%在我的电脑上所做的性能下降.
#include <iostream>
#include <chrono>
#include <x86intrin.h>
int main(int argc, char* argv[]) {
using namespace std;
if (argc != 2) {
cerr << "usage: array_size in MB" << endl;
return -1;
}
uint64_t size = atol(argv[1])<<20;
uint64_t* buffer = new uint64_t[size/8];
char* charbuffer = reinterpret_cast<char*>(buffer);
for (unsigned i=0; i<size; ++i)
charbuffer[i] = rand()%256;
uint64_t count,duration;
chrono::time_point<chrono::system_clock> startP,endP;
{
startP = chrono::system_clock::now();
count = 0;
for( unsigned k = 0; k < …Run Code Online (Sandbox Code Playgroud) 我试图比较内联汇编语言和C++代码的性能,所以我写了一个函数,添加两个大小为2000的数组,持续100000次.这是代码:
#define TIMES 100000
void calcuC(int *x,int *y,int length)
{
for(int i = 0; i < TIMES; i++)
{
for(int j = 0; j < length; j++)
x[j] += y[j];
}
}
void calcuAsm(int *x,int *y,int lengthOfArray)
{
__asm
{
mov edi,TIMES
start:
mov esi,0
mov ecx,lengthOfArray
label:
mov edx,x
push edx
mov eax,DWORD PTR [edx + esi*4]
mov edx,y
mov ebx,DWORD PTR [edx + esi*4]
add eax,ebx
pop edx
mov [edx + esi*4],eax
inc esi
loop label
dec edi …Run Code Online (Sandbox Code Playgroud) 以下所有说明都做同样的事情:设置%eax为零.哪种方式最佳(需要最少的机器周期)?
xorl %eax, %eax
mov $0, %eax
andl $0, %eax
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) 我一直试图很好地掌握x86汇编语言,并且想知道是否存在快速和短暂的等价物movl $1, %eax.就在那时,我认为语言中经常使用的习语列表可能是个好主意.
这可能包括与... xorl %eax, %eax相对movl $0, %eax或testl %eax, %eax反对的首选使用cmpl $0, %eax.
哦,并且每个帖子发布一个例子!
我在一篇博客文章中读到,最近的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; 我不确定这些因素有多重要.
快速提问,事先假设
mov eax, 0
Run Code Online (Sandbox Code Playgroud)
哪个更有效率?
inc eax
inc eax
Run Code Online (Sandbox Code Playgroud)
要么
add eax, 2
Run Code Online (Sandbox Code Playgroud)
另外,如果两个incs更快,编译器(比方说,GCC)通常(即没有积极的优化标志)是否优化var += 2呢?
谢谢你的时间!
PS:不要费心回答"不要过早优化",这仅仅是学术兴趣.
我们不时要分析汇编程序代码(IA32),而且我经常会遇到如下所示的指令:
xor ax, ax
Run Code Online (Sandbox Code Playgroud)
或与其他寄存器藏汉:xor dx, dx,xor al, al,...
这到底是做什么的?(ax xor ax总是给0?)
我正在研究代理和带有引用类型参数的泛型类,它非常慢.特别是对于通用方法(对于刚刚返回null的普通泛型方法,大约400毫秒对3200毫秒).我决定尝试看看如果我在C#中重写生成的类,它会如何执行,并且它表现得更好,与非泛型类代码的性能相同.
这是我写的C#类::(注意我通过命名方案改变但不是很多)::
namespace TestData
{
public class TestClassProxy<pR> : TestClass<pR>
{
private InvocationHandler<Func<TestClass<pR>, object>> _0_Test;
private InvocationHandler<Func<TestClass<pR>, pR, GenericToken, object>> _1_Test;
private static readonly InvocationHandler[] _proxy_handlers = new InvocationHandler[] {
new InvocationHandler<Func<TestClass<pR>, object>>(new Func<TestClass<pR>, object>(TestClassProxy<pR>.s_0_Test)),
new GenericInvocationHandler<Func<TestClass<pR>, pR, GenericToken, object>>(typeof(TestClassProxy<pR>), "s_1_Test") };
public TestClassProxy(InvocationHandler[] handlers)
{
if (handlers == null)
{
throw new ArgumentNullException("handlers");
}
if (handlers.Length != 2)
{
throw new ArgumentException("Handlers needs to be an array of 2 parameters.", "handlers");
}
this._0_Test = (InvocationHandler<Func<TestClass<pR>, object>>)(handlers[0] ?? _proxy_handlers[0]);
this._1_Test …Run Code Online (Sandbox Code Playgroud) 1.
TEST EAX,EAX
JNZ SHORT program.00407190
Run Code Online (Sandbox Code Playgroud)
2.
XOR EAX,EAX
JNZ SHORT program.00407190
Run Code Online (Sandbox Code Playgroud)
如果我错了,请纠正我.谢谢!:)
根据我的理解到目前为止:
JNZ如果不等于零则跳跃,它将跳转或不跳,取决于是否ZF设置为1.如果是1,它就不会跳跃.否则,它会跳跃.
根据我对代码#1的理解,TEST EAX,EAX将检查它是否为零.如果它不等于零(ZF为0),它将跳转到地址00407190.
对于代码#2,
XOR EAX,EAX将EAX寄存器设置为0.是否设置了任何标志?如果没有,JNZ指令如何确定跳转?
最后,人们为什么要检查是否EAX为0?请帮助我更简单详细的解释,我还是初学者.
考虑以下代码:
#include <stdio.h>
void foo() {
printf("Hello world\n");
}
void bar() {
printf("Hello world");
}
Run Code Online (Sandbox Code Playgroud)
这两个函数产生的程序集是:
.LC0:
.string "Hello world"
foo():
mov edi, OFFSET FLAT:.LC0
jmp puts
bar():
mov edi, OFFSET FLAT:.LC0
xor eax, eax
jmp printf
Run Code Online (Sandbox Code Playgroud)
现在我知道puts 和 printf之间的区别,但我发现这很有趣,因为 gcc 能够内省 const char* 并确定是调用 printf 还是 puts。
另一个有趣的事情是bar,编译器将返回寄存器 ( eax)清零,即使它是一个void函数。为什么它在那里而不是在里面foo?
我假设编译器“内省了我的字符串”是否正确,或者对此有另一种解释?
我试图了解该程序的反汇编版本:
#include <stdio.h>
int main(){
int i=0;
printf("HELLO VIK");
return 0;
}
Run Code Online (Sandbox Code Playgroud)
gdb反汇编:
(gdb) disass main
Dump of assembler code for function main:
0x0000000100000ef0 <main+0>: push rbp
0x0000000100000ef1 <main+1>: mov rbp,rsp
0x0000000100000ef4 <main+4>: sub rsp,0x10
0x0000000100000ef8 <main+8>: mov DWORD PTR [rbp-0xc],0x0
0x0000000100000eff <main+15>: xor al,al
0x0000000100000f01 <main+17>: lea rcx,[rip+0x50] # 0x100000f58
0x0000000100000f08 <main+24>: mov rdi,rcx
0x0000000100000f0b <main+27>: call 0x100000f2c <dyld_stub_printf>
0x0000000100000f10 <main+32>: mov DWORD PTR [rbp-0x8],0x0
0x0000000100000f17 <main+39>: mov eax,DWORD PTR [rbp-0x8]
0x0000000100000f1a <main+42>: mov DWORD PTR [rbp-0x4],eax
0x0000000100000f1d <main+45>: …Run Code Online (Sandbox Code Playgroud)