1

少し前に、固定小数点値を操作するための一連の C マクロを作成しました。SOに関するいくつかの質問と回答に励まされて、プログラムの計算集約的な部分でパフォーマンスが向上することを望んでいました。コードは正しい結果を生成しているように見えますが、ルーチンの通常の浮動小数点バージョンよりも実際に実行が遅いため、単純すぎたり単純化しすぎたりしていないかどうか疑問に思っています (Wintel でバイキュービック画像補間を行っています)。私のマクロを含むこの短いコードを見て、特にパフォーマンスに関して改善点を提案していただけませんか? ありがとう。

// these are architecture-dependent
typedef short int fixed16;
typedef int fixed32;
typedef __int64 fixed64;

// value of 2^n
#define POW2(n) (1 << n)

// create 16bit integer-based fixed point value from a floating point value, n is the number of bits reserved for the fractional part
#define FP_MAKE16(x, n) ((x) > 0.0 ? static_cast<fixed16>(floor((x) * POW2(n) + 0.5)) : static_cast<fixed16>(ceil((x) * POW2(n) - 0.5)))
// the same, 32bit
#define FP_MAKE32(x, n) ((x) > 0.0 ? static_cast<fixed32>(floor((x) * POW2(n) + 0.5)) : static_cast<fixed32>(ceil((x) * POW2(n) - 0.5)))
// and 64bit
#define FP_MAKE64(x, n) ((x) > 0.0 ? static_cast<fixed64>(floor((x) * POW2(n) + 0.5)) : static_cast<fixed64>(ceil((x) * POW2(n) - 0.5)))

// convert a fixed-point integer from one (n) format to another (m) assuming n < m
#define FP_CONVERT_UP(x, n, m) ((x) << (m-n))
// same for n > m
#define FP_CONVERT_DOWN(x, n, m) ((x) >> (n-m))

// convert floating-point value back to float
#define FP_FLOAT(x, n) (static_cast<float>(x) / POW2(n))
// same for double 
#define FP_DOUBLE(x, n) (static_cast<double>(x) / POW2(n))
// and for int. fractional part will be discarded! 
#define FP_INT(x, n) ((x) >> n)

// arithmetic operations for same-format numbers 
#define FP_NEG(a) ((~a)+1)
#define FP_ADD(a, b) ((a) + (b))
#define FP_SUB(a, b) ((a) + FP_NEG(b))
#define FP_MUL(a, b, n) (((a) * (b)) >> n)
#define FP_DIV(a, b, n) (((a) << n) / (b))
#define FP_POW2(a, n) (((a) * (a)) >> n)
#define FP_POW3(a, n) (((((a) * (a)) >> n)*(a)) >> n)

// arithmetic for different-format numbers, assuming n is the target (result) format and n > m
#define FP_ADD_UP(a, b, n, m) ((a) + ((b) << (n-m)))
#define FP_SUB_UP(a, b, n, m) ((a) + FP_NEG((b) << (n-m)))
#define FP_MUL_UP(a, b, n, m) (((a) * (b)) >> m)
#define FP_DIV_UP(a, b, n, m) (((a) << m) / (b))

// same for n < m
#define FP_ADD_DOWN(a, b, n, m) ((a) + ((b) >> (m-n)))
#define FP_SUB_DOWN(a, b, n, m) ((a) + FP_NEG((b) >> (m-n)))
#define FP_MUL_DOWN(a, b, n, m) (((a) * (b)) >> m)
#define FP_DIV_DOWN(a, b, n, m) (((a) << m) / (b))

編集:基本的に、回答とコメントは次の 2 つの点に向けられました。

  • コードは恐ろしく、使用と保守が困難です。私は心から同意し、時間をかけてクラス内にカプセル化します。対処されたパフォーマンスについていくつかの懸念がありましたが、それらは根拠のないものであると確信しており、すべてのトラブルを無料で行ってきました. :)
  • とにかく、固定小数点は問題に値しません。私のプラットフォームではそうかもしれませんが、浮動小数点ハードウェアがないプラットフォームでコードの実行速度を向上させるためにこれを作成していました。そこでは、浮動小数点演算に時間がかかりすぎたため、固定するしか方法がありませんでした。現時点ではターゲット ハードウェアにアクセスできないため、Intel でのみこれをテストしていました。

これまでに提供された洞察に非常に感謝していますが、固定小数点で実際に計算を行った人から、これらの算術演算が実際に進むべき道であるかどうかを教えてもらいたいと思っていました. おそらく、私が気付いていない気の利いたビット操作が追加されている可能性があります。これは、パフォーマンスや精度に関して違いをもたらしますか? つまり、このコードをカプセル化する場合、インライン演算子関数の同じ算術命令を基本的に現在と同じに保つことができますか、それとも何らかの方法で変更する必要がありますか?

4

4 に答える 4

5

Inline functions. Use them. Not macros. Use a class, overload it's operators. And static_cast does not exist in C. This code is so abysmally terrible, if you posted a code sample using it, it would be totally unreadable.

Remember that floating point operations are implemented in hardware, and the fixed-point operations you've implemented are in software. There's going to be a penalty for that change and it could easily be that your code simply isn't fast enough at the algorithmic level to overcome that change.

于 2011-03-17T15:59:47.857 に答える
4

@neuviemeporte さんのパフォーマンスに関するコメントへの返信です。コードをより簡単にフォーマットできるように、これをコメントではなく回答にしています。

あなたは、「知る限り、それらは実際には呼び出しオーバーヘッドを持つ関数として実装されています」、「構造体メンバーも何らかのポインターによって参照される必要があると推測しています。「this」から逃れることはできません」と言いました。これらの懸念はどちらも表面上は有効ですが、さらに調査してみましょう。

Linux/x86 で gcc を使用しています。このプログラムを考えてみましょう:

typedef int fixed32;
#define FP_ADD(a,b) ((a)+(b))
#define FP_NEG(a) ((~a)+1)
#define FP_SUB(a,b) ((a)+FP_NEG(b))
#define FP_INT(x,n) ((x)>>n)
#define FP_MUL(a,b,n) (((a)*(b))>>n)
#define FP_DIV(a,b,n) (((a)<<n)/(b))

template<class T, unsigned n>
struct Fixed {
private:
    T theBits;

public:
    Fixed(T t = T()) : theBits(t) {}

    Fixed operator+(const Fixed& rhs) const {
        return Fixed(theBits + rhs.theBits);
    }

    Fixed operator-(const Fixed& rhs) const {
        return Fixed(theBits - rhs.theBits);
    }

    Fixed operator~() const {
        return Fixed(~theBits);
    }

    Fixed operator*(const Fixed& rhs) const {
        return Fixed((theBits*rhs.theBits)>>n);
    }

    Fixed operator/(const Fixed& rhs) const {
        return Fixed((theBits<<n)/rhs.theBits);
    }

    operator T() const {
        return theBits >> n;
    }
};

int DoFpAdd(const fixed32 a, const fixed32 b) {
     fixed32 result = FP_ADD(a, b);
     return FP_INT(result, 16);
}

int DoFixedAdd(const Fixed<int, 16> a, const Fixed<int, 16> b) {
    return a+b;
}


int DoFpSub(const fixed32 a, const fixed32 b) {
     fixed32 result = FP_SUB(a, b);
     return FP_INT(result, 16);
}

int DoFixedSub(const Fixed<int, 16> a, const Fixed<int, 16> b) {
    return a-b;
}

int DoFpMul(const fixed32 a, const fixed32 b) {
     fixed32 result = FP_MUL(a, b, 16);
     return FP_INT(result, 16);
}

int DoFixedMul(const Fixed<int, 16> a, const Fixed<int, 16> b) {
    return a*b;
}

int DoFpDiv(const fixed32 a, const fixed32 b) {
     fixed32 result = FP_DIV(a, b, 16);
     return FP_INT(result, 16);
}
int DoFixedDiv(const Fixed<int, 16> a, const Fixed<int, 16> b) {
    return a/b;
}

このコマンドラインでコンパイルしましたg++ -O4 -Wall -pedantic -ansi -S x.cc && c++filt <x.s > x.S

似たような名前の関数が同じアセンブリ言語を生成したことを知って驚かれるかもしれません。はい、FP_ADD() と Fixed<>::operator+ は同じです。関数呼び出しはありません (すべてインライン化されています)thisポインターはなく、命令ごとに同一のアセンブリ言語です。

実行速度に違いはありません。使いやすさ、保守性、読みやすさに大きな違いがあります。使用しているプラ​​ットフォームに関係なく、同様の実験を行い、クラス インターフェイスに切り替えることをお勧めします。

于 2011-03-17T17:02:48.630 に答える
2

このペーパーは、C++委員会の「C++パフォーマンスに関するテクニカルレポート」から読む必要があります。

http://www.open-std.org/jtc1/sc22/wg21/docs/TR18015.pdf

それは事実上いくつかの神話を殺します。

于 2011-03-17T16:50:03.923 に答える
2

実装の単体テストを作成することで確認できます。連続するアサーションで簡単に実現できます。

assert(FP_ADD(7, 5) == 12)
assert(FP_SUB(7, 5) == 2)

コードに自信が持てるまで、十分なユースケースをカバーしてください。また、小さなイプシロン内でdoubles とsを比較することを忘れないでください。floatビット表現の制限により、等式は期待どおりに機能しない場合があります。

于 2011-03-17T16:03:15.617 に答える