モジュラスの数学的概念がどのように機能するのか、または20 % 3 == 2
これをうまく理解しているのかを尋ねているわけではありません。コンパイラ/インタプリタがこれをどのように決定するのか、もっと興味があります。次のように、非常に素朴な方法を考えることができます。
int modulus(int a, int b) { while (a > b) { a -= b; } return a; }
(aはbよりも小さい可能性があるため、明らかにこれにはさらにいくつかのケースが必要ですが、アイデアは得られます。)しかし、これにはいくつかの深刻なパフォーマンスの問題があります。両方の係数の効率をテストするために、いくつかのC ++をまとめました。演算子と私の素朴な関数。
#include <iostream> #include <chrono> #include <limits> #include "chrono_io" using namespace std; typedef chrono::high_resolution_clock Clock; int mod(int a, int b) { while (a > b) { a -= b; } return a; } int main() { auto start = Clock::now(); cout << 20 % 3 << endl; auto end = Clock::now(); cout << "Modulus on a small number took " << end - start << " seconds to calculate.\n"; start = Clock::now(); cout << numeric_limits<int>::max() % 3 << endl; end = Clock::now(); cout << "Modulus on a large number took " << end - start << " seconds to calculate.\n"; start = Clock::now(); cout << mod(20, 3) << endl; end = Clock::now(); cout << "My modulus function on a small number took " << end - start << " seconds to calculate.\n"; start = Clock::now(); cout << mod(numeric_limits<int>::max(), 3) << endl; end = Clock::now(); cout << "My modulus function on a large number took " << end - start << " seconds to calculate.\n"; }
出力は次のとおりです。
2 Modulus on a small number took 43629 nanoseconds seconds to calculate. 1 Modulus on a large number took 5644 nanoseconds seconds to calculate. 2 My modulus function on a small number took 3682 nanoseconds seconds to calculate. 1 My modulus function on a large number took 2153824554 nanoseconds seconds to calculate.
モジュラス演算子は4,000〜70,000ナノ秒の範囲で変化しました。私の関数は常に小さい数値で勝ちましたが、大きい数値では約2秒かかりました。モジュラス演算子の方がはるかに一貫しているため、ビット単位のレベルで数値を比較すると思います。
では、モジュラス演算子はどのように機能しますか? 決定が非常に簡単な場合がいくつか考えられます。たとえば、mod 2を使用すると、最後のバイトまたはmod4を使用すると、最後から2番目のbを確認できます。 yte。ただし、すべての数値がそのような単純なパターンを持っているわけではありません。
コメント
回答
ほぼすべてのCPUには、値のモジュラスを返す単一の命令があります。たとえば、次のプログラムについて考えてみます。
int main() { int i = 10; return i % 3; }
g ++-Sを使用してIntelOS Xマシンでこれをコンパイルすると、結果は定型文になります。
movl $3, %eax movl $0, -4(%rbp) movl $10, -8(%rbp) movl -8(%rbp), %ecx movl %eax, -12(%rbp) ## 4-byte Spill movl %ecx, %eax cltd movl -12(%rbp), %ecx ## 4-byte Reload idivl %ecx movl %edx, %eax
実際の係数は次の命令で発生します:idivl %ecx
。この命令が実行されると、商は%eax
に配置され、余りは%edx
に配置されます。
実際には、これは%
操作に数クロックサイクルしかかからないことを意味し、その大部分は実際にデータを正しいレジスタに移動します。また、少なくともIntelでは、同じ操作で商と剰余の両方が検出されるため、コメントを参照すると、/
と%
まったく同じ時間かかります。同じ操作です。変更されるのは、コンパイラが結果を取得するレジスタだけです。
過去数十年に作成されたCPUを使用すると、基本的な数学演算(sqrt
またはcos
)は、実際には単一のマシン命令であり、通常、最大で数クロックサイクルしかかかりません。
[UPDATE]
コメントで指摘されているように、何かが正しいタイミングに近づいていることを確認するには、次のように、タイミングセクションから出力を削除する必要があります。
int i; auto start = Clock::now(); i = 20 % 3; auto end = Clock::now(); cout << i << endl;
ただし、タイミングの実際の粒度が実際の粒度を超える可能性があるため、それでも正確ではない可能性があります。時間を計ろうとしています。代わりに、次のようにすることをお勧めします。
int i=0; int x; auto start = Clock::now(); for(i=0;i<1000000;i++) x = i % 3; auto end = Clock::now(); cout << i << endl;
次に、タイミングを1,000,000で割ります。 (割り当て、比較、増分にかかる時間が含まれるため、これは少し長くなります。)私のマシンでは、これにより5ナノ秒の時間が与えられます。
コメント
- 本当に? sqrtは非常に遅い命令だと言われました。
- stackoverflowを参照してください。com / questions / 7724061 / …
- @StevenBurnapその回答は、2011年のアーキテクチャに基づいて、少し古くなっています。
Agner Fog 'テーブル。少なくとも毎年更新されます、HaswellSSE2命令SQRTSS / SQRTPS(32ビット浮動小数点数、重要度が23ビットの)のレイテンシーは11サイクル、SQRTSD / SQRTPD(64ビット浮動小数点数)のレイテンシーは16サイクルです。これらの指示は正確なものです。つまり、高速近似の変形ではありません。実際、古いアーキテクチャではかなり遅くなります。また、
std::sqrt()
を呼び出すと、ほとんどのC ++コンパイラでライブラリ呼び出しが発生することに注意してください。 - コンパイラは、特別な場合に強度低減の最適化を追加で適用できます。たとえば、特定のアーキテクチャで'が高速である場合、x%2はビットマスクに最適化される可能性があります。
- @rwong
std:sqrt
は通常インライン化されます(そしてインラインアセンブリを使用します)が、私は'チェックしたことがありません。
DIV
およびIDIV
命令は、整数除算を実行し、余りもレジスタに格納します)。 ハードウェアがどのように機能するかを尋ねていますか?cout <<
のほとんどの操作にかかる時間を実際に測定しているタイミングです。コンパイラがコンパイル時に'を実行しなかったとしても、プロセッサが20 % 3
を実行するのにかかる時間を実際に測定できるとは思えません。 。