web-dev-qa-db-ja.com

基準に違反しないほぼ一定の時間回転

私は、C/C++標準に違反しない一定の時間ローテーションを考え出すのにかなりの時間を費やしています。

問題は、操作がアルゴリズムで呼び出され、それらのアルゴリズムを変更できないエッジ/コーナーの場合です。たとえば、以下は Crypto ++ からのものであり、 GCC ubsan (つまり、_g++ fsanitize=undefined_)の下でテストハーネスを実行します。

_$ ./cryptest.exe v | grep runtime
misc.h:637:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
misc.h:643:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
misc.h:625:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
misc.h:637:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
misc.h:643:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
misc.h:637:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
_

そして_misc.h:637_のコード:

_template <class T> inline T rotlMod(T x, unsigned int y)
{
    y %= sizeof(T)*8;
    return T((x<<y) | (x>>(sizeof(T)*8-y)));
}
_

IntelのICCは特に冷酷で、y %= sizeof(T)*8なしで関数呼び出し全体を削除しました。数年前にそれを修正しましたが、定数時間の解決策がなかったため、他のエラッタはそのままにしました。

残りの問題点が1つあります。 _y = 0_の場合、_32 - y = 32_の条件が発生し、未定義の動作が設定されます。 if(y == 0) ...のチェックを追加すると、コードは一定時間の要件を満たせなくなります。

Linuxカーネルから他の暗号化ライブラリまで、他の多くの実装を見てきました。それらはすべて同じ未定義の動作を含んでいるため、行き止まりのように見えます。

最小限の命令でほぼ一定の時間で回転を実行するにはどうすればよいですか?

[〜#〜]編集[〜#〜]:byほぼ一定の時間、つまり分岐を避けて、同じ命令が常に実行されるようにします。 CPUマイクロコードのタイミングは気になりません。分岐予測はx86/x64で優れている場合がありますが、組み込みなどの他のプラットフォームではうまく機能しない場合があります。


[〜#〜] gcc [〜#〜] または Clangほぼ一定の時間で回転する)を実行するための組み込み関数を提供する場合、これらのトリックは必要ありません。 。彼らにはそれさえないので、私は「回転を実行する」ことさえ決心するでしょう。

25
jww

このコミュニティwikiの質問 を含む他のいくつかの「ローテーション」質問からの完全な詳細については、この回答にリンクしました。これは、ベストプラクティスで最新の状態に保つ必要があります。

この問題に関するブログ投稿を見つけましたが、最終的には解決された問題のようです(コンパイラのバージョンが十分に新しくなっています)。

ユタ大学のJohn Regehr 回転関数を作成する試みのバージョン「c」を推奨します。私は彼のアサーションをビット単位のANDに置き換えましたが、それでも単一の回転insnにコンパイルされることがわかりました。

typedef uint32_t rotwidth_t;  // parameterize for comparing compiler output with various sizes

rotwidth_t rotl (rotwidth_t x, unsigned int n)
{
  const unsigned int mask = (CHAR_BIT*sizeof(x)-1);  // e.g. 31

  assert ( (n<=mask)  &&"rotate by type width or more");
  n &= mask;  // avoid undef behaviour with NDEBUG.  0 overhead for most types / compilers
  return (x<<n) | (x>>( (-n)&mask ));
}

rotwidth_t rot_const(rotwidth_t x)
{
  return rotl(x, 7);
}

これはxの型に基づいてテンプレート化できますが、関数名に幅を含めること(rotl32など)を実際に使用する方がおそらく理にかなっています。通常、回転しているときは、必要な幅がわかっています。これは、現在値を格納しているサイズ変数よりも重要です。

また、これは符号なしタイプでのみ使用するようにしてください。符号付きタイプの右シフトは算術シフトを行い、符号ビットをシフトします。 (これは技術的には実装に依存する動作ですが、現在はすべて2の補数を使用しています。)

パビゴットは私がやる前に独立して同じアイデアを思いついた そしてそれをgibhubに投稿した 。彼のバージョンにはC++ static_assertチェックがあり、型の範囲外の回転カウントを使用するとコンパイル時エラーになります。

I gcc.godbolt.orgでテスト済みの鉱山 、NDEBUGが定義されており、変数とコンパイル時の定数のローテーションカウント:

  • gcc:gcc> = 4.9.0、非分岐neg + shifts +またはそれ以前の最適なコード。
    (コンパイル時のconstカウント:gcc 4.4.7で問題ありません)
  • clang:clang> = 3.5.0、非分岐neg + shifts +またはそれ以前の最適なコード。
    (コンパイル時のconst回転カウント:clang 3.0で問題ありません)
  • icc 13:最適なコード。
    (-march = nativeを使用したコンパイル時のconstカウント:より遅いshld $7, %edi, %ediを生成します。-march=nativeがなくても問題ありません)

新しいコンパイラバージョンでも、ブランチやcmovを生成せずに、ウィキペディア(godboltサンプルに含まれています)から一般的に提供されるコードを処理できます。 John Regehrのバージョンには、回転カウントが0の場合の未定義の動作を回避できるという利点があります。

8ビットと16ビットの回転にはいくつかの注意点がありますが、nuint32_tの場合、コンパイラは32または64で問題ないように見えます。 uint*_tのさまざまな幅をテストしたときのメモについては、 godbolt link のコードのコメントを参照してください。うまくいけば、このイディオムは、将来、型幅のより多くの組み合わせのために、すべてのコンパイラーによってよりよく認識されるでしょう。 x86 ISAは、最初のステップとして正確なANDを使用して回転insnを定義している場合でも、gccが回転カウントでANDinsnを無用に出力することがあります。

「最適」とは、次のように効率的であることを意味します。

# gcc 4.9.2 rotl(unsigned int, unsigned int):
    movl    %edi, %eax
    movl    %esi, %ecx
    roll    %cl, %eax
    ret
# rot_const(unsigned int):
    movl    %edi, %eax
    roll    $7, %eax
    ret

インライン化された場合、コンパイラーは、最初に値が正しいレジスターにあるように調整できる必要があり、その結果、1回のローテーションだけになります。

古いコンパイラでは、ローテーションカウントがコンパイル時定数である場合でも理想的なコードを取得できます。 Godboltを使用すると、ARMをターゲットとしてテストでき、そこでもローテーションを使用します。古いコンパイラの変数カウントを使用すると、コードが少し肥大しますが、ブランチや大きなパフォーマンスの問題は発生しません。 、したがって、このイディオムは一般的に安全に使用できるはずです。

ところで、私はJohnRegehrのオリジナルをCHAR_BIT * sizeof(x)を使用するように変更し、gcc/clang/iccはuint64_tにも最適なコードを出力します。ただし、関数の戻り値の型がuint64_tのままでxuint32_tに変更すると、gccがそれをshifts /またはにコンパイルすることに気付きました。したがって、64bの下位32bをローテーションする場合は、結果を別のシーケンスポイントで32ビットにキャストするように注意してください。つまり、結果を64ビット変数に割り当ててから、キャスト/リターンします。 iccは引き続きrotateinsnを生成しますが、gccとclangは生成しません。

// generates slow code: cast separately.
uint32_t r = (uint32_t)( (x<<n) | (x>>( -n&(CHAR_BIT*sizeof(x)-1) )) );

誰かがMSVCでこれをテストできるなら、そこで何が起こるかを知ることは役に立ちます。

12
Peter Cordes

32ビットによるシフトを防ぐために1つのモジュロ演算を追加できますが、分岐予測子と組み合わせてifチェックを使用するよりもこれが高速であるとは確信していません。

template <class T> inline T rotlMod(T x, unsigned int y)
{
    y %= sizeof(T)*8;
    return T((x<<y) | (x>>((sizeof(T)*8-y) % (sizeof(T)*8))));
}
6
Mark B

式をT((x<<y) | ((x>>(sizeof(T)*CHAR_BITS-y-1)>>1))として記述すると、yがパディングのない符号なし型であると仮定して、ビットサイズ未満のTのすべての値に対して定義された動作が得られます。コンパイラに優れたオプティマイザがない限り、結果のコードは元の式で生成されたものほど良くない可能性があります。しかし、多くのコンパイラで実行が遅くなる不格好な読みにくいコードに我慢しなければならないことは、進歩の代償の一部です。

if (y) do_something();
return T((x<<y) | (x>>(sizeof(T)*8-y)));

do_somethingを無条件で呼び出すことにより、コードの「効率」が向上する可能性があります。

PS:yxのビットサイズと正確に等しいときにx >> yになるように、右シフトの定義を変更する実際のプラットフォームがあるのではないかと思います。 0またはxのいずれかを生成する必要がありますが、任意の(指定されていない)方法で選択を行うことができ、プラットフォームに追加のコードを生成する必要がありますか、または真に有用不自然なシナリオでの最適化を排除しますか?

3
supercat

追加のモジュロの代わりに、0または1を掛けることができます(!!のおかげで):

template <class T> T rotlMod(T x, unsigned int y)
{
    y %= sizeof(T) * 8;
    return T((x << y) | (x >> ((!!y) * (sizeof(T) * 8 - y)));
}
2
Jarod42