对于现代硬件上的典型现代编译器,? :运算符是否会导致影响指令流水线的分支?
换句话说哪个更快,调用两个案例以避免可能的分支:
bool testVar = someValue(); // Used later.
purge(white);
purge(black);
Run Code Online (Sandbox Code Playgroud)
或选择实际需要清除的一个并且只与操作员一起做?::
bool testVar = someValue();
purge(testVar ? white : black);
Run Code Online (Sandbox Code Playgroud)
我知道你不知道purge()需要多长时间,但我只是在这里问一个关于我是否想要两次调用purge()来避免代码中可能的分支的一般性问题.
我意识到这是一个非常小的优化,并没有真正的区别,但仍然想知道.我希望?:不会导致分支,但希望确保我的理解是正确的.
这是我想出的一点微优化好奇心:
struct Timer {
bool running{false};
int ticks{0};
void step_versionOne(int mStepSize) {
if(running) ticks += mStepSize;
}
void step_versionTwo(int mStepSize) {
ticks += mStepSize * static_cast<int>(running);
}
};
Run Code Online (Sandbox Code Playgroud)
这两种方法似乎实际上做了同样的事情.第二个版本是否避免了分支(因此,比第一个版本更快),或者是否有任何编译器能够进行这种优化-O3?
我想用英特尔I64汇编程序做一些长整数数学运算(128位),需要创建一个2的补码.让我们说我的正面价值在于RDX:RAX.
2的补码是通过"翻转位并加1"来完成的.所以最天真的实现是(4条指令和14个字节的代码):
NOT RAX
NOT RDX
ADD RAX,1 ; Can't use INC, it doesn't set Carry
ADC RDX,0
Run Code Online (Sandbox Code Playgroud)
当我在RAX而不是NOT上使用NEG指令时,它对我来说是"+1"但是Carry是错误的,当RAX为零时NEG RAX清除了Carry,但是我需要携带JUST IN THIS CASE.所以下一个最好的方法可能是(4条指令和11个字节的代码):
NOT RDX
NEG RAX
CMC
ADC RDX,0 ; fixed, thanks lurker
Run Code Online (Sandbox Code Playgroud)
还有4条说明.但是不是加+1,我可以减去-1,因为SBB将Carry-Bit加到减数上,当Carry清零时我会加+1.所以我的下一个最好的尝试是这个,有3个指令和10个字节的代码:
NOT RDX
NEG RAX
SBB RDX,-1
Run Code Online (Sandbox Code Playgroud)
从我冗长的文字中可以看出,这一点并不明显.是否有一种更好,更易理解的方法来在汇编程序中进行级联2的补码?
使用此代码:
include Benchmark
n = 10**8
r = []
Benchmark.benchmark(" "*7 + CAPTION, 7, FORMAT, ">avg:", ">total:") do |b|
a = 1
r << b.report("Benchmark -1:") { (n).times do
-1 * a
end }
r << b.report("Benchmark - :") { (n).times do
-a
end }
[(r.sum{|e| e }) / 2, r.sum{|e| e }]
end
Run Code Online (Sandbox Code Playgroud)
我得到了这个结果ruby 2.1.5p273 (2014-11-13 revision 48405) [x86_64-linux]:
user system total real
Benchmark -1: 4.930000 0.000000 4.930000 ( 4.938359)
Benchmark - : 5.650000 0.000000 5.650000 ( …Run Code Online (Sandbox Code Playgroud) 可能这甚至都不是微观但纳米优化,但主题让我感兴趣,我想知道在长模式下使用非本机寄存器大小时是否存在任何惩罚?
我从各种来源了解到,部分寄存器更新(比如ax代替eax)会导致eflags停顿并降低性能.但我不确定长模式.对于此处理器操作模式,哪个寄存器大小被视为原生?x86-64仍然是x86架构的扩展,因此我相信32位仍然是原生的.还是我错了?
例如,像
sub eax, r14d
Run Code Online (Sandbox Code Playgroud)
要么
sub rax, r14
Run Code Online (Sandbox Code Playgroud)
具有相同的尺寸,但在使用其中任何一种时可能会有任何处罚吗?在如下连续指令中混合寄存器大小时可能会有任何处罚吗?(假设高dword在所有情况下均为零)
sub ecx, eax
sub r14, rax
Run Code Online (Sandbox Code Playgroud) 环境:
我没有写很多汇编程序代码,当我这么做时,它要么足够短,要么足够简单,以至于我不必担心压缩它的最大数量.我的更复杂的代码通常用C编写,我让编译器的优化器担心延迟,代码对齐等.
但是在我目前的项目中,MSVC的优化器在关键路径中的代码上做得非常糟糕.所以...
我还没有找到一个好的工具,可以对x64汇编代码进行静态或运行时分析,以便消除停顿,改善延迟等等.我所拥有的只是VS分析器,它告诉我(大致)哪些指令花了最多的时间.墙上的时钟告诉我最近的变化是否使事情变得更好或更糟.
作为替代方案,我一直在通过Agner的文档进行操作,希望能从我的代码中挤出一些更多的信息.问题是,在你理解了所有这些工作之前,很难理解他的任何工作.但它的一部分是有意义的,我正在尝试应用我学到的东西.
记住这一点,这里是我最内层循环的核心(不足为奇)是VS剖析器说我花费的时间:
nottop:
vpminub ymm2, ymm2, ymm3 ; reset out of range values
vpsubb ymm2, ymm2, ymm0 ; take a step
top:
vptest ymm2, ymm1 ; check for out of range values
jnz nottop
; Outer loop that does some math, does a "vpsubb ymm2, ymm2, ymm0",
; and eventually jumps back to top
Run Code Online (Sandbox Code Playgroud)
是的,这几乎是一个依赖链的教科书示例:这个紧密的小循环中的每个指令都取决于前一个操作的结果.这意味着没有并行性,这意味着我没有充分利用处理器.
受Agner的"优化汇编程序"文档的启发,我想出了一种方法(希望)允许我一次做2个操作,所以我可以有一个管道更新ymm2和另一个更新(比如说)ymm8.
虽然这是一个非平凡的变化,所以在我开始撕掉所有东西之前,我想知道它是否可能有所帮助.看看Agner的kaby lake(我的目标)的"指令表",我看到:
uops
each
port Latency
pminub …Run Code Online (Sandbox Code Playgroud) 在 ARM Cortex M3(类似于 STM32F101)上的实时应用程序¹中,我需要在尽可能紧密的循环中轮询一些内部外设寄存器直到它为零。我使用位带来访问适当的位。(工作)C 代码是
while (*(volatile uint32_t*)kMyBit != 0);
Run Code Online (Sandbox Code Playgroud)
该代码被复制到片上可执行 RAM 中。经过一些手动优化²,轮询循环下降到以下,我将³计时为 6 个周期:
0x00600200 681A LDR r2,[r3,#0x00]
0x00600202 2A00 CMP r2,#0x00
0x00600204 D1FC BNE 0x00600200
Run Code Online (Sandbox Code Playgroud)
如何降低投票的不确定性?5 个周期的循环符合我的目标:在它变为零后尽可能接近 15.5 个周期对同一位进行采样。
我的规范要求可靠地检测至少 6.5 个 CPU 时钟周期的低脉冲;如果持续时间少于 12.5 个周期,则可靠地将其归类为短;如果它持续超过 18.5 个周期,就可以可靠地对其进行分类。脉冲与 CPU 时钟没有确定的相位关系,这是我唯一准确的时序参考。这需要最多 5 个时钟的轮询循环。实际上,我正在模拟在几十年前的 8 位 CPU 上运行的代码,该 CPU 可以以 5 个时钟周期进行轮询,而这已成为规范。
我试图通过在循环之前插入 NOP 来抵消代码对齐,在我尝试过的许多变体中,但从未观察到任何变化。
我试图反转 CMP 和 LDR,但仍然得到 6 个周期:
0x00600200 681A LDR r2,[r3,#0x00]
; we loop here
0x00600202 2A00 CMP r2,#0x00
0x00600204 681A LDR r2,[r3,#0x00]
0x00600206 …Run Code Online (Sandbox Code Playgroud) 基本上,正如标题所说。我想知道一种计算方法floor(log2(x / y)),其中x和y是非零无符号机器整数,在尽可能少的周期内(尽可能避免使用分支,内存带宽,除法等在微小部分中很昂贵的东西像这样的代码)。这里需要准确的(整数)答案。我在考虑如何通过有效地计算来优化Adaptive Shivers Sort的外循环,因为它需要计算floor(log2(r / c)),其中r是c算法的运行长度和元参数;假设x <= y适用于此类离线版本的解决方案,其中c 被选择为等于输入的长度,但通用解决方案在其他设置中可能有用。
您可以假设使用PopCountand CountLeadingZeros/CountTrailingZeros、常见的 SSE 样式指令,甚至浮点计算——但它需要是处理器可以在短短几个周期内完成的事情。
有没有优化此代码(x86-64)的好方法?
mov dword ptr[rsp], 0;
mov dword ptr[rsp+4], 0
Run Code Online (Sandbox Code Playgroud)
其中立即数可以是任何值,不一定是零,但在这种情况下总是立即数。
原来的那对店还慢吗?硬件中的写入组合和 ?ops 的并行操作可能会使一切变得非常快?我想知道有没有问题可以解决。
我正在考虑类似的事情(不知道以下说明是否存在)
mov qword ptr[rsp], 0
Run Code Online (Sandbox Code Playgroud)
或者
mov eax, 0;
mov qword ptr[rsp], rax ; assuming we can spare a register, a bad idea to corrupt one though
Run Code Online (Sandbox Code Playgroud) 我正在尝试按整数解析二进制文件,以检查整数值是否满足某个条件,但循环非常慢。
此外,我发现memory-mapped files 是将文件快速读入内存的最快方法,因此我使用了以下Boost基于代码的代码:
unsigned long long int get_file_size(const char *file_path) {
const filesystem::path file{file_path};
const auto generic_path = file.generic_path();
return filesystem::file_size(generic_path);
}
boost::iostreams::mapped_file_source read_bytes(const char *file_path,
const unsigned long long int offset,
const unsigned long long int length) {
boost::iostreams::mapped_file_params parameters;
parameters.path = file_path;
parameters.length = static_cast<size_t>(length);
parameters.flags = boost::iostreams::mapped_file::mapmode::readonly;
parameters.offset = static_cast<boost::iostreams::stream_offset>(offset);
boost::iostreams::mapped_file_source file;
file.open(parameters);
return file;
}
boost::iostreams::mapped_file_source read_bytes(const char *file_path) {
const auto file_size = get_file_size(file_path);
const auto mapped_file_source = read_bytes(file_path, 0, …Run Code Online (Sandbox Code Playgroud)