我正在尝试操纵一个特殊的结构,我需要某种混合运算符。为此,有一个重载的数组[]
运算符是有意义的,但我不想有任何分支,因为结构的特定规范允许理论上的解决方法。
目前,结构看起来像这样:
struct f32x4
{
float fLow[2];
float fHigh[2];
f32x4(float a, float b, float c, float d)
{
fLow[0] = a;
fLow[1] = b;
fHigh[0] = c;
fHigh[1] = d;
}
// template with an int here?
inline float& operator[] (int x) {
if (x < 2)
return fLow[x];
else
return fHigh[x - 2];
}
};
我可以/应该做些什么来避免分支?我的想法是使用带有整数参数的模板并定义特化,但目前尚不清楚它是否有意义以及该怪物的语法可能是什么样的。
我明确地说,在任何情况下都不能使用float[4]
数组来合并两个(也没有联合技巧)。如果你需要一个很好的理由,那是因为float[2]
实际上类似于平台特定的PowerPC配对单曲。普通的Windows编译器不适用于配对的单一,这就是我用float[2]
替换代码的原因。
使用GreenHills编译器我得到这个程序集输出(这表明分支确实发生了):
.LDW31:
00000050 80040000 89 lwz r0, 0(r4)
00000054 2c000000 90 cmpwi r0, 0
00000058 41820000 91 beq .L69
92 #line32
93
94 .LDWlin1:
0000005c 2c000001 95 cmpwi r0, 1
00000060 40820000 96 bne .L74
97 #line32
98
99 .LDWlin2:
00000064 38630004 100 addi r3, r3, 4
00000068 38210018 101 addi sp, sp, 24
0000006c 4e800020 102 blr
103 .L74:
00000070 2c000002 104 cmpwi r0, 2
00000074 40820000 105 bne .L77
106 #line33
107
108 .LDWlin3:
00000078 38630008 109 addi r3, r3, 8
0000007c 38210018 110 addi sp, sp, 24
00000080 4e800020 111 blr
112 .L77:
00000084 2c000003 113 cmpwi r0, 3
00000088 40820000 114 bne .L80
115 #line34
116
117 .LDWlin4:
0000008c 3863000c 118 addi r3, r3, 12
00000090 38210018 119 addi sp, sp, 24
00000094 4e800020 120 blr
121 .L80:
00000098 38610008 122 addi r3, sp, 8
123 .L69:
124 # .ef
该代码段的相应C ++代码应为以下代码:
inline const float& operator[](const unsigned& idx) const
{
if (idx == 0) return xy[0];
if (idx == 1) return xy[1];
if (idx == 2) return zw[0];
if (idx == 3) return zw[1];
return 0.f;
}
答案 0 :(得分:6)
索引x
是运行时变量,或编译时常量。
如果它是编译时常量,那么优化器很可能在内联operator[]
时修剪死分支。
如果是运行时变量,例如
for (int i=0; i<4; ++i) { dosomething(f[i]); }
无论如何你需要分支。当然,除非你的优化器展开循环,在这种情况下它可以用四个常量替换变量,inline&amp;修剪如上。
您是否对此进行了剖析以显示存在真正的问题,并对其进行编译以显示分支确实发生在可以避免的地方?
示例代码:
float foo(f32x4 &f)
{
return f[0]+f[1]+f[2]+f[3];
}
来自g++ -O3 -S
.globl _Z3fooR5f32x4
.type _Z3fooR5f32x4, @function
_Z3fooR5f32x4:
.LFB4:
.cfi_startproc
movss (%rdi), %xmm0
addss 4(%rdi), %xmm0
addss 8(%rdi), %xmm0
addss 12(%rdi), %xmm0
ret
.cfi_endproc
答案 1 :(得分:4)
说真的,不要这样做!!只需组合阵列。但既然你问了这个问题,这是一个答案:
#include <iostream>
float fLow [2] = {1.0,2.0};
float fHigh [2] = {50.0,51.0};
float * fArrays[2] = {fLow, fHigh};
float getFloat (int i)
{
return fArrays[i>=2][i%2];
}
int main()
{
for (int i = 0; i < 4; ++i)
std::cout << getFloat(i) << '\n';
return 0;
}
输出:
1
2
50
51
答案 2 :(得分:3)
由于您在评论中说您的索引始终是模板参数,因此您确实可以在编译时而不是运行时进行分支。以下是使用std::enable_if
:
#include <iostream>
#include <type_traits>
struct f32x4
{
float fLow[2];
float fHigh[2];
f32x4(float a, float b, float c, float d)
{
fLow[0] = a;
fLow[1] = b;
fHigh[0] = c;
fHigh[1] = d;
}
template <int x>
float& get(typename std::enable_if<(x >= 0 && x < 2)>::type* = 0)
{
return fLow[x];
}
template <int x>
float& get(typename std::enable_if<(x >= 2 && x < 4)>::type* = 0)
{
return fHigh[x-2];
}
};
int main()
{
f32x4 f(0.f, 1.f, 2.f, 3.f);
std::cout << f.get<0>() << " " << f.get<1>() << " "
<< f.get<2>() << " " << f.get<3>(); // prints 0 1 2 3
}
关于性能,我认为没有任何区别,因为优化器应该能够轻松传播常量并随后删除死代码,从而完全删除分支。但是,使用这种方法,您可以获得任何使用无效索引调用函数的尝试都会导致编译器错误。
答案 3 :(得分:1)
创建一个包含所有4个元素的数组(或向量),fLow值占据前两个位置,然后是第二个位置的高位。然后只需将其索引。
inline float& operator[] (int x) {
return newFancyArray[x]; //But do some bounds checking above.
}
答案 4 :(得分:0)
根据Luc Touraille的回答,由于缺乏编译器支持而没有使用类型特征,我发现以下内容可以达到问题的目的。由于operator []不能使用int参数进行模板化并且在语法上有效,因此我引入了at
方法。这是结果:
struct f32x4
{
float fLow[2];
float fHigh[2];
f32x4(float a, float b, float c, float d)
{
fLow[0] = a;
fLow[1] = b;
fHigh[0] = c;
fHigh[1] = d;
}
template <unsigned T>
const float& at() const;
};
template<>
const float& f32x4::at<0>() const { return fLow[0]; }
template<>
const float& f32x4::at<1>() const { return fLow[1]; }
template<>
const float& f32x4::at<2>() const { return fHigh[0]; }
template<>
const float& f32x4::at<3>() const { return fHigh[1]; }