1

ベクトル、行列、四元数などを処理するための多数の数学ルーチンを備えた C ライブラリがあります。組み込み作業や Lua 拡張としてよく使用するため、C のままにしておく必要があります。さらに、より便利なオブジェクト管理と、C API を使用した数学演算の演算子のオーバーロードを可能にする C++ クラス ラッパーがあります。ラッパーはヘッダー ファイルのみで構成され、インライン化が可能な限り使用されます。

C コードをラップすることと、実装を C++ クラスに直接移植してインライン化することとの間に、かなりのペナルティがありますか? このライブラリは、タイム クリティカルなアプリケーションで使用されます。では、インダイレクションを排除することによるブーストは、2 つのポートのメンテナンスの頭痛の種を補うのでしょうか?

C インターフェイスの例:

typedef float VECTOR3[3];

void v3_add(VECTOR3 *out, VECTOR3 lhs, VECTOR3 rhs);

C++ ラッパーの例:

class Vector3
{
private:
    VECTOR3 v_;

public:
    // copy constructors, etc...

    Vector3& operator+=(const Vector3& rhs)
    {
        v3_add(&this->v_, this->v_, const_cast<VECTOR3> (rhs.v_));
        return *this;
    }

    Vector3 operator+(const Vector3& rhs) const
    {
        Vector3 tmp(*this);
        tmp += rhs;
        return tmp;
    }

    // more methods...
};
4

6 に答える 6

4

C ライブラリ呼び出しを C++ クラス関数でラップするだけの場合 (つまり、C++ 関数は C 関数を呼び出すだけ)、コンパイラはこれらの呼び出しを最適化して、パフォーマンスが低下しないようにします。

于 2008-11-13T02:48:54.000 に答える
3

パフォーマンスに関する質問と同様に、答えを得るために測定するように指示されます (これが厳密に正しい答えです)。

ただし、経験則として、実際にインライン化できる単純なインライン メソッドの場合、パフォーマンスの低下は見られません。一般に、呼び出しを別の関数に渡すだけのインライン メソッドは、インライン化の優れた候補です。

ただし、ラッパー メソッドがインライン化されていない場合でも、ラッパー メソッドが重要なループで呼び出されていない限り、パフォーマンスの低下はなく、測定可能なものでさえないことに気付くと思います。それでも、ラップされた関数自体があまり機能しない場合にのみ、測定可能になる可能性があります。

この種のことは、最後に懸念されることについてです。まず、コードを正しく、保守しやすくし、適切なアルゴリズムを使用していることを心配してください。

于 2008-11-13T06:41:28.630 に答える
2

最適化に関連するすべての場合と同様に、答えは、最適化が価値があるかどうかを知る前に、パフォーマンス自体を測定する必要があるということです。

  • 2 つの異なる関数をベンチマークします。1 つは C スタイルの関数を直接呼び出し、もう 1 つはラッパーを介して呼び出します。どちらがより速く実行されるか、または差が測定の誤差範囲内にあるかどうかを確認します (つまり、測定できる差がないことを意味します)。
  • 前の手順で 2 つの関数によって生成されたアセンブリ コードを確認します (gcc では、-Sまたはを使用します-save-temps)。コンパイラが愚かなことをしたかどうか、またはラッパーにパフォーマンスのバグがないかどうかを確認してください。

パフォーマンスの違いが大きすぎてラッパーを使用しない場合を除き、再実装はお勧めできません。バグが発生するリスクがあるためです (正常に見えても間違った結果が生じる可能性さえあります)。違いが大きい場合でも、C++ は C と非常に互換性があることを覚えておいて、C++ コード内でも C スタイルでライブラリを使用する方が簡単でリスクが少ないでしょう。

于 2008-11-13T03:02:47.887 に答える
2

ラッパー自体はインライン化されますが、C ライブラリへのメソッド呼び出しは通常インライン化されません。(これには、技術的に可能であるリンク時の最適化が必要ですが、今日のツールではせいぜい初歩的なものです)

一般に、関数呼び出し自体はそれほど高価ではありません。サイクル コストはここ数年で大幅に減少し、簡単に予測できるため、コール ペナルティ自体は無視できます。

ただし、インライン化により、より多くの最適化への扉が開かれます。v = a + b + c の場合、ラッパー クラスはスタック変数の生成を強制しますが、インライン呼び出しの場合、データの大部分を FPU スタックに保持できます。また、インライン コードを使用すると、命令を単純化したり、定数値を考慮したりできます。

したがって、投資する前の測定ルールは当てはまりますが、ここには改善の余地があると思います。


典型的な解決策は、C 実装をインライン関数または "C" 本体として使用できる形式にすることです。

// V3impl.inl
void V3DECL v3_add(VECTOR3 *out, VECTOR3 lhs, VECTOR3 rhs)
{
    // here you maintain the actual implementations
    // ...
}

// C header
#define V3DECL 
void V3DECL v3_add(VECTOR3 *out, VECTOR3 lhs, VECTOR3 rhs);

// C body
#include "V3impl.inl"


// CPP Header
#define V3DECL inline
namespace v3core {
  #include "V3impl.inl"
} // namespace

class Vector3D { ... }

これは、比較的単純な本体を持つ選択されたメソッドに対してのみ意味をなす可能性があります。通常はメソッドを直接必要としないため、メソッドを C++ 実装用の別の名前空間に移動します。

(インラインは単なるコンパイラのヒントであり、メソッドを強制的にインライン化するわけではないことに注意してください。ただし、これは良いことです。内部ループのコード サイズが命令キャッシュを超えると、インライン化によってパフォーマンスが簡単に低下します)

pass/return-by-reference を解決できるかどうかは、コンパイラの強度に依存します。foo(X * out) がスタック変数を強制するのに対し、X foo() はレジスタに値を保持する多くの例を見てきました。

于 2008-11-13T09:26:02.700 に答える
1

関数呼び出しのオーバーヘッドが原因で速度が低下しているのではないかと心配している場合は、C コードのインライン展開やマクロへの変換をテストしてみませんか?

また、C コードの const の正確性を向上させてみませんか? const_cast は、特に制御するインターフェイスでは慎重に使用する必要があります。

于 2008-11-13T05:43:08.607 に答える
1

パフォーマンスの違いはあまり感じられないと思います。ターゲット プラットフォームがすべてのデータ型をサポートしていると仮定すると、

私はDSと他のいくつかのARMデバイスのコーディングを行っており、浮動小数点は悪です...固定小数点に浮動小数点をtypedefする必要がありました<16,8>

于 2008-11-13T02:47:54.810 に答える