20

私が行った場合:

int x = 4;
pow(2, x);

それは本当に、単に行うよりもはるかに効率が悪いのですか?

1 << 4

4

4 に答える 4

34

はい。これを示す簡単な方法は、同じことを行う次の2つの関数をコンパイルしてから、逆アセンブリを確認することです。

#include <stdint.h>
#include <math.h>

uint32_t foo1(uint32_t shftAmt) {
    return pow(2, shftAmt);
}

uint32_t foo2(uint32_t shftAmt) {
    return (1 << shftAmt);
}

cc -arch armv7 -O3 -S -o - shift.c(ARM asmの方が読みやすいと思いますが、x86が必要な場合は、archフラグを削除してください)

    _foo1:
@ BB#0:
    push    {r7, lr}
    vmov    s0, r0
    mov r7, sp
    vcvt.f64.u32    d16, s0
    vmov    r0, r1, d16
    blx _exp2
    vmov    d16, r0, r1
    vcvt.u32.f64    s0, d16
    vmov    r0, s0
    pop {r7, pc}

_foo2:
@ BB#0:
    movs    r1, #1
    lsl.w   r0, r1, r0
    bx  lr

あなたfoo2は2つの命令だけを取るfoo1のに対して、それはいくつかの命令をとるのを見ることができます。データをFPHWレジスタ(vmov)に移動し、整数をfloat(vcvt.f64.u32)に変換して関数を呼び出しexp、回答をuint(vcvt.u32.f64)に変換して、FPHWからGPレジスタに戻す必要があります。

于 2012-09-24T01:23:40.280 に答える
5

はい。どれだけ言えないのに。それを判断する最も簡単な方法は、ベンチマークを行うことです。

関数はdoubleを使用powします...少なくとも、C標準に準拠している場合。その関数がのベースを検出したときにビットシフトを使用したとしても2、その結論に到達するためのテストと分岐があり、その時点で単純なビットシフトが完了します。また、関数呼び出しのオーバーヘッドについてはまだ考慮していません。

同等性のために、私はあなたが1 << xの代わりに使うつもりだったと思います1 << 4

おそらくコンパイラはこれらの両方を最適化できますが、への呼び出しを最適化する可能性ははるかに低くなりpowます。2の累乗を計算する最速の方法が必要な場合は、シフトを使用してください。

更新...ベンチマークは簡単だと言ったので、それを実行することにしました。たまたまWindowsとVisualC++が手元にあるので、それを使用しました。結果は異なります。私のプログラム:

#include <Windows.h>

#include <cstdio>
#include <cmath>
#include <ctime>

LARGE_INTEGER liFreq, liStart, liStop;


inline void StartTimer()
{
    QueryPerformanceCounter(&liStart);
}


inline double ReportTimer()
{
    QueryPerformanceCounter(&liStop);
    double milli = 1000.0 * double(liStop.QuadPart - liStart.QuadPart) / double(liFreq.QuadPart);
    printf( "%.3f ms\n", milli );
    return milli;
}


int main()
{    
    QueryPerformanceFrequency(&liFreq);

    const size_t nTests = 10000000;
    int x = 4;
    int sumPow = 0;
    int sumShift = 0;

    double powTime, shiftTime;

    // Make an array of random exponents to use in tests.
    const size_t nExp = 10000;
    int e[nExp];
    srand( (unsigned int)time(NULL) );
    for( int i = 0; i < nExp; i++ ) e[i] = rand() % 31;

    // Test power.
    StartTimer();
    for( size_t i = 0; i < nTests; i++ )
    {
        int y = (int)pow(2, (double)e[i%nExp]);
        sumPow += y;
    }
    powTime = ReportTimer();

    // Test shifting.
    StartTimer();
    for( size_t i = 0; i < nTests; i++ )
    {
        int y = 1 << e[i%nExp];
        sumShift += y;
    }
    shiftTime = ReportTimer();

    // The compiler shouldn't optimize out our loops if we need to display a result.
    printf( "Sum power: %d\n", sumPow );
    printf( "Sum shift: %d\n", sumShift );

    printf( "Time ratio of pow versus shift: %.2f\n", powTime / shiftTime );

    system("pause");
    return 0;
}

私の出力:

379.466 ms
15.862 ms
Sum power: 157650768
Sum shift: 157650768
Time ratio of pow versus shift: 23.92
于 2012-09-23T22:49:39.233 に答える
2

それはコンパイラーによって異なりますが、一般的に(コンパイラーが完全に頭がおかしいわけではない場合)はい、シフトは1つのCPU命令であり、もう1つは関数呼び出しであり、現在の状態を保存してスタックフレームを設定する必要があります。指示。

于 2012-09-23T22:45:46.740 に答える
1

ビットシフトはプロセッサにとって非常に基本的な操作であるため、通常はそうです。

一方、多くのコンパイラはコードを最適化するため、パワーを上げることは実際には少しシフトします。

于 2012-09-23T22:48:51.967 に答える