3

特別な構造体を操作しようとしていますが、ある種のスウィズル演算子が必要です。このため、オーバーロードされた配列演算子を使用することは理にかなっています[]が、構造体の特定の仕様により理論的な回避策が可能になるため、分岐はしたくありません。

現在、構造体は次のようになっています。

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]配列を使用して 2 つをマージすることはできません (また、ユニオン トリックはありません)。その正当な理由が必要な場合は、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

そのスニペットに対応する 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;
        }
4

5 に答える 5

6

インデックスxは実行時変数またはコンパイル時定数のいずれかです。

  • コンパイル時の定数である場合、オプティマイザーがインライン化時にデッド ブランチを削除できる可能性が高くなりoperator[]ます。

  • 次のようなランタイム変数の場合

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

    とにかくブランチが必要です。もちろん、オプティマイザーがループをアンロールしない限り、変数を 4 つの定数に置き換え、上記のようにインライン化およびプルーニングできます。

これをプロファイリングして、実際の問題があることを示し、コンパイルして、回避できる場所で分岐が実際に発生することを示しましたか?


コード例:

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
于 2012-12-20T17:03:12.333 に答える
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
于 2012-12-20T17:02:12.457 に答える
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
}

パフォーマンスに関しては、オプティマイザーは定数を簡単に伝播し、その後デッドコードを削除して、ブランチを完全に削除できるはずなので、違いはないと思います。ただし、このアプローチでは、無効なインデックスで関数を呼び出そうとするとコンパイラ エラーが発生するという利点があります。

于 2012-12-20T17:37:31.613 に答える
1

4 つの要素すべてを含む 1 つの配列 (またはベクトル) を作成します。fLow 値は最初の 2 つの位置を占め、次に 2 番目の位置を占めます。次に、それにインデックスを付けます。

inline float& operator[] (int x) {
    return newFancyArray[x]; //But do some bounds checking above.
}
于 2012-12-20T16:54:10.343 に答える
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]; }
于 2012-12-21T10:39:45.637 に答える