2016-05-22 6 views
1

2つの数字の間の符号の変化を検出する方法についていくつかの実験を行っていました。 xyという2つの数字が与えられているとします。例えば、doubleは、それらが異なる符号を持っているかどうかを知りたいとします。私はいつも、私は一般的にsignbitによって検出されたサインの変化とゼロとの比較と比較

x*y > 0 

を見てきたものを真似している。しかし、これは正確に一つが通常どうなるのかのようではない読み込みます。 2つの数値を掛け合わせて読み込み、結果の符号をチェックします。しかし、私たちが本当にやることは、それぞれの数字のサインをチェックし、通常のルールでサインの変更を決定することです。これは似ている

signbit(x)^signbit(y) 

数字を掛けなければならないかどうかは、パフォーマンスに何らかの影響があるかどうかが分かりました。私は悪影響を期待していた。

パフォーマンスを比較すると、前者が高速に計算されます。

なぜか分かりません。コンパイラは、x*y > 0signbit(x)^signbit(y)という意味意味で置き換えることができます。つまり、符号ビットのxorはxyです。説明は何ですか?

注:signbit(x)^signbit(y)x*yが、全体x*y > 0に代わるものではありません。使用

コード:(Visual Studioで作成)

#include <iostream> 
#include <math.h> 
#include <string> 
#include <chrono> 

using namespace std; 
using namespace std::chrono; 

#define N 1000000 

int main() { 

    double x, y; 

    cout << "x = "; 
    cin >> x; 
    cout << "y = "; 
    cin >> y; 

    bool answer; 

    high_resolution_clock::time_point t1 = high_resolution_clock::now(); 
    for (auto i = 0; i < N; ++i) { 
     answer = signbit(x)^signbit(y); 
    } 
    high_resolution_clock::time_point t2 = high_resolution_clock::now(); 
    auto diffBit = std::chrono::duration_cast<std::chrono::nanoseconds>(t2 - t1).count(); 


    high_resolution_clock::time_point t3 = high_resolution_clock::now(); 
    for (auto i = 0; i < N; ++i) { 
     answer = (x*y > 0); 
    } 
    high_resolution_clock::time_point t4 = high_resolution_clock::now(); 
    auto diffMult = std::chrono::duration_cast<std::chrono::nanoseconds>(t4 - t3).count(); 


    cout << "Bit function lasted = " << diffBit << endl; 
    cout << "Multiplication lasted = " << diffMult << endl; 
} 
+2

'x * y> 0'は' signbit(x)^ signbit(y)> 0'と等しくないので、例えば 'x = 1'と' y = 1'をとります。 – Dani

+1

@Dani私は、signbitsのxorをゼロと比較していません。 signbitsのxorはすでに符号の変化を計算します。 – myfirsttime1

+0

テストコード、コンパイラとバージョン、コマンドラインが使用されていますか? –

答えて

1

あなたのテストは、あなたが想像するかもしれない何をしません。

できるだけ多くのコードを削除するために簡素化:

main: 
     pushq %rbp 
     pushq %rbx 
     subq $8, %rsp 
     call getxy() 
     call std::chrono::_V2::system_clock::now() 
     movq %rax, %rbx 
     movl $1000000, %eax 
.L2: 
     subl $1, %eax 
     jne  .L2 
     call std::chrono::_V2::system_clock::now() 
     subq %rbx, %rax 
     movq %rax, %rbp 
     call std::chrono::_V2::system_clock::now() 
     movq %rax, %rbx 
     call std::chrono::_V2::system_clock::now() 
     subq %rbx, %rax 
     movq %rbp, %rdi 
     movq %rax, %rbx 
     call void out<long>(long) 
     movq %rbx, %rdi 
     call void out<long>(long) 
     addq $8, %rsp 
     xorl %eax, %eax 
     popq %rbx 
     popq %rbp 
     ret 

お知らせすべての計算が省略されています

#include <math.h> 
#include <string> 
#include <chrono> 
#include <utility> 
#include <tuple> 

using namespace std; 
using namespace std::chrono; 

#define N 1000000 

std::pair<double, double> getxy(); 
template<class T> void out(T t); 

int main() { 

    double x, y; 
    std::tie(x,y) = getxy(); 


    bool answer; 

    high_resolution_clock::time_point t1 = high_resolution_clock::now(); 
    for (auto i = 0; i < N; ++i) { 
     answer = signbit(x)^signbit(y); 
    } 
    high_resolution_clock::time_point t2 = high_resolution_clock::now(); 
    auto diffBit = std::chrono::duration_cast<std::chrono::nanoseconds>(t2 - t1).count(); 


    high_resolution_clock::time_point t3 = high_resolution_clock::now(); 
    for (auto i = 0; i < N; ++i) { 
     answer = (x*y > 0); 
    } 
    high_resolution_clock::time_point t4 = high_resolution_clock::now(); 
    auto diffMult = std::chrono::duration_cast<std::chrono::nanoseconds>(t4 - t3).count(); 


    out(diffBit); 
    out(diffMult); 
} 

が続いて-O2でgcc5.3でコンパイルし、次のアセンブラを生み出します材料に副作用がないためです。

2番目のループは完全に省略されています。

+0

興味深い。だから、私が得ていた時代は本当に2つの比較ではありません。コンパイラが2番目のループを削除しないようにするにはどうしたらいいですか?各計算の結果をベクトルに格納するとどうなりますか?そのため、100Kループは非常に高速に動作していました。 – myfirsttime1

+0

私は各ループの結果をベクトルに保存しました。そして、実行時間はほぼ同じです。 'x * y> 0 'のものはほんの少し早いです。コンパイラは、乗算全体が必要でないことを理解していると思います。一方、 'x'と' y'の型が、例えば、複数桁の ''多重精度 ''に変わった場合、どれだけ真実が残っているのだろうかと思います。 – myfirsttime1

+0

@ myfirsttime1簡単な答えは、 "パフォーマンステスト"を書くことは本当に難しいということです。コンパイラは賢明で、結果に重要な計算とそうでない計算があります。真実は、実際には、適切なコードでは、同じ答えを生成し、同じ(もしあれば)メモリに触れる限り、区別できないほど最適化されます。したがって、どのアプローチを取るか心配することは合理的な懸念ではありません。意図を表現することについて心配してください。コンパイラに実装を心配させてください。 –

0

最適化の有無にかかわらずコンパイルしていますか? C++ 11では、signbitはオーバーロードされた関数なので、インライン化されていない限り、関数呼び出しのオーバーヘッドが呼び出されます。

いずれの場合でも、signbitが二重から抽出されるsignbitよりも(x * y> 0)の方がはるかに簡単なので、signbitがインライン展開されても、(x * y> 0)。私は、より単純な表現がより効率的であることに驚くことはありません!

何が起こっているかをより詳しく知るには、各式に対して生成されたコードのアセンブリを見てください。

関連する問題