乗算と除算は、ビット演算子を使用して実現できます。
i*2 = i<<1
i*3 = (i<<1) + i;
i*10 = (i<<3) + (i<<1)
等々。
直接(i<<3)+(i<<1)
使用i*10
するよりも、sayを使用して10を乗算する方が実際に高速ですか?この方法で乗算または除算できない入力の種類はありますか?
乗算と除算は、ビット演算子を使用して実現できます。
i*2 = i<<1
i*3 = (i<<1) + i;
i*10 = (i<<3) + (i<<1)
等々。
直接(i<<3)+(i<<1)
使用i*10
するよりも、sayを使用して10を乗算する方が実際に高速ですか?この方法で乗算または除算できない入力の種類はありますか?
回答:
短い答え:ありそうもない。
長い答え:コンパイラにはオプティマイザがあり、ターゲットプロセッサアーキテクチャで可能な限り速く乗算する方法を知っています。最善の策は、コンパイラに意図を明確に伝え(つまり、i << 1ではなくi * 2)、コンパイラに最速のアセンブリ/マシンコードシーケンスを決定させることです。マイクロコード内のシフトと加算のシーケンスとして、プロセッサ自体が乗算命令を実装している可能性さえあります。
結論-これについて心配するのに多くの時間を費やさないでください。シフトするつもりなら、シフト。乗算するつもりなら、乗算します。意味的に最も明確なことを行います。同僚は後で感謝します。または、可能であれば、後でそうする場合は後で呪います。
gcc -O3
return i*10
millis() >> 2
です。単に割り算を求めるのは多すぎるでしょうか?
i / 32
vs i >> 5
とi / 4
vs i >> 2
を最適化-O3でテストしましたが、結果のアセンブリはまったく同じでした。最初に除算を使用するのは好きではありませんでしたが、それは私の意図を説明しており、出力は同じです。
ちょうど具体的な測定ポイント:何年も前に、ハッシュアルゴリズムの2つのバージョンをベンチマークしました。
unsigned
hash( char const* s )
{
unsigned h = 0;
while ( *s != '\0' ) {
h = 127 * h + (unsigned char)*s;
++ s;
}
return h;
}
そして
unsigned
hash( char const* s )
{
unsigned h = 0;
while ( *s != '\0' ) {
h = (h << 7) - h + (unsigned char)*s;
++ s;
}
return h;
}
私がベンチマークしたすべてのマシンで、最初のマシンは少なくとも2番目のマシンと同じくらい高速でした。やや驚いたことに、それは時々より高速でした(例えば、Sun Sparc)。ハードウェアが高速な乗算をサポートしていなかった場合(そしてそのほとんどが当時はサポートされていませんでした)、コンパイラーは乗算をシフトと加算/減算の適切な組み合わせに変換します。また、最終的な目標を知っているため、シフトと追加/サブを明示的に記述した場合よりも少ない指示で実行できる場合があります。
これは15年前のようなものでした。うまくいけば、コンパイラーはそれ以来良くなっているだけなので、コンパイラーが正しいことを実行することにかなりの数を期待できます。(また、コードがC'ishのように見える理由は、15年以上前だったからです。std::string
今日は明らかにイテレータを使用します。)
ここでの他のすべての良い答えに加えて、除算または乗算を意味するときにシフトを使用しない別の理由を指摘させてください。乗算と加算の相対的な優先順位を忘れて誰かがバグを導入するのを見たことがありません。メンテナンスプログラマがシフトを介した「乗算」は論理的には乗算であることを構文的にではなく、構文的には乗算と同じ優先順位ではないことを忘れたときに導入されたバグを見ました。x * 2 + z
とx << 1 + z
はかなり異なります!
数値を扱う場合は、などの算術演算子を使用します+ - * / %
。ビットの配列で作業している場合は、などのビット操作演算子を使用します& ^ | >>
。それらを混ぜないでください。少しいじると算術の両方を持つ式は、発生するのを待っているバグです。
これはプロセッサとコンパイラに依存します。一部のコンパイラはすでにこの方法でコードを最適化していますが、そうでないコンパイラもあります。したがって、この方法でコードを最適化する必要があるたびに確認する必要があります。
必死に最適化する必要がない限り、アセンブリ命令またはプロセッササイクルを保存するためだけにソースコードをスクランブルすることはありません。
>>
演算子はより高速で/
あり、符号付きの値が負になる可能性がある場合は、意味的に同様に優れていることがよくあります。x>>4
生成される値が必要な場合x < 0 ? -((-1-x)/16)-1 : x/16;
、それはよりも明確であり、コンパイラーが後者の式をどのように素晴らしいものに最適化できるか想像できません。
i * 10を直接使用するよりも、say(i << 3)+(i << 1)を使用して10を乗算する方が実際に高速ですか?
それはあなたのマシン上にあるかもしれないし、そうでないかもしれません-もし気にしたら、あなたの実際の使用法で測定してください。
ベンチマークを有意義に行うことは非常に困難ですが、いくつかの事実を見ることができます。http://www.penguin.cz/~literakl/intel/s.html#SALおよび http://www.penguin.cz/~literakl/intel/i.html#IMULから、x86クロックサイクルの概念がわかります算術シフトと乗算に必要です。"486"(最新のものはリストされています)、32ビットのレジスタとイミディエイトに固執するとします。IMULは13〜42サイクル、IDIV 44です。各SALは2を取り、1を加えます。勝者のように。
最近、コアi7で:
(http://software.intel.com/en-us/forums/showthread.php?t=61481から)
レイテンシは、整数の加算では1サイクル、整数の乗算では3サイクルです。レイテンシとスループットは、http://www.intel.com/products/processor/manuals/にある「インテル®64およびIA-32アーキテクチャー最適化リファレンスマニュアル」の付録Cにあります。
(一部のIntelの宣伝文から)
Core i7はSSEを使用して、加算と乗算の同時命令を発行できるため、クロックサイクルあたりのピークレートは8浮動小数点演算(FLOP)になります。
これは、物事がどこまで進んだかについてのアイデアを与えます。ビットシフトと同様*
に、90年代にまで真剣に取られた最適化のトリビアは、現在はもう使われていません。ビットシフトはさらに高速ですが、2の累乗ではないmul / divの場合、すべてのシフトを実行して結果を追加するまでに、速度は再び遅くなります。次に、より多くの命令はより多くのキャッシュ障害、より多くの潜在的なパイプラインの問題、より多くの一時レジスターの使用はスタックからのレジスター内容のより多くの保存と復元を意味するかもしれません...それはすべての影響を明確に定量化するにはあまりに複雑になりすぎますが、主に否定的です。
より一般的には、質問にはCおよびC ++のタグが付けられます。これらは第3世代言語として、基になるCPU命令セットの詳細を隠すように特別に設計されています。言語標準を満たすには、基盤となるハードウェアがサポートしていない場合でも、乗算およびシフト演算(およびその他の多くの演算)をサポートする必要があります。このような場合、他の多くの命令を使用して必要な結果を合成する必要があります。同様に、CPUに浮動小数点演算がなく、FPUがない場合は、浮動小数点演算のソフトウェアサポートを提供する必要があります。最新のCPUはすべてサポートし*
、<<
、これは不合理に理論的かつ歴史的に見えるかもしれませんが、重要なことは、実装を選択する自由は両方の方向に進むということです。CPUが一般的なケースでソースコードで要求された操作を実装する命令を持っている場合でも、コンパイラは自由にコンパイラーが直面している特定のケースに適しているため、他の方法を選択してください。
例(架空のアセンブリ言語を使用)
source literal approach optimised approach
#define N 0
int x; .word x xor registerA, registerA
x *= N; move x -> registerA
move x -> registerB
A = B * immediate(0)
store registerA -> x
...............do something more with x...............
排他的または(xor
)のような命令はソースコードとは関係ありませんが、それ自体で何かをXORすると、すべてのビットがクリアされるため、何かを0に設定するために使用できます。メモリアドレスを意味するソースコードは、何も使用されない可能性があります。
この種のハッキングは、コンピューターが使用されている限り使用されてきました。3GLの初期の頃は、開発者の取り込みを確保するために、コンパイラの出力は既存のハードコアの手最適化アセンブリ言語開発者を満足させる必要がありました。生成されたコードが遅くなったり、冗長になったり、それ以外に悪くなったりしないコミュニティ。コンパイラーは多くの優れた最適化をすぐに採用しました-個々のアセンブリ言語プログラマーが持つ可能性よりも、集中化された最適化されたストアになりました。誰かがその経験を彼らにフィードバックするまで、コンパイラは彼らが言われたようにちょうどそうする間、それを気を抜いて何か良いものを模索します。
したがって、特定のハードウェアでシフトと追加がさらに速い場合でも、コンパイラー作成者は、安全で有益な場合に正確に機能する可能性があります。
ハードウェアが変更された場合、再コンパイルしてターゲットCPUを確認し、別の最良の選択を行うことができますが、「最適化」を再確認したり、乗算を使用するコンパイル環境とシフトするコンパイル環境をリストしたりすることはほとんどありません。10年以上前に書かれた2のべき乗以外のすべてのビットシフトされた「最適化」を考えてみてください。現在のプロセッサーで実行すると、コードが遅くなります...!
ありがたいことに、GCCのような優れたコンパイラは通常、最適化が有効(つまり...main(...) { return (argc << 4) + (argc << 2) + argc; }
-> imull $21, 8(%ebp), %eax
)になっている場合、一連のビットシフトと算術を直接乗算で置き換えることができるため、コードを修正しなくても再コンパイルが役立つ場合がありますが、これは保証されません。
乗算または除算を実装する奇妙なビットシフトコードは、概念的に達成しようとしていたことをはるかに表現しにくいため、他の開発者は混乱し、混乱したプログラマーは、バグを導入したり、正気に見えるようにするために不可欠なものを削除したりする可能性が高くなります。それらが本当に具体的に有益な場合に自明ではないことだけを行い、それからそれらを適切に文書化する(ただし、とにかく直観的な他のものは文書化しない)場合、誰もが幸せになります。
int
実際に値を保存するだけであるなどの追加の知識がある場合x
、y
およびz
、それらの値に対して機能するいくつかの命令を実行して、コンパイラーがない場合よりも迅速に結果を得ることができる場合があります。その洞察とすべてのint
価値のために働く実装が必要です。たとえば、あなたの質問を考えてみましょう:
乗算と除算はビット演算子を使用して実現できます...
あなたは乗算を説明していますが、除算はどうですか?
int x;
x >> 1; // divide by 2?
C ++標準5.8によると:
-3- E1 >> E2の値は、E1を右シフトしたE2ビット位置です。E1に符号なしの型がある場合、またはE1に符号付きの型があり、負でない値である場合、結果の値は、E1をE2で累乗した数量2で割った商の整数部です。E1に符号付きの型と負の値がある場合、結果の値は実装定義です。
そのため、x
が負の場合、ビットシフトは実装定義の結果になります。異なるマシンでは同じように機能しない可能性があります。しかし、/
はるかに予測どおりに機能します。 (マシンごとに負の数の表現が異なり、したがって表現を構成するビット数が同じであっても範囲が異なるため、完全に一貫性がない場合もあります。)
「私は気にしない...それint
は従業員の年齢を格納しているので、決して負になることはありません」と言うかもしれません。そのような特別な洞察があれば、はい- >>
コードで明示的に行わない限り、安全な最適化はコンパイラーによって渡される可能性があります。しかし、それは危険であり、ほとんどの場合、この種の洞察が得られないのでほとんど役に立ちません。同じコードで作業している他のプログラマーは、あなたがデータのいくつかの異常な期待に家を賭けたことを知らないでしょう処理します...「最適化」のために、それらに対する完全に安全な変更は裏目に出る可能性があります。
この方法で乗算または除算できない入力の種類はありますか?
はい...上で述べたように、負の数は、ビットシフトによって「除算」されたときに、実装で定義された動作をします。
intVal>>1
異なるセマンティクスがintVal/2
使用されます。ありふれたアーキテクチャがに対してもたらす価値をポータブルな方法で計算する必要がある場合intVal >> 1
、式はかなり複雑で読みにくくする必要があり、に対して生成されたコードよりも実質的に劣るコードを生成する可能性がありますintVal >> 1
。
これをコンパイルして私のマシンで試してみました:
int a = ...;
int b = a * 10;
分解すると出力が生成されます:
MOV EAX,DWORD PTR SS:[ESP+1C] ; Move a into EAX
LEA EAX,DWORD PTR DS:[EAX+EAX*4] ; Multiply by 5 without shift !
SHL EAX, 1 ; Multiply by 2 using shift
このバージョンは、手動で最適化されたコードよりも高速で、純粋なシフトと追加が行われます。
コンパイラーが何を考え出すかは本当にわからないので、コンパイラーが最適化できないことを知っている非常に正確な場合を除いて、通常の乗算を単純に記述し、彼が望む方法で最適化できるようにする方が良いでしょう。
vector<T>::size()
。私のコンパイラはかなり古くからありました!:)
通常、シフトは命令レベルで乗算するよりもはるかに高速ですが、時期尚早の最適化を行うために時間を浪費している可能性があります。コンパイラーは、コンパイル時にこれらの最適化を実行します。自分で行うと、読みやすさに影響し、パフォーマンスに影響を与えない可能性があります。プロファイリングを行い、これがボトルネックであるとわかった場合にのみ、このようなことをする価値があります。
実際、「マジックディビジョン」として知られるディビジョントリックは、実際に大きな利益を生み出すことができます。繰り返しますが、最初にプロファイルを作成して、それが必要かどうかを確認する必要があります。しかし、それを使用する場合、同じ除算セマンティクスに必要な命令を理解するのに役立つ便利なプログラムがあります。次に例を示します。http://www.masm32.com/board/index.php?topic = 12421.0
MASM32のOPのスレッドから持ち上げた例:
include ConstDiv.inc
...
mov eax,9999999
; divide eax by 100000
cdiv 100000
; edx = quotient
生成されます:
mov eax,9999999
mov edx,0A7C5AC47h
add eax,1
.if !CARRY?
mul edx
.endif
shr edx,16
シフトおよび整数乗算命令は、最近のほとんどのCPUで同様のパフォーマンスを発揮します。整数乗算命令は、1980年代には比較的遅くなりましたが、一般的にはこれは当てはまりません。整数乗算命令はレイテンシが高くなる可能性があるため、シフトの方が望ましい場合もあります。より多くの実行ユニットをビジー状態に保つことができる場合の同上(これは両方の方法を削減できますが)。
ただし、整数の除算はまだ比較的遅いため、2の累乗による除算の代わりにシフトを使用することは依然として有利であり、ほとんどのコンパイラーはこれを最適化として実装します。ただし、この最適化を有効にするには、被除数が符号なしであるか、正であることがわかっている必要があります。負の被除数の場合、シフトと除算は同等ではありません!
#include <stdio.h>
int main(void)
{
int i;
for (i = 5; i >= -5; --i)
{
printf("%d / 2 = %d, %d >> 1 = %d\n", i, i / 2, i, i >> 1);
}
return 0;
}
出力:
5 / 2 = 2, 5 >> 1 = 2
4 / 2 = 2, 4 >> 1 = 2
3 / 2 = 1, 3 >> 1 = 1
2 / 2 = 1, 2 >> 1 = 1
1 / 2 = 0, 1 >> 1 = 0
0 / 2 = 0, 0 >> 1 = 0
-1 / 2 = 0, -1 >> 1 = -1
-2 / 2 = -1, -2 >> 1 = -1
-3 / 2 = -1, -3 >> 1 = -2
-4 / 2 = -2, -4 >> 1 = -2
-5 / 2 = -2, -5 >> 1 = -3
したがって、コンパイラを支援する場合は、被除数の変数または式が明示的に符号なしであることを確認してください。
私の知る限り、一部のマシンでは、乗算に最大16〜32マシンサイクルが必要です。だから、はい、マシン・タイプに応じて、ビットシフト演算子は、より高速な乗算/除算よりも。
ただし、特定のマシンには、乗算/除算用の特別な命令を含む演算プロセッサがあります。
ドリュー・ホールのマークされた答えに同意します。答えはいくつかの追加のメモを使用することもできます。
ソフトウェア開発者の大多数にとって、プロセッサとコンパイラはもはやこの問題に関係ありません。私たちのほとんどは8088とMS-DOSをはるかに超えています。それはおそらく、組み込みプロセッサ向けにまだ開発中の人にのみ関連します...
私のソフトウェア会社では、すべての数学にMath(add / sub / mul / div)を使用する必要があります。一方、データタイプ間で変換する場合はShiftを使用する必要があります。u >> は、 n / 256 ではなく n >> 8としてバイトに変換します。
符号付き整数と右シフトvs除算の場合、違いが生じる可能性があります。負の数の場合、シフトは負の無限大に丸め、除算はゼロに丸めます。もちろん、コンパイラーは除算をより安価なものに変更しますが、通常は除算と同じ丸め動作をするものに変更します。これは、変数が負にならないことを証明できないか、単にそうしないためです。ケア。したがって、数値が負にならないことを証明できる場合、またはどの方法で丸めるかを気にしない場合は、違いを生む可能性が高い方法で最適化を実行できます。
unsigned
Pythonは、同じ乱数に対して同じ乗算を1億回実行します。
>>> from timeit import timeit
>>> setup_str = 'import scipy; from scipy import random; scipy.random.seed(0)'
>>> N = 10*1000*1000
>>> timeit('x=random.randint(65536);', setup=setup_str, number=N)
1.894096851348877 # Time from generating the random #s and no opperati
>>> timeit('x=random.randint(65536); x*2', setup=setup_str, number=N)
2.2799630165100098
>>> timeit('x=random.randint(65536); x << 1', setup=setup_str, number=N)
2.2616429328918457
>>> timeit('x=random.randint(65536); x*10', setup=setup_str, number=N)
2.2799630165100098
>>> timeit('x=random.randint(65536); (x << 3) + (x<<1)', setup=setup_str, number=N)
2.9485139846801758
>>> timeit('x=random.randint(65536); x // 2', setup=setup_str, number=N)
2.490908145904541
>>> timeit('x=random.randint(65536); x / 2', setup=setup_str, number=N)
2.4757170677185059
>>> timeit('x=random.randint(65536); x >> 1', setup=setup_str, number=N)
2.2316000461578369
したがって、Pythonで2のべき乗による乗算/除算ではなくシフトを実行すると、わずかに改善されます(除算では最大10%、乗算では最大1%)。2のべき乗でない場合は、かなりの速度低下が見られます。
繰り返しになりますが、これらの#はプロセッサ、コンパイラ(またはインタプリタ-単純化のためにPythonで行ったもの)によって異なります。
他の人と同様に、時期尚早に最適化しないでください。非常に読みやすいコードを記述し、十分に高速でない場合はプロファイルを作成してから、遅い部分を最適化してください。覚えておいてください、あなたのコンパイラーはあなたよりも最適化に優れています。
コンパイラーが実行できない最適化があります。これらは最適化された入力のセットに対してのみ機能するためです。
以下は、64ビットの「逆数による乗算」を行う高速な除算を実行できるc ++サンプルコードです。分子と分母の両方が特定のしきい値を下回っている必要があります。通常の除算よりも実際に高速になるには、64ビット命令を使用するようにコンパイルする必要があることに注意してください。
#include <stdio.h>
#include <chrono>
static const unsigned s_bc = 32;
static const unsigned long long s_p = 1ULL << s_bc;
static const unsigned long long s_hp = s_p / 2;
static unsigned long long s_f;
static unsigned long long s_fr;
static void fastDivInitialize(const unsigned d)
{
s_f = s_p / d;
s_fr = s_f * (s_p - (s_f * d));
}
static unsigned fastDiv(const unsigned n)
{
return (s_f * n + ((s_fr * n + s_hp) >> s_bc)) >> s_bc;
}
static bool fastDivCheck(const unsigned n, const unsigned d)
{
// 32 to 64 cycles latency on modern cpus
const unsigned expected = n / d;
// At least 10 cycles latency on modern cpus
const unsigned result = fastDiv(n);
if (result != expected)
{
printf("Failed for: %u/%u != %u\n", n, d, expected);
return false;
}
return true;
}
int main()
{
unsigned result = 0;
// Make sure to verify it works for your expected set of inputs
const unsigned MAX_N = 65535;
const unsigned MAX_D = 40000;
const double ONE_SECOND_COUNT = 1000000000.0;
auto t0 = std::chrono::steady_clock::now();
unsigned count = 0;
printf("Verifying...\n");
for (unsigned d = 1; d <= MAX_D; ++d)
{
fastDivInitialize(d);
for (unsigned n = 0; n <= MAX_N; ++n)
{
count += !fastDivCheck(n, d);
}
}
auto t1 = std::chrono::steady_clock::now();
printf("Errors: %u / %u (%.4fs)\n", count, MAX_D * (MAX_N + 1), (t1 - t0).count() / ONE_SECOND_COUNT);
t0 = t1;
for (unsigned d = 1; d <= MAX_D; ++d)
{
fastDivInitialize(d);
for (unsigned n = 0; n <= MAX_N; ++n)
{
result += fastDiv(n);
}
}
t1 = std::chrono::steady_clock::now();
printf("Fast division time: %.4fs\n", (t1 - t0).count() / ONE_SECOND_COUNT);
t0 = t1;
count = 0;
for (unsigned d = 1; d <= MAX_D; ++d)
{
for (unsigned n = 0; n <= MAX_N; ++n)
{
result += n / d;
}
}
t1 = std::chrono::steady_clock::now();
printf("Normal division time: %.4fs\n", (t1 - t0).count() / ONE_SECOND_COUNT);
getchar();
return result;
}
2つの累乗で乗算または除算したい場合は、コンパイラーがMUL / DIVに変換したとしても、ビットシフト演算子の使用で問題が発生することはないと思います。マクロ)とにかく、それらの場合、特にシフトが1より大きい場合、特にCPUにビットシフト演算子がない場合はMUL / DIVになりますが、CPUがビットシフト演算子を使用すると、マイクロコードの分岐を回避できます。これにより、命令数が少なくなります。
高密度のバイナリツリーで動作しているため、多くの2倍算/半分演算を必要とするコードを今書いています。さらに、加算よりも最適であると思われる演算が1つあります-左(2の累乗) )加算でシフトします。これは左シフトとxorで置き換えることができます。シフトが追加するビット数よりも広い場合、簡単な例は(i << 1)^ 1で、1を2倍の値に追加します。もちろん、これは右シフト(2の累乗の除算)には適用されません。左(リトルエンディアン)シフトのみがギャップをゼロで埋めるためです。
私のコードでは、これらの2の乗算/除算と2の累乗の演算が非常に集中的に使用されており、式はすでに非常に短いため、削除できる各命令はかなりの利益になります。プロセッサがこれらのビットシフト演算子をサポートしていない場合、ゲインは発生しませんが、ロスも発生しません。
また、私が書いているアルゴリズムでは、発生する動きを視覚的に表現しているので、その意味で実際にはより明確です。二分木の左側は大きく、右側は小さくなります。それだけでなく、私のコードでは、奇数と偶数には特別な意味があり、ツリー内のすべての左側の子は奇数であり、すべての右側の子とルートは偶数です。まだ遭遇していないが、実際にはこれさえ考えていなかった場合もあります。x&1は、x%2よりも最適な操作である可能性があります。偶数のx&1はゼロを生成しますが、奇数の場合は1を生成します。
奇数/偶数の識別だけではなく、x&3のゼロが得られた場合、4は数値の係数であり、x%7の場合は8と同じであることがわかります。これらのケースのユーティリティは限られていることを知っていますが、ビットごとの演算はほとんど常に最速であり、コンパイラにとってあいまいになる可能性が最も低いため、モジュラス演算を回避し、代わりにビットごとの論理演算を使用できることを知っておくと便利です。
私はほとんど密な二分木の分野を発明しているので、人々が2のべき乗のみ、または2の乗算/除算のみで因数分解のみを実行することを望んでいないので、人々はこのコメントの価値を理解できないかもしれません。
実際に高速かどうかは、実際に使用されているハードウェアとコンパイラによって異なります。
gccコンパイラでx + x、x * 2、x << 1構文の出力を比較すると、x86アセンブリで同じ結果が得られます:https : //godbolt.org/z/JLpp0j
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov eax, DWORD PTR [rbp-4]
add eax, eax
pop rbp
ret
したがって、gccは、入力した内容とは関係なく、彼自身の最善の解決策を決定するのに十分なほど賢いと考えることができます。
私も家を倒せるかどうか見たかった。これは、任意の数による任意の数の乗算のより一般的なビット単位です。私が作成したマクロは、通常の乗算よりも約25%長く、2倍遅くなっています。2の倍数に近いか、2の倍数で構成されている場合に他の人が言うように、勝つかもしれません。(X << 4)+(X << 2)+(X << 1)+ Xで構成されるX * 23のように、(X << 6)+ Xで構成されるX * 65より遅くなります。
#include <stdio.h>
#include <time.h>
#define MULTIPLYINTBYMINUS(X,Y) (-((X >> 30) & 1)&(Y<<30))+(-((X >> 29) & 1)&(Y<<29))+(-((X >> 28) & 1)&(Y<<28))+(-((X >> 27) & 1)&(Y<<27))+(-((X >> 26) & 1)&(Y<<26))+(-((X >> 25) & 1)&(Y<<25))+(-((X >> 24) & 1)&(Y<<24))+(-((X >> 23) & 1)&(Y<<23))+(-((X >> 22) & 1)&(Y<<22))+(-((X >> 21) & 1)&(Y<<21))+(-((X >> 20) & 1)&(Y<<20))+(-((X >> 19) & 1)&(Y<<19))+(-((X >> 18) & 1)&(Y<<18))+(-((X >> 17) & 1)&(Y<<17))+(-((X >> 16) & 1)&(Y<<16))+(-((X >> 15) & 1)&(Y<<15))+(-((X >> 14) & 1)&(Y<<14))+(-((X >> 13) & 1)&(Y<<13))+(-((X >> 12) & 1)&(Y<<12))+(-((X >> 11) & 1)&(Y<<11))+(-((X >> 10) & 1)&(Y<<10))+(-((X >> 9) & 1)&(Y<<9))+(-((X >> 8) & 1)&(Y<<8))+(-((X >> 7) & 1)&(Y<<7))+(-((X >> 6) & 1)&(Y<<6))+(-((X >> 5) & 1)&(Y<<5))+(-((X >> 4) & 1)&(Y<<4))+(-((X >> 3) & 1)&(Y<<3))+(-((X >> 2) & 1)&(Y<<2))+(-((X >> 1) & 1)&(Y<<1))+(-((X >> 0) & 1)&(Y<<0))
#define MULTIPLYINTBYSHIFT(X,Y) (((((X >> 30) & 1)<<31)>>31)&(Y<<30))+(((((X >> 29) & 1)<<31)>>31)&(Y<<29))+(((((X >> 28) & 1)<<31)>>31)&(Y<<28))+(((((X >> 27) & 1)<<31)>>31)&(Y<<27))+(((((X >> 26) & 1)<<31)>>31)&(Y<<26))+(((((X >> 25) & 1)<<31)>>31)&(Y<<25))+(((((X >> 24) & 1)<<31)>>31)&(Y<<24))+(((((X >> 23) & 1)<<31)>>31)&(Y<<23))+(((((X >> 22) & 1)<<31)>>31)&(Y<<22))+(((((X >> 21) & 1)<<31)>>31)&(Y<<21))+(((((X >> 20) & 1)<<31)>>31)&(Y<<20))+(((((X >> 19) & 1)<<31)>>31)&(Y<<19))+(((((X >> 18) & 1)<<31)>>31)&(Y<<18))+(((((X >> 17) & 1)<<31)>>31)&(Y<<17))+(((((X >> 16) & 1)<<31)>>31)&(Y<<16))+(((((X >> 15) & 1)<<31)>>31)&(Y<<15))+(((((X >> 14) & 1)<<31)>>31)&(Y<<14))+(((((X >> 13) & 1)<<31)>>31)&(Y<<13))+(((((X >> 12) & 1)<<31)>>31)&(Y<<12))+(((((X >> 11) & 1)<<31)>>31)&(Y<<11))+(((((X >> 10) & 1)<<31)>>31)&(Y<<10))+(((((X >> 9) & 1)<<31)>>31)&(Y<<9))+(((((X >> 8) & 1)<<31)>>31)&(Y<<8))+(((((X >> 7) & 1)<<31)>>31)&(Y<<7))+(((((X >> 6) & 1)<<31)>>31)&(Y<<6))+(((((X >> 5) & 1)<<31)>>31)&(Y<<5))+(((((X >> 4) & 1)<<31)>>31)&(Y<<4))+(((((X >> 3) & 1)<<31)>>31)&(Y<<3))+(((((X >> 2) & 1)<<31)>>31)&(Y<<2))+(((((X >> 1) & 1)<<31)>>31)&(Y<<1))+(((((X >> 0) & 1)<<31)>>31)&(Y<<0))
int main()
{
int randomnumber=23;
int randomnumber2=23;
int checknum=23;
clock_t start, diff;
srand(time(0));
start = clock();
for(int i=0;i<1000000;i++)
{
randomnumber = rand() % 10000;
randomnumber2 = rand() % 10000;
checknum=MULTIPLYINTBYMINUS(randomnumber,randomnumber2);
if (checknum!=randomnumber*randomnumber2)
{
printf("s %i and %i and %i",checknum,randomnumber,randomnumber2);
}
}
diff = clock() - start;
int msec = diff * 1000 / CLOCKS_PER_SEC;
printf("MULTIPLYINTBYMINUS Time %d milliseconds", msec);
start = clock();
for(int i=0;i<1000000;i++)
{
randomnumber = rand() % 10000;
randomnumber2 = rand() % 10000;
checknum=MULTIPLYINTBYSHIFT(randomnumber,randomnumber2);
if (checknum!=randomnumber*randomnumber2)
{
printf("s %i and %i and %i",checknum,randomnumber,randomnumber2);
}
}
diff = clock() - start;
msec = diff * 1000 / CLOCKS_PER_SEC;
printf("MULTIPLYINTBYSHIFT Time %d milliseconds", msec);
start = clock();
for(int i=0;i<1000000;i++)
{
randomnumber = rand() % 10000;
randomnumber2 = rand() % 10000;
checknum= randomnumber*randomnumber2;
if (checknum!=randomnumber*randomnumber2)
{
printf("s %i and %i and %i",checknum,randomnumber,randomnumber2);
}
}
diff = clock() - start;
msec = diff * 1000 / CLOCKS_PER_SEC;
printf("normal * Time %d milliseconds", msec);
return 0;
}