【问题标题】:C++ templated array operator[] using ints使用整数的 C++ 模板数组运算符 []
【发布时间】:2012-12-20 16:47:37
【问题描述】:

我正在尝试操作一个特殊的结构,我需要某种 swizzle 运算符。为此,有一个重载数组[] 运算符是有意义的,但我不想有任何分支,因为结构的特定规范允许理论上的解决方法。

目前,结构体如下所示:

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]s 替换了代码。

使用 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

sn-p 对应的 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;
        }

【问题讨论】:

  • 您能否详细说明“但我不想有任何分支,因为结构的特定规范允许理论上的解决方法”?
  • @MarkB 哎呀,是的,修复了这个错误。当然,出于简洁的原因,其中也没有 assert(x
  • @piokuc 即在编译时执行 - 因为 x 只有 4 个可能的值适用于该类的实例。
  • 这个问题感觉很本地化。
  • @ahenderson - 设置是本地化的,但对我来说这似乎是一个关于优化技术的合理问题。

标签: c++ templates operator-overloading swizzling


【解决方案1】:

索引x 是运行时变量或编译时常量。

  • 如果它是一个编译时常量,那么优化器很有可能在内联operator[] 时修剪死分支。

  • 如果是运行时变量,比如

    for (int i=0; i<4; ++i) { dosomething(f[i]); }
    

    无论如何你都需要分支。当然,除非您的优化器展开循环,在这种情况下,它可以将变量替换为四个常量,如上所示。

您是否对此进行了分析以表明存在真正的问题,并对其进行编译以显示分支确实发生在可以避免的地方?


示例代码:

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

【讨论】:

  • 我没有对其进行分析,但是由于某些奇怪的原因,程序集输出似乎正在分支(只有模板 int 参数用作 [] 运算符的输入,这应该算作编译器优化)..我会看看我能用它做什么。
  • 我刚刚检查过,-O3 让我完成了内联和常量折叠 [在 x86 上使用 gcc 4.5.1]。您的呼叫站点是什么样的?
  • 我更新了代码。汇编输出是在我调用一个简单的 printf 之后编写的:printf("%f %f %f %f", v0[0], v0[1], v0[2], v0[3]); 编译器的策略是通过最大内联来优化速度。如果我没记错的话,还是有分店要花钱的,对吧?
  • 我在你的编译器输出中看到了分支——但我不知道这是否是一个离线实例化......你能否也显示调用站点和优化级别?跨度>
  • 好的,没问题。如果您的优化器无法管理不断折叠,我同意 Luc 的答案可能是最佳选择。
【解决方案2】:

说真的,不要这样做!只需组合数组。但是既然你问了这个问题,这里有一个答案:

#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

【讨论】:

  • 我不确定用间接替换分支是否正是 OP 所需要的(假设动机是速度)
【解决方案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
}

关于性能,我认为不会有任何区别,因为优化器应该能够轻松地传播常量并随后删除死代码,从而完全删除分支。但是,使用这种方法,您会得到好处,即任何尝试使用无效索引调用函数都会导致编译器错误。

【讨论】:

  • 据我所知,我可以将它标记为真正的宝石,并给这个答案至少 +5。尽管它在具有相当不错的编译器(g++、VS cl)的 x86 平台上运行得非常好,但它不适用于我的特定编译器/平台(似乎它不支持类型特征)。尽管如此,所有其他答案也提供了必要的提示 - 最后,我会接受这个,因为它的内容是固定的。非常感谢。
【解决方案4】:

创建一个包含所有 4 个元素的数组(或向量),fLow 值占据前两个位置,然后是第二个 2 的高值。然后只需对其进行索引。

inline float& operator[] (int x) {
    return newFancyArray[x]; //But do some bounds checking above.
}

【讨论】:

  • OP 已经说过这不是一个选项(在对 Mark B 的回答的评论中)
  • 我们同时说的;)
  • 似乎不公平,只有你们中的一个人得到了评论;)
【解决方案5】:

根据 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]; }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-04-15
    • 2015-05-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-03
    相关资源
    最近更新 更多