2015-11-21 8 views
6

対unsigned int型、私はいくつかの簡単なビット単位の演算と算術演算の速度は私の64ビットPC上intunsignedlong longunsigned long longの間で大きく異なることに気づきました。C++は今日署名し、長い長いスピード

特に、次のループは、の場合の約2倍の速さです(予想外でした)。ここで

int k = 15; 
int N = 30; 
int mask = (1 << k) - 1; 
while (!(mask & 1 << N)) { 
    int lo = mask & ~(mask - 1); 
    int lz = (mask + lo) & ~mask; 
    mask |= lz; 
    mask &= ~(lz - 1); 
    mask |= (lz/lo/2) - 1; 
} 

(フルコードhere

は(g++ -O-O2-O3用)(秒)のタイミングです:

1.834207723 (int) 
3.054731598 (long long) 
1.584846237 (unsigned) 
2.201142018 (unsigned long long) 

これらの時間は非常に一貫している(つまり、1%マージン)。 -Oフラグがない場合、それぞれの速度は約1秒遅くなりますが、相対速度は同じです。

明確な理由はありますか? 32ビット型ではベクトル化があるかもしれませんが、 の差がlong longunsigned long longのどこにあるのかわかりません。 いくつかのタイプのオペレーションは、他のタイプのオペレーションよりもかなり遅いでしょうか(たとえば、 )、または64ビットタイプの方が一般的です(64ビットアーキテクチャでさえ)。

このループは、正確に15要素の{1,2,...,30}のすべてのサブセットにループします。これは、正確に15ビットが設定された1<<30未満のすべての整数に対して(順番に)ループすることによって行われます。 現在のケースでは、それは155117520の繰り返しです。 このスニペットのソースはもうわかりませんが、this投稿で詳しく説明しています。

編集

それはタイプが符号なしの場合に除算を高速化することができますアセンブリコードかららしいです。サインビットを考慮する必要がないので、意味があると思います。 64ビット動作がmovqxxxqを使用しながら

また、32ビット演算はmovlおよび他xxxl命令、 を使用します。

編集2

Iがリンクポストを読んだ後、私は上の式を使用することを決めた:

T k = 15; 
T N = 30; 
T mask = (1 << k) - 1; 
while (!(mask & 1 << N)) { 
    T t = mask | (mask - 1); 
    mask = (t + 1) | (((~t & -~t) - 1) >> (__builtin_ctz(mask) + 1)); 
} 

これは上記の投稿コードの時間の約3分の1で実行され、そして4つのタイプすべてで同じ時間を使用します。

+1

は、あなたが生成されたアセンブリを見たことがありますか? –

+0

さて、アセンブリは本当に優れたものではありませんが、試してみる価値があるかもしれません。 – Ragnar

+2

バイナリをx64で再確認しますか? – jmnben

答えて

8

コード内の最も遅い動作

mask |= (lz/lo/2) - 1 

32ビット分割は、64ビットの分割よりも大幅に高速です。例えば、Ivy Bridgeでは32ビットのIDIVが19-26クロック、IDIVの64ビットは28-103クロックのレイテンシを要します。

2で除算すると符号なしの場合は単純にビットシフトされ、サイズ拡張呼び出し(CDQ、CQO)がないため、符号なしバージョンも署名済みより高速です。で

に署名しながら、符号なしの場合

は、単純なビットシフトである[1] http://www.agner.org/optimize/instruction_tables.pdf

+0

おそらく "...はかなり高速です..." –

+0

除算なしでコードを使用した後、4つのタイプはすべて同じ速度を使用します。また、アセンブリは分割命令から離れてほとんど同じだったので、あなたが正しいと思います。 – Ragnar