LOOP(英特尔参考手动输入)递减ecx/rcx,然后如果非零则跳转.这很慢,但是英特尔不能廉价地把它变得很快吗? dec/jnz已经将宏观融合成 Sandybridge家族的一个 uop; 唯一的区别是设置标志.
loop关于各种微体系结构,来自Agner Fog的说明表:
Bulldozer-family/Ryzen:1 m-op(与宏观融合测试和分支相同,或者jecxz)
P4:4次(相同jecxz)
loope/ loopne).吞吐量= 4c(loop)或7c(loope/ne).loope/ loopne). 吞吐量=每5个循环一个,这是将循环计数器保留在内存中的瓶颈!jecxz只有2 uops,吞吐量与普通吞吐量相同jcc难道解码器不能像lea rcx, [rcx-1]/ 那样解码jrcxz吗?这将是3 uops.至少那是没有地址大小前缀的情况,否则它必须使用ecx和截断RIP,EIP如果跳转; 也许奇怪的地址大小选择控制减量的宽度解释了许多uops?
或者更好,只需将其解码为不设置标志的融合分支和分支? dec ecx …
我一直在做一些项目Euler练习,以提高我对C++的知识.
我写了以下函数:
int a = 0,b = 0,c = 0;
for (a = 1; a <= SUMTOTAL; a++)
{
for (b = a+1; b <= SUMTOTAL-a; b++)
{
c = SUMTOTAL-(a+b);
if (c == sqrt(pow(a,2)+pow(b,2)) && b < c)
{
std::cout << "a: " << a << " b: " << b << " c: "<< c << std::endl;
std::cout << a * b * c << std::endl;
}
}
}
Run Code Online (Sandbox Code Playgroud)
这计算在17毫秒.
但是,如果我改变了这条线
if (c == sqrt(pow(a,2)+pow(b,2)) && b < …Run Code Online (Sandbox Code Playgroud) 我使用英特尔®架构代码分析器(IACA)发现了一些意想不到的东西(对我而言).
以下指令使用[base+index]寻址
addps xmm1, xmmword ptr [rsi+rax*1]
Run Code Online (Sandbox Code Playgroud)
根据IACA没有微熔丝.但是,如果我用[base+offset]这样的
addps xmm1, xmmword ptr [rsi]
Run Code Online (Sandbox Code Playgroud)
IACA报告它确实融合了.
英特尔优化参考手册的第2-11节给出了以下"可以由所有解码器处理的微融合微操作"的示例
FADD DOUBLE PTR [RDI + RSI*8]
Run Code Online (Sandbox Code Playgroud)
和Agner Fog的优化装配手册也给出了使用[base+index]寻址的微操作融合的例子.例如,请参见第12.2节"Core2上的相同示例".那么正确的答案是什么?
我有一个非常奇怪的编译器行为,其中G ++将计算拉入热循环,严重降低了生成的代码的性能.这里发生了什么?
考虑这个功能:
#include <cstdint>
constexpr bool noLambda = true;
void funnyEval(const uint8_t* columnData, uint64_t dataOffset, uint64_t dictOffset, int32_t iter, int32_t limit, int32_t* writer,const int32_t* dictPtr2){
// Computation X1
const int32_t* dictPtr = reinterpret_cast<const int32_t*>(columnData + dictOffset);
// Computation X2
const uint16_t* data = (const uint16_t*)(columnData + dataOffset);
// 1. The less broken solution without lambda
if (noLambda) {
for (;iter != limit;++iter){
int32_t t=dictPtr[data[iter]];
*writer = t;
writer++;
}
}
// 2. The totally broken solution with lambda
else …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 …
我目前编码的一些C99标准库字符串函数高度优化的版本,例如strlen(),memset()等等,采用x86-64的组件,SSE-2指令.
到目前为止,我已经在性能方面取得了很好的成绩,但是当我尝试优化更多时,我有时会遇到奇怪的行为.
例如,添加或甚至删除一些简单的指令,或者只是重新组织一些用于跳转的本地标签会完全降低整体性能.在代码方面绝对没有理由.
所以我的猜测是代码对齐存在一些问题,和/或有错误预测的分支.
我知道,即使使用相同的架构(x86-64),不同的CPU也有不同的分支预测算法.
但是,在开发x86-64的高性能时,是否存在一些关于代码对齐和分支预测的一般建议?
特别是关于对齐,我应该确保跳转指令使用的所有标签都在DWORD上对齐吗?
_func:
; ... Some code ...
test rax, rax
jz .label
; ... Some code ...
ret
.label:
; ... Some code ...
ret
Run Code Online (Sandbox Code Playgroud)
在前面的代码中,我之前应该使用align指令.label:,例如:
align 4
.label:
Run Code Online (Sandbox Code Playgroud)
如果是这样,使用SSE-2时是否足以对齐DWORD?
关于分支预测,是否有一种"优先"的方式来组织跳转指令使用的标签,以帮助CPU,或者今天的CPU是否足够聪明,可以通过计算分支的计数来确定在运行时?
编辑
好的,这是一个具体的例子 - 这是strlen()SSE-2 的开始:
_strlen64_sse2:
mov rsi, rdi
and rdi, -16
pxor xmm0, xmm0
pcmpeqb xmm0, [ rdi ]
pmovmskb rdx, xmm0
; ...
Run Code Online (Sandbox Code Playgroud)
使用1000个字符串运行10'000'000次约为0.48秒,这很好.
但它不会检查NULL字符串输入.显然,我会添加一个简单的检查:
_strlen64_sse2:
test rdi, rdi
jz .null
; ... …Run Code Online (Sandbox Code Playgroud) 我一直看到人们声称MOV指令可以在x86中免费,因为寄存器重命名.
对于我的生活,我无法在一个测试用例中验证这一点.每个测试用例我尝试揭穿它.
例如,这是我用Visual C++编译的代码:
#include <limits.h>
#include <stdio.h>
#include <time.h>
int main(void)
{
unsigned int k, l, j;
clock_t tstart = clock();
for (k = 0, j = 0, l = 0; j < UINT_MAX; ++j)
{
++k;
k = j; // <-- comment out this line to remove the MOV instruction
l += j;
}
fprintf(stderr, "%d ms\n", (int)((clock() - tstart) * 1000 / CLOCKS_PER_SEC));
fflush(stderr);
return (int)(k + j + l);
}
Run Code Online (Sandbox Code Playgroud)
这为循环生成以下汇编代码(随意生成这个你想要的;你显然不需要Visual C++):
LOOP:
add edi,esi
mov …Run Code Online (Sandbox Code Playgroud) 我发现了
mov al, bl
mov ah, bh
Run Code Online (Sandbox Code Playgroud)
比...快得多
mov ax, bx
Run Code Online (Sandbox Code Playgroud)
谁能解释我为什么?我在Windows XP下以32位模式运行Core 2 Duo 3 Ghz.使用NASM进行编译,然后与VS2010进行链接.Nasm编译命令:
nasm -f coff -o triangle.o triangle.asm
Run Code Online (Sandbox Code Playgroud)
这是我用来渲染三角形的主循环:
; some variables on stack
%define cr DWORD [ebp-20]
%define dcr DWORD [ebp-24]
%define dcg DWORD [ebp-32]
%define dcb DWORD [ebp-40]
loop:
add esi, dcg
mov eax, esi
shr eax, 8
add edi, dcb
mov ebx, edi
shr ebx, 16
mov bh, ah
mov eax, cr
add eax, dcr
mov cr, eax
mov ah, bh ; …Run Code Online (Sandbox Code Playgroud) 我发现这个主题为什么处理排序数组比未排序数组更快?.并尝试运行此代码.而且我发现了奇怪的行为.如果我使用-O3优化标志编译此代码,则需要2.98605 sec运行.如果我用-O2它编译1.98093 sec.我尝试在同一环境中的同一台机器上运行此代码几次(5或6),我关闭所有其他软件(chrome,skype等).
gcc --version
gcc (Ubuntu 4.9.2-0ubuntu1~14.04) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Run Code Online (Sandbox Code Playgroud)
那么请你能解释一下为什么会这样吗?我阅读gcc手册,我看到-O3包括-O2.谢谢你的帮助.
PS添加代码
#include <algorithm>
#include <ctime>
#include <iostream>
int main()
{
// Generate data
const unsigned arraySize = 32768;
int data[arraySize];
for (unsigned …Run Code Online (Sandbox Code Playgroud) 我决定要对特定函数进行基准测试,所以我天真地编写如下代码:
#include <ctime>
#include <iostream>
int SlowCalculation(int input) { ... }
int main() {
std::cout << "Benchmark running..." << std::endl;
std::clock_t start = std::clock();
int answer = SlowCalculation(42);
std::clock_t stop = std::clock();
double delta = (stop - start) * 1.0 / CLOCKS_PER_SEC;
std::cout << "Benchmark took " << delta << " seconds, and the answer was "
<< answer << '.' << std::endl;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
一位同事指出,我应该声明start和stop变量volatile以避免代码重新排序.他建议优化器可以,例如,有效地重新排序代码,如下所示:
std::clock_t start = std::clock();
std::clock_t stop = …Run Code Online (Sandbox Code Playgroud)