7

私はプロジェクトにGCCSIMDベクトル拡張を使用しています。すべてが非常にうまく機能しますが、キャストします。ベクトルのすべてのコンポーネントをリセットするだけです。

マニュアルには次のように記載されています。

同じサイズであれば、あるベクトル型から別のベクトル型にキャストすることができます(実際、同じサイズの他のデータ型との間でベクトルをキャストすることもできます)。

簡単な例を次に示します。

#include <stdio.h>

typedef int int4 __attribute__ (( vector_size( sizeof( int ) * 4 ) ));
typedef float float4 __attribute__ (( vector_size( sizeof( float ) * 4 ) ));

int main()
{
    int4 i = { 1 , 2 , 3 , 4 };
    float4 f = { 0.1 , 0.2 , 0.3 , 0.4 };

    printf( "%i %i %i %i\n" , i[0] , i[1] , i[2] , i[3] );
    printf( "%f %f %f %f\n" , f[0] , f[1] , f[2] , f[3] );

    f = ( float4 )i;

    printf( "%f %f %f %f\n" , f[0] , f[1] , f[2] , f[3] );
}

gcc cast.c -O3 -o cast私のマシンでコンパイルして実行すると、次のようになります。

1 2 3 4
0.100000 0.200000 0.300000 0.400000
0.000000 0.000000 0.000000 0.000000 <-- no no no

私はそのアセンブラーの第一人者ではありませんが、ここでいくつかのバイトの動きが見られます。

[...]
400454:f2 0f 10 1d 1c 02 00 movsd 0x21c(%rip)、%xmm3
40045b:00
40045c:bf 49 06 40 00 mov $ 0x400649、%edi
400461:f2 0f 10 15 17 02 00 movsd 0x217(%rip)、%xmm2
400468:00
400469:b8 04 00 00 00 mov $ 0x4、%eax
40046e:f2 0f 10 0d 12 02 00 movsd 0x212(%rip)、%xmm1
400475:00
400476:f2 0f 10 05 12 02 00 movsd 0x212(%rip)、%xmm0
40047d:00
40047e:48 83 c408追加$0x8、%rsp
400482:e9 59 ff ff ff jmpq 4003e0

私はスカラーと同等のベクトルを疑っています:

*( int * )&float_value = int_value;

この動作をどのように説明できますか?

4

3 に答える 3

9

それがベクトルキャストが行うように定義されていることです(他のものは完全にばかげているでしょう、そして標準的なベクトルプログラミングイディオムを書くのは非常に苦痛になります)。実際に変換を取得したい場合は、おそらく_mm_cvtepi32_psのようなある種の組み込み関数を使用することをお勧めします(これはもちろん、ベクトルコードの優れたアーキテクチャ上の独立性を壊しますが、これも厄介です。一般的なアプローチは「組み込み関数」のポータブルセットを定義する変換ヘッダー)。

なぜこれが便利なのですか?さまざまな理由がありますが、最大のものは次のとおりです。

ベクトルコードでは、分岐することはほとんどありません。代わりに、条件付きで何かを行う必要がある場合は、条件の両側を評価し、マスクを使用してレーンごとに適切な結果を選択します。これらのマスクベクトルは「当然」整数型ですが、データベクトルは浮動小数点であることがよくあります。論理演算を使用して2つを組み合わせる必要があります。この非常に一般的なイディオムは、ベクトルキャストが単にビットを再解釈する場合に最も自然です。

確かに、このケース、または他の一般的なベクトルイディオムのバッグのいずれかを回避することは可能ですが、「ベクトルはビットのバッグ」ビューは非常に一般的であり、ほとんどのベクトルプログラマーの考え方を反映しています。

于 2012-09-11T18:38:19.480 に答える
2

実際のところ、あなたのケースでは単一のベクトル命令も生成されておらず、実行時に型キャストも実行されていません。スイッチがあるため、すべてコンパイル時に実行されます-O3。4つのMOVSD命令は、実際には事前変換された引数をにロードしていますprintf。実際、SysV AMD64 ABIによると、浮動小数点引数はXMMレジスタで渡されます。分解したセクションは次のとおりです(でコンパイルして取得したアセンブリコード-S):

    movsd   .LC6(%rip), %xmm3
    movl    $.LC5, %edi
    movsd   .LC7(%rip), %xmm2
    movl    $4, %eax
    movsd   .LC8(%rip), %xmm1
    movsd   .LC9(%rip), %xmm0
    addq    $8, %rsp
    .cfi_def_cfa_offset 8
    jmp     printf
    .cfi_endproc

.LC5フォーマット文字列にラベルを付けます。

.LC5:
    .string "%f %f %f %f\n"

フォーマット文字列へのポインタはクラスINTEGERであるため、レジスタに渡されRDIます(VAスペースの最初の4 GiBのどこかにあるため、の下部に32ビット移動を発行することで一部のコードバイトが保存されますRDI)。レジスターRAXEAXコードバイトを節約するために使用)には、XMMレジスターで渡された引数の数がロードされます(これも、可変数の引数を持つ関数の呼び出しに関するSysV AMD64 ABIに準拠しています)。4つすべてMOVSD(MOVe Scalar Double-precision)は、XMMレジスタ内の対応する引数を移動します。.LC9たとえば、2つのダブルワードにラベルを付けます。

    .align 8
.LC9:
    .long   0
    .long   916455424

これらの2つは、64ビットIEEE754表現0x36A0000000000000でたまたま2-149である64ビットクワッドワードを形成します。非正規化された32ビットIEEE754では、のように見えるため0x00000001、実際には整数の変換は行われません1(ただし、引数が必要なため、倍精度に事前変換されます)。2番目の引数は次のとおりです。printfdouble

    .align 8
.LC8:
    .long   0
    .long   917504000

これは、64ビットIEEE754および非正規化32ビットIEEE754では0x36B0000000000000または2-1480x00000002です。他の2つの引数についても同じです。

上記のコードは単一のスタック変数を使用していないことに注意してください。事前に計算された定数でのみ動作します。これは、非常に高い最適化レベル(-O3)を使用した結果です。-O2より低い最適化レベル(またはより低い)でコンパイルすると、実際のランタイム変換が発生します。次に、型キャストを実行するために次のコードが発行されます。

    movaps  -16(%rbp), %xmm0
    movaps  %xmm0, -32(%rbp)

これにより、4つの整数値が浮動小数点ベクトルの対応するスロットに移動するだけなので、変換は行われません。次に、要素ごとに、単精度から倍精度に変換するために、いくつかのSSEマンボジャンボが実行されます(予想どおりprintf)。

    movss   -20(%rbp), %xmm0
    unpcklps        %xmm0, %xmm0
    cvtps2pd        %xmm0, %xmm3

CVTSS2SD( SSE命令セットの理解を超えて使用するだけではないのはなぜですか)

于 2012-09-12T15:05:05.823 に答える
2

要素を直接ループすることで、intからfloatにキャストできます

float4 cast(int4 x) {
    float4 y;
    for(int i=0; i<4; i++) y[i] = x[i];
    return y;
}

GCC、Clang、およびICCはすべて、このための1つの命令cvtdq2ps xmm0, xmm0を生成します。

https://godbolt.org/g/KU1aPg

于 2018-01-18T12:56:44.593 に答える