2

余暇に実装しているビデオゲームのために、ルックアップ テーブルを使用して、sinf()、cosf()、および atan2f() の独自のバージョンを実装しようとしました。その意図は、精度は低くなりますが、より高速な実装を行うことです。

私の最初の実装は以下です。関数は機能し、適切な近似値を返します。唯一の問題は、標準の sinf()、cosf()、および atan2f() 関数を呼び出すよりも遅いことです。

それで、私は何を間違っていますか?

// Geometry.h includes definitions of PI, TWO_PI, etc., as
// well as the prototypes for the public functions
#include "Geometry.h"

namespace {
    // Number of entries in the sin/cos lookup table
    const int SinTableCount = 512;

    // Angle covered by each table entry
    const float SinTableDelta = TWO_PI / (float)SinTableCount;

    // Lookup table for Sin() results
    float SinTable[SinTableCount];

    // This object initializes the contents of the SinTable array exactly once
    class SinTableInitializer {
    public:
        SinTableInitializer() {
            for (int i = 0; i < SinTableCount; ++i) {
                SinTable[i] = sinf((float)i * SinTableDelta);
            }
        }
    };
    static SinTableInitializer sinTableInitializer;

    // Number of entries in the atan lookup table
    const int AtanTableCount = 512;

    // Interval covered by each Atan table entry
    const float AtanTableDelta = 1.0f / (float)AtanTableCount;

    // Lookup table for Atan() results
    float AtanTable[AtanTableCount];

    // This object initializes the contents of the AtanTable array exactly once
    class AtanTableInitializer {
    public:
        AtanTableInitializer() {
            for (int i = 0; i < AtanTableCount; ++i) {
                AtanTable[i] = atanf((float)i * AtanTableDelta);
            }
        }
    };
    static AtanTableInitializer atanTableInitializer;

    // Lookup result in table.
    // Preconditions: y > 0, x > 0, y < x
    static float AtanLookup2(float y, float x) {
        assert(y > 0.0f);
        assert(x > 0.0f);
        assert(y < x);

        const float ratio = y / x;
        const int index = (int)(ratio / AtanTableDelta);
        return AtanTable[index];    
    }

}

float Sin(float angle) {
    // If angle is negative, reflect around X-axis and negate result
    bool mustNegateResult = false;
    if (angle < 0.0f) {
        mustNegateResult = true;
        angle = -angle;
    }

    // Normalize angle so that it is in the interval (0.0, PI)
    while (angle >= TWO_PI) {
        angle -= TWO_PI;
    }

    const int index = (int)(angle / SinTableDelta);
    const float result = SinTable[index];

    return mustNegateResult? (-result) : result;
}

float Cos(float angle) {
    return Sin(angle + PI_2);
}

float Atan2(float y, float x) {
    // Handle x == 0 or x == -0
    // (See atan2(3) for specification of sign-bit handling.)
    if (x == 0.0f) {
        if (y > 0.0f) {
            return PI_2;
        }
        else if (y < 0.0f) {
            return -PI_2;
        }
        else if (signbit(x)) {
            return signbit(y)? -PI : PI;
        }
        else {
            return signbit(y)? -0.0f : 0.0f;
        }
    }

    // Handle y == 0, x != 0
    if (y == 0.0f) {
        return (x > 0.0f)? 0.0f : PI;
    }

    // Handle y == x
    if (y == x) {
        return (x > 0.0f)? PI_4 : -(3.0f * PI_4);
    }

    // Handle y == -x
    if (y == -x) {
        return (x > 0.0f)? -PI_4 : (3.0f * PI_4);
    }

    // For other cases, determine quadrant and do appropriate lookup and calculation
    bool right = (x > 0.0f);
    bool top = (y > 0.0f);
    if (right && top) {
        // First quadrant
        if (y < x) {
            return AtanLookup2(y, x);
        }
        else {
            return PI_2 - AtanLookup2(x, y);
        }
    }
    else if (!right && top) {
        // Second quadrant
        const float posx = fabsf(x);
        if (y < posx) {
            return PI - AtanLookup2(y, posx);
        }
        else {
            return PI_2 + AtanLookup2(posx, y);
        }
    }
    else if (!right && !top) {
        // Third quadrant
        const float posx = fabsf(x);
        const float posy = fabsf(y);
        if (posy < posx) {
            return -PI + AtanLookup2(posy, posx);
        }
        else {
            return -PI_2 - AtanLookup2(posx, posy);
        }
    }
    else { // right && !top
        // Fourth quadrant
        const float posy = fabsf(y);
        if (posy < x) {
            return -AtanLookup2(posy, x);
        }
        else {
            return -PI_2 + AtanLookup2(x, posy);
        }
    }

    return 0.0f;
}
4

5 に答える 5

9

「時期尚早の最適化はすべての悪の根源です」-ドナルド・クヌース

現在、コンパイラーは、最新のプロセッサー(SSEなど)を最大限に活用する三角関数に非常に効率的な組み込み関数を提供します。これが、組み込み関数に勝るものがない理由を説明しています。これらの部分に多くの時間を費やさないでください。代わりに、プロファイラーで見つけることができる実際のボトルネックに集中してください。

于 2009-03-16T15:12:20.597 に答える
3

コプロセッサがあることを思い出してください...1993年だったら速度が向上していたでしょう...しかし、今日ではネイティブの組み込み関数を打ち負かすのに苦労するでしょう.

分解してsinfを表示してみてください。

于 2009-03-16T16:04:57.617 に答える
2

誰かが既にこれをベンチマークしており、関数は既に最適化されているように見え、Trig.Math思いつくどのルックアップ テーブルよりも高速です。

http://www.tommti-systems.de/go.html?http://www.tommti-systems.de/main-Dateien/reviews/languages/benchmarks.html

(ページでアンカーを使用していないため、約 1/3 下にスクロールする必要があります)

于 2009-03-16T15:15:08.750 に答える
0

私はこの場所が心配です:

// Normalize angle so that it is in the interval (0.0, PI)
while (angle >= TWO_PI) {
    angle -= TWO_PI;
}

ただし、次のことができます。すべての関数にタイムメーターを追加し、特別なパフォーマンステストを作成し、パフォーマンステストを実行し、タイムテストのレポートを印刷します。このテストの後で答えがわかると思います。

また、AQTimeなどのプロファイリングツールを使用することもできます。

于 2009-03-16T15:11:34.763 に答える
0

組み込み関数はすでに非常に最適化されているため、それらを打ち負かすのは非常に困難です。個人的には、パフォーマンスが向上する場所を他の場所で探します。

とはいえ、コードで確認できる最適化の1つは次のとおりです。

// Normalize angle so that it is in the interval (0.0, PI)
while (angle >= TWO_PI) {
    angle -= TWO_PI;
}

次のものに置き換えることができます:

angle = fmod(angle, TWO_PI);
于 2009-03-16T15:18:55.667 に答える