所以,我有以下代码:
uint32_t val;
if (swap) {
val = ((uint32_t)a & 0x0000ffff) | ((uint32_t)b << 16);
} else {
val = ((uint32_t)b & 0x0000ffff) | ((uint32_t)a << 16);
}
是否有一种优化方法,并且swap
会以某种方式检查 嵌入语句中?
答案 0 :(得分:2)
如果目标是避免分支,则可以这样写:
val = ((!!swap) * (uint32_t)a + (!swap) * (uint32_t)b) & 0x0000ffff)
| (((!!swap) * (uint32_t)b + (!swap) * (uint32_t)a) << 16);
这利用了这样一个事实:!x
为真时swap
取值为0,swap
为假时则取值为1,因此!!x
的取值为{{ 1}}是真实的,即使x
本身可能不是1。乘以结果也可以选择x
或a
。
但是,请注意,您现在有了多个逻辑和算术运算,而不是一个比较和分支。尚不清楚在实践中是否可以提高性能。
由@ChristianGibbons提供:
[假设b
和a
被保证为非负且小于2 16 ,]您可以通过删除按位AND组件并应用来显着简化此方法转移而不是参数:
b
这有可能胜过原始代码(但仍然不确定这样做),但是在那种情况下,与原始代码版本具有相同属性的比较会更公平地进行比较。输入:
val = ((uint32_t) a << (16 * !swap)) | ((uint32_t)b << (16 * !!swap));
答案 1 :(得分:1)
我们没有太多需要优化的地方
这里有两个版本
typedef union
{
uint16_t u16[2];
uint32_t u32;
}D32_t;
uint32_t foo(uint32_t a, uint32_t b, int swap)
{
D32_t da = {.u32 = a}, db = {.u32 = b}, val;
if(swap)
{
val.u16[0] = da.u16[1];
val.u16[1] = db.u16[0];
}
else
{
val.u16[0] = db.u16[1];
val.u16[1] = da.u16[0];
}
return val.u32;
}
uint32_t foo2(uint32_t a, uint32_t b, int swap)
{
uint32_t val;
if (swap)
{
val = ((uint32_t)a & 0x0000ffff) | ((uint32_t)b << 16);
}
else
{
val = ((uint32_t)b & 0x0000ffff) | ((uint32_t)a << 16);
}
return val;
}
生成的代码几乎相同。
c声:
foo: # @foo
mov eax, edi
test edx, edx
mov ecx, esi
cmove ecx, edi
cmove eax, esi
shrd eax, ecx, 16
ret
foo2: # @foo2
movzx ecx, si
movzx eax, di
shl edi, 16
or edi, ecx
shl esi, 16
or eax, esi
test edx, edx
cmove eax, edi
ret
gcc:
foo:
test edx, edx
je .L2
shr edi, 16
mov eax, esi
mov edx, edi
sal eax, 16
mov ax, dx
ret
.L2:
shr esi, 16
mov eax, edi
mov edx, esi
sal eax, 16
mov ax, dx
ret
foo2:
test edx, edx
je .L6
movzx eax, di
sal esi, 16
or eax, esi
ret
.L6:
movzx eax, si
sal edi, 16
or eax, edi
ret
您看到clang喜欢联合,gcc会移位。
答案 2 :(得分:1)
与避免任何分支的约翰·博林格(John Bollinger)的回答类似,我想出了以下方法来尝试减少执行的运算量,尤其是乘法。
uint8_t shift_mask = (uint8_t) !swap * 16;
val = ((uint32_t) a << (shift_mask)) | ((uint32_t)b << ( 16 ^ shift_mask ));
两个编译器实际上都没有使用乘法指令,因为这里唯一的乘法是2的幂,因此它仅使用简单的左移来构造将用于移位a
或{{ 1}}。
使用Clang -O2拆卸原件
b
使用Clang -O2拆卸新版本
0000000000000000 <cat>:
0: 85 d2 test %edx,%edx
2: 89 f0 mov %esi,%eax
4: 66 0f 45 c7 cmovne %di,%ax
8: 66 0f 45 fe cmovne %si,%di
c: 0f b7 c0 movzwl %ax,%eax
f: c1 e7 10 shl $0x10,%edi
12: 09 f8 or %edi,%eax
14: c3 retq
15: 66 66 2e 0f 1f 84 00 data16 nopw %cs:0x0(%rax,%rax,1)
1c: 00 00 00 00
使用gcc -O2拆卸原始版本
0000000000000000 <cat>:
0: 80 f2 01 xor $0x1,%dl
3: 0f b6 ca movzbl %dl,%ecx
6: c1 e1 04 shl $0x4,%ecx
9: d3 e7 shl %cl,%edi
b: 83 f1 10 xor $0x10,%ecx
e: d3 e6 shl %cl,%esi
10: 09 fe or %edi,%esi
12: 89 f0 mov %esi,%eax
14: c3 retq
15: 66 66 2e 0f 1f 84 00 data16 nopw %cs:0x0(%rax,%rax,1)
1c: 00 00 00 00
使用gcc -O2反汇编新版本
0000000000000000 <cat>:
0: 84 d2 test %dl,%dl
2: 75 0c jne 10 <cat+0x10>
4: 89 f8 mov %edi,%eax
6: 0f b7 f6 movzwl %si,%esi
9: c1 e0 10 shl $0x10,%eax
c: 09 f0 or %esi,%eax
e: c3 retq
f: 90 nop
10: 89 f0 mov %esi,%eax
12: 0f b7 ff movzwl %di,%edi
15: c1 e0 10 shl $0x10,%eax
18: 09 f8 or %edi,%eax
1a: c3 retq
编辑:
正如John Bollinger指出的那样,该解决方案是在0000000000000000 <cat>:
0: 83 f2 01 xor $0x1,%edx
3: 0f b7 c6 movzwl %si,%eax
6: 0f b7 ff movzwl %di,%edi
9: c1 e2 04 shl $0x4,%edx
c: 89 d1 mov %edx,%ecx
e: 83 f1 10 xor $0x10,%ecx
11: d3 e0 shl %cl,%eax
13: 89 d1 mov %edx,%ecx
15: d3 e7 shl %cl,%edi
17: 09 f8 or %edi,%eax
19: c3 retq
和a
是无符号值的情况下编写的,从而使位掩码变得多余。如果要将此方法与32位以下的带符号值一起使用,则需要进行修改:
b
我不会对这个版本进行反汇编,但这是-O2的clang输出:
uint8_t shift_mask = (uint8_t) !swap * 16;
val = ((uint32_t) (a & 0xFFFF) << (shift_mask)) | ((uint32_t) (b & 0xFFFF) << ( 16 ^ shift_mask ));
为了回应P__J__在性能方面与他的联合解决方案相关的问题,以下是lang在0000000000000000 <cat>:
0: 80 f2 01 xor $0x1,%dl
3: 0f b6 ca movzbl %dl,%ecx
6: c1 e1 04 shl $0x4,%ecx
9: 0f b7 d7 movzwl %di,%edx
c: d3 e2 shl %cl,%edx
e: 0f b7 c6 movzwl %si,%eax
11: 83 f1 10 xor $0x10,%ecx
14: d3 e0 shl %cl,%eax
16: 09 d0 or %edx,%eax
18: c3 retq
19: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
发出的关于该代码可安全处理带符号类型的版本的信息:
-O3
在总指令中它更接近于联合解决方案,但是不使用SHRD,根据This的回答,在Intel Skylake处理器上执行需要4个时钟,并占用了多个操作单元。我会很好奇地好奇他们各自的表现如何。
答案 3 :(得分:0)
val = swap ? ((uint32_t)a & 0x0000ffff) | ((uint32_t)b << 16) : ((uint32_t)b & 0x0000ffff) | ((uint32_t)a << 16);
这将实现您要求的“嵌入”。但是,我不建议这样做,因为它会使可读性变差并且没有运行时优化。
答案 4 :(得分:0)