これが実際の例です:固定小数点は古いコンパイラで乗算されます。
これらは、浮動小数点のないデバイスで便利なだけでなく、予測可能なエラーで32ビットの精度を提供するため、精度に関して優れています(浮動小数点は23ビットしかなく、精度の低下を予測することは困難です)。つまり、均一に近い相対精度ではなく、範囲全体で均一な絶対精度精度(float
)。
最新のコンパイラーはこの固定小数点の例を適切に最適化しているため、コンパイラー固有のコードを必要とする最新の例については、
Cには完全乗算演算子がありません(Nビット入力からの2Nビットの結果)。Cでそれを表現する通常の方法は、入力をより広い型にキャストし、コンパイラーが入力の上位ビットが興味深いものではないことを認識することを期待することです。
// on a 32-bit machine, int can hold 32-bit fixed-point integers.
int inline FixedPointMul (int a, int b)
{
long long a_long = a; // cast to 64 bit.
long long product = a_long * b; // perform multiplication
return (int) (product >> 16); // shift by the fixed point bias
}
このコードの問題は、C言語で直接表現できない処理を行うことです。2つの32ビット数値を乗算して64ビットの結果を取得し、その結果、中央の32ビットを返します。ただし、Cではこの乗算は存在しません。できることは、整数を64ビットにプロモートし、64 * 64 = 64乗算を行うことだけです。
ただし、x86(およびARM、MIPSなど)は、単一の命令で乗算を実行できます。一部のコンパイラは、この事実を無視して、ランタイムライブラリ関数を呼び出して乗算を行うコードを生成していました。多くの場合、16シフトはライブラリルーチンによって行われます(x86もこのようなシフトを実行できます)。
したがって、乗算のために1つまたは2つのライブラリー呼び出しが残っています。これは深刻な結果をもたらします。シフトが遅くなるだけでなく、レジスターは関数呼び出し全体で保存する必要があり、インライン化とコード展開の助けにもなりません。
(インライン)アセンブラーで同じコードを書き換えると、速度が大幅に向上します。
これに加えて、ASMの使用は問題を解決するための最良の方法ではありません。ほとんどのコンパイラでは、Cでそれらを表現できない場合に、一部のアセンブラ命令を組み込み形式で使用できます。たとえば、VS.NET2008コンパイラは、32 * 32 = 64ビットmulを__emulとして、64ビットシフトを__ll_rshiftとして公開します。
組み込み関数を使用すると、Cコンパイラが何が起こっているのかを理解できるように関数を書き換えることができます。これにより、コードをインライン化し、レジスタを割り当て、共通の部分式を削除し、定数の伝播を行うこともできます。あなたは巨大になるでしょうやり方という手書きのアセンブラコードよりもパフォーマンスの改善を。
参考:VS.NETコンパイラの固定小数点mulの最終結果は次のとおりです。
int inline FixedPointMul (int a, int b)
{
return (int) __ll_rshift(__emul(a,b),16);
}
固定小数点除算のパフォーマンスの違いはさらに大きくなります。いくつかのasm行を記述することにより、除算の重い固定小数点コードに対して最大10倍の改善がありました。
Visual C ++ 2013を使用すると、両方の方法で同じアセンブリコードが提供されます。
2007年のgcc4.1は、純粋なCバージョンも適切に最適化します。(Godboltコンパイラエクスプローラーには以前のバージョンのgccがインストールされていませんが、おそらく古いGCCバージョンでも組み込み関数なしでこれを行うことができます。)
Godboltコンパイラーエクスプローラーの x86(32ビット)およびARMのソース+ asmを参照してください。(残念ながら、単純な純粋なCバージョンから不良コードを生成するのに十分古いコンパイラーはありません。)
現代のCPUは、Cはのための演算子を持っていないことを行うことができますすべてで、同様popcnt
またはビットスキャン最初または最後のセットビットを見つけること。(POSIXにはffs()
関数がありますが、そのセマンティクスはx86 bsf
/と一致しません。https://en.wikipedia.org/wiki/Find_first_setをbsr
参照してください)。
一部のコンパイラーは、整数の設定ビット数をカウントするループを認識し、それをpopcnt
命令にコンパイルすることができます(コンパイル時に有効になっている場合)が__builtin_popcnt
、GNU C、またはx86で使用する方がはるかに信頼性が高いSSE4.2でハードウェアをターゲットにする:_mm_popcnt_u32
から<immintrin.h>
。
またはC ++では、に割り当ててstd::bitset<32>
を使用します.count()
。(これは、標準ライブラリを通じてpopcountの最適化された実装を移植可能に公開する方法を言語が見つけた場合であり、常に正しいものにコンパイルされ、ターゲットがサポートするものを利用できます。)httpsも参照してください。://en.wikipedia.org/wiki/Hamming_weight#Language_support。
同様に、一部のC実装でntohl
コンパイルbswap
(エンディアン変換用のx86 32ビットバイトスワップ)できます。
組み込み関数または手書きのasmのもう1つの主要な領域は、SIMD命令による手動のベクトル化です。コンパイラーはdst[i] += src[i] * 10.0;
、のような単純なループでは問題ありませんが、状況が複雑になると、多くの場合、問題が発生するか、自動ベクトル化がまったく行われません。たとえば、SIMDを使用してatoiを実装する方法のようなものを取得することはほとんどありませんか?コンパイラによってスカラーコードから自動的に生成されます。