我有一个带有 2 个计数器的循环:i 和 j。如果它们具有相同的值 - 迭代的工作速度比它们的值不同时快得多:
Benchmark Mode Cnt Score Error Units
FloatsArrayBenchmark.times thrpt 20 341805.800 ± 1623.320 ops/s
FloatsArrayBenchmark.times2 thrpt 20 198764.909 ± 1608.387 ops/s
Run Code Online (Sandbox Code Playgroud)
Java 字节码是相同的,这意味着它与一些较低级别的优化有关。有人可以解释为什么会这样吗?这是基准:
import org.openjdk.jmh.annotations.*;
public class FloatsArrayBenchmark {
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(new String[]{FloatsArrayBenchmark.class.getSimpleName()});
}
@Benchmark @Fork(value = 1, warmups = 0)
public void times(Data data) {
float[] result = new float[10000];;
for (int i = 0, j=0; i < 9_999; i++,j++)
result[j] = data.floats[i] * 10;
}
@Benchmark …Run Code Online (Sandbox Code Playgroud) 假设您有一个简单的函数,它根据查找表返回一个值,例如:
请参阅有关假设的编辑。
uint32_t
lookup0(uint32_t r) {
static const uint32_t tbl[] = { 0, 1, 2, 3 };
if(r >= (sizeof(tbl) / sizeof(tbl[0]))) {
__builtin_unreachable();
}
/* Can replace with: `return r`. */
return tbl[r];
}
uint32_t
lookup1(uint32_t r) {
static const uint32_t tbl[] = { 0, 0, 1, 1 };
if(r >= (sizeof(tbl) / sizeof(tbl[0]))) {
__builtin_unreachable();
}
/* Can replace with: `return r / 2`. */
return tbl[r];
}
Run Code Online (Sandbox Code Playgroud)
是否有任何超级优化基础设施或算法可以从查找表到优化的 ALU 实现。
动机是我正在为 NUMA 机器构建一些锁,并且希望能够通用地配置我的代码。在 NUMA 锁中,您需要执行 …
对于现代硬件上的典型现代编译器,? :运算符是否会导致影响指令流水线的分支?
换句话说哪个更快,调用两个案例以避免可能的分支:
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?
or eax,eax和之间有什么区别test eax,eax?我已经看到不同的编译器生成两者用于相同的比较,并且就文档而言它们做的完全相同,所以我想知道为什么它们并非全部使用test eax,eax.考虑它and eax,eax会以同样的方式设置标志,但我没有在freepascal,delphi或msVC++中看到它.
我确实在delphi中编译了一些asm块并检查了汇编源代码,并且所有3种形式在操作码中的长度完全相同,并且还检查了英特尔性能PDF并且它说它们具有相同的延迟和吞吐量.
编辑:
问题具体是关于具体案例之间的区别test eax,eax,or eax,eax和and eax,eax.对于寄存器,标志,操作码长度,延迟,吞吐量,所有3都给出完全相同的结果.然而,如果测试0,如果不是零,或者如果签名,一些编译器会在使用test eax,eax时使用or eax,eax,我想知道为什么它们并非全部使用,test eax,eax因为它使代码更加清晰.
编辑2:
作为参考,我在家里只有老版本的msvc ++和Delphi,但测试一个变量,如果为零,msvc ++就是这样test eax,eax,而Delphi则是or eax,eax.
我想用英特尔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的补码?
环境:
我没有写很多汇编程序代码,当我这么做时,它要么足够短,要么足够简单,以至于我不必担心压缩它的最大数量.我的更复杂的代码通常用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) 访问结构体成员的过程比访问直接变量慢吗?如果我在多个地方使用相同的结构成员,我是否应该在变量中声明它并将其保存在那里,以节省多次访问同一成员的成本?我应该重复Struct.Member Struct.Member Struct.Member还是应该将其保存在变量中int Member = Struct.Member Member Member Member?什么时候应该使用后者而不是前者?如果这些成员是位字段怎么办?
具体来说,这是我正在讨论的代码:
float InvSqrt(float x) {
float xhalf = 0.5f*x;
int i = *(int*)&x; // warning: strict-aliasing UB, use memcpy instead
i = 0x5f375a86- (i >> 1);
x = *(float*)&i; // same
x = x*(1.5f-xhalf*x*x);
return x;
}
Run Code Online (Sandbox Code Playgroud)
我忘了我从哪里得到这个,但它显然比原来的 Quake III 算法(魔法常数略有不同)更好、更高效或更精确,但这个算法创建以来已经有 20 多年了,我只是想知道它是否是就性能而言,或者如果有一条指令已经在现代 x86-64 CPU 中实现了它,那么仍然值得使用它。
我想替换整数中的最低字节。在 x86 上这确实是这样mov al, [mem],但我似乎无法让编译器输出它。我是否遗漏了一个明显的可识别代码模式,我是否误解了某些内容,或者这只是一个错过的优化?
unsigned insert_1(const unsigned* a, const unsigned char* b)
{
return (*a & ~255) | *b;
}
unsigned insert_2(const unsigned* a, const unsigned char* b)
{
return *a >> 8 << 8 | *b;
}
Run Code Online (Sandbox Code Playgroud)
GCC 实际上使用al但只是为了归零。
mov eax, DWORD PTR [rdi]
movzx edx, BYTE PTR [rsi]
xor al, al
or eax, edx
ret
Run Code Online (Sandbox Code Playgroud)
Clang 几乎逐字编译两者
mov ecx, -256
and ecx, dword ptr [rdi]
movzx eax, byte ptr [rsi]
or eax, ecx …Run Code Online (Sandbox Code Playgroud)