6

(はい、通常は 1 つの機械語命令は問題にならないことを知っています。私がこの質問をするのは、pimpl のイディオムを理解し、可能な限り最良の方法で使用したいからです。また、1 つの機械語命令を気にすることもあるためです。 )

以下のサンプル コードには、 と の 2 つのクラスがThingあり OtherThingます。ユーザーは「thing.hh」を含めます。 Thingpimpl イディオムを使用して実装を隠します。 OtherThingC スタイル (ポインターを返したり受け取ったりする非メンバー関数) を使用します。このスタイルは、わずかに優れたマシン コードを生成します。私は疑問に思っています: C++ スタイルを使用する方法 (つまり、関数をメンバー関数にする方法) はありますか? クラス外の名前空間を汚染しないので、このスタイルが気に入っています。

注: メンバー関数の呼び出しのみを検討しています (この場合はcalc)。オブジェクトの割り当ては見ていません。

以下は、私の Mac 上のファイル、コマンド、およびマシン コードです。

こと.hh:

class ThingImpl;
class Thing
{
    ThingImpl *impl;
public:
    Thing();
    int calc();
};

class OtherThing;    
OtherThing *make_other();
int calc(OtherThing *);

事.cc:

#include "thing.hh"

struct ThingImpl
{
    int x;
};

Thing::Thing()
{
    impl = new ThingImpl;
    impl->x = 5;
}

int Thing::calc()
{
    return impl->x + 1;
}

struct OtherThing
{
    int x;
};

OtherThing *make_other()
{
    OtherThing *t = new OtherThing;
    t->x = 5;
}

int calc(OtherThing *t)
{
    return t->x + 1;
}

main.cc (コードが実際に動作することをテストするためだけに...)

#include "thing.hh"
#include <cstdio>

int main()
{
    Thing *t = new Thing;
    printf("calc: %d\n", t->calc());

    OtherThing *t2 = make_other();
    printf("calc: %d\n", calc(t2));
}

メイクファイル:

all: main

thing.o : thing.cc thing.hh
    g++ -fomit-frame-pointer -O2 -c thing.cc

main.o : main.cc thing.hh
    g++ -fomit-frame-pointer -O2 -c main.cc

main: main.o thing.o
    g++ -O2 -o $@ $^

clean: 
    rm *.o
    rm main

makeマシンコードを実行して見てください。私が使用するMacでotool -tv thing.o | c++filt。Linux ではobjdump -d thing.o. 関連する出力は次のとおりです。

Thing :: calc(): 0000000000000000
MOVQ(%RDI)、%
RAX 00000000000003 MOVL(%RAX )、%EAX 0000000000000005 INPLECT EAX 0000000000000007 RET CALC(その他): 00000000000010 MOVL(%RDI) 0000000000000014 ret





ポインタの間接化による余分な命令に注意してください。最初の関数は 2 つのフィールド (impl、次に x) を検索しますが、2 番目の関数は x を取得するだけで済みます。何ができるでしょうか?

4

5 に答える 5

7

1 つの指示に多くの時間を費やすことはめったにありません。まず、コンパイラは、より複雑なユースケースで pImpl をキャッシュすることができるため、実際のシナリオでコストを償却できます。第 2 に、パイプライン化されたアーキテクチャでは、クロック サイクルでの実際のコストを予測することがほとんど不可能になります。これらの操作をループで実行し、差を測定すると、コストについてより現実的な考えが得られます。

于 2010-05-21T08:31:06.823 に答える
5

難しいことではありません。クラス内で同じテクニックを使用してください。中程度の適切なオプティマイザーは、単純なラッパーをインライン化します。

class ThingImpl;
class Thing
{
    ThingImpl *impl;
    static int calc(ThingImpl*);
public:
    Thing();
    int calc() { calc(impl); }
};
于 2010-05-21T08:30:14.390 に答える
2

私はあなたの使用法について同意しません.2つの同じものを比較していません.

#include "thing.hh"
#include <cstdio>

int main()
{
    Thing *t = new Thing;                // 1
    printf("calc: %d\n", t->calc());

    OtherThing *t2 = make_other();       // 2
    printf("calc: %d\n", calc(t2));
}
  1. 実際、ここでは new への呼び出しが 2 つあります。1 つは明示的で、もう 1 つは暗黙的です ( Thing.
  2. ここに 1 つの新しいものがあります。暗黙的です (2 の内部)。

Thingおそらく二重逆参照命令を変更することはありませんが、スタックに割り当てる必要があります...しかし、そのコストを変更する可能性があります(キャッシュミスを削除します)。

ただし、重要な点はThing、メモリを独自に管理することです。したがって、実際のメモリを削除することを忘れることはできませんが、C スタイルの方法では確実に削除できます。

自動メモリ処理は追加のメモリ命令の価値があると私は主張します。特に、言われているように、逆参照された値は、複数回アクセスするとおそらくキャッシュされるため、ほとんど何もないからです。

正確さはパフォーマンスよりも重要です。

于 2010-05-21T11:30:15.917 に答える
2

ポインターを十分な大きさの符号なし文字の配列に置き換えてからThingImpl、配置/新規キャストを再解釈し、ThingImplオブジェクトを明示的に破棄するという厄介な方法があります。

Thingまたは、 へのポインターよりも大きくてはならないため、単に値でアラウンドを渡すこともできますがThingImpl、それよりも少し多く必要になる場合があります ( の参照カウントはThingImpl最適化を無効にするため、「所有している」にフラグを付ける何らかの方法が必要です)。 ' Thing、一部のアーキテクチャでは余分なスペースが必要になる場合があります)。

于 2010-05-21T09:10:06.757 に答える
0

コンパイラにそれを心配させてください。それは、私たちよりも実際に何が速いか遅いかについてはるかに多くを知っています。特にそのような微細なスケールで。

クラスにアイテムを含めることには、カプセル化だけでなく、はるかに多くの利点があります。プライベートキーワードの使用方法を忘れた場合は、PIMPLをお勧めします。

于 2010-05-21T12:09:49.500 に答える