-1

レンダリングするオブジェクトのリストを作成する必要があります...そして今、スタイルや個人的な好みを無視し、パフォーマンスのみを考慮して、どの代替案がより高速であるか疑問に思い始めました。

複数のタイプ/switch ステートメント:

void Object::Render()
{
    switch(type)
    {
        case BUTTON:
            RenderButton(this);
            break;
        case NOT_BUTTON:
            RenderNotButton(this);
            break;
        case WIDGET_ABC:
            RenderWidgetAbc(this);
            break;
    }
}

またはポリモーフィズム:

virtual void Object::Render();

class Button : public Object
{
     void Render();
}

編集1:役立つ場合は、これがARM v6デバイスで実行されることに言及しています

編集 2: 現在、プロジェクトにはオブジェクトがありません (プロジェクトの目的の一部は、より機能的なプログラミング方法で C++ を使用した実験です)、これが検討されている理由です。

また、これはコードの中で最も頻繁に呼び出される部分になります。これが switch ステートメントとして実装されている場合、他のメソッドはこのメソッドとして呼び出されないため、パフォーマンスが非常に重要になります。

編集 3: コードのこの部分は、実際には頂点と UV のリストを処理して GPU にダンプします。GPU は CPU よりも高速であり、頂点と UV をできるだけ高速にスローしたいと考えています。レンダリングは CPU バウンドではありません。また、単純な OS ライクな GUI だけでなく、ポリゴン オブジェクトも含まれます。

4

5 に答える 5

3

次のコードを実行して、2 つを比較しました。予想通り、仮想関数はわずかに高速でした (理由は後で説明します)。

#include <stdio.h>

#include <sys/time.h>
#include <stdio.h>
#include <unistd.h>

class Object
{
    public:
        enum Type
        {
            BUTTON,
            NOT_BUTTON,
            WIDGET_ABC
        };

        Object(Type type);

        virtual void renderVirtual() = 0;
        void renderSwitch();

        int counter;

    private:
        void renderButton();
        void renderNotButton();
        void renderWidgetAbc();

        Type type;
};

class Button : public Object
{
    public:
        Button();

        virtual void renderVirtual();
};

class NotButton : public Object
{
    public:
        NotButton();

        virtual void renderVirtual();
};

class WidgetAbc : public Object
{
    public:
        WidgetAbc();

        virtual void renderVirtual();
};

Object::Object(Type type)
    :type(type),
     counter(0)
{

}

void Object::renderSwitch()
{
    switch(type)
    {
        case BUTTON:
            renderButton();
            break;
        case NOT_BUTTON:
            renderNotButton();
            break;
        case WIDGET_ABC:
            renderWidgetAbc();
            break;
    }
}

void Object::renderButton()
{
    counter += 1;
}

void Object::renderNotButton()
{
    counter += 2;
}

void Object::renderWidgetAbc()
{
    counter += 3;
}

Button::Button()
    :Object(BUTTON)
{

}

void Button::renderVirtual()
{
    counter += 1;
}

NotButton::NotButton()
    :Object(NOT_BUTTON)
{

}

void NotButton::renderVirtual()
{
    counter += 2;
}

WidgetAbc::WidgetAbc()
    :Object(WIDGET_ABC)
{

}

void WidgetAbc::renderVirtual()
{
    counter += 3;
}

static struct timeval start, end;
static long mtime, seconds, useconds;

static void startTime()
{
    gettimeofday(&start, NULL);
}

static void printTimeDiff()
{
    gettimeofday(&end, NULL);
    seconds  = end.tv_sec  - start.tv_sec;
    useconds = end.tv_usec - start.tv_usec;
    mtime = ((seconds) * 1000 + useconds/1000.0) + 0.5;
    printf("Elapsed time: %ld milliseconds\n", mtime);
}

int main()
{
    const int size = 10000000;
    Object *button = new Button();
    Object *notButton = new NotButton();
    Object *widgetAbc = new WidgetAbc();

    startTime();

    for(int i = 0; i < size; i++)
    {
        button->renderVirtual();
        notButton->renderVirtual();
        widgetAbc->renderVirtual();
    }

    printf("Virtual Function:\n");
    printTimeDiff();
    printf("button counter = %d\n", button->counter);
    printf("notButton counter = %d\n", notButton->counter);
    printf("widgetAbc counter = %d\n", widgetAbc->counter);

    startTime();

    for(int i = 0; i < size; i++)
    {
        button->renderSwitch();
        notButton->renderSwitch();
        widgetAbc->renderSwitch();
    }

    printf("Switch Function:\n");
    printTimeDiff();
    printf("button counter = %d\n", button->counter);
    printf("notButton counter = %d\n", notButton->counter);
    printf("widgetAbc counter = %d\n", widgetAbc->counter);

    return 0;
}

「g++ main.cpp」を使用してビルドしたところ、次の結果が得られました

Virtual Function
Elapsed time 132 milliseconds
button counter = 10000000
notButton counter = 20000000
widgetAbc counter = 30000000
Switch Function
Elapsed time 206 milliseconds
button counter = 20000000
notButton counter = 40000000
widgetAbc counter = 60000000

次に、(最適化のために) -02 を追加してビルドすると、次の結果が得られました。

Virtual Function
Elapsed time 58 milliseconds
button counter = 10000000
notButton counter = 20000000
widgetAbc counter = 30000000
Switch Function
Elapsed time 76 milliseconds
button counter = 20000000
notButton counter = 40000000
widgetAbc counter = 60000000

どちらの場合も、仮想関数の方が高速でした。

仮想関数は非仮想関数よりも低速ですが、オーバーヘッドは最小限です。仮想関数は、関数ポインターである可能性が最も高いです (ただし、コンパイラーは別の方法で行うことができます)。したがって、仮想関数を呼び出す場合、余分なオーバーヘッドはポインターの逆参照だけです。以下は、仮想呼び出しに対してコンパイラが実行できることの例です。コンパイラはもう少しエレガントにそれを行うことができますが、アイデアを得ることができます.

#include <stdio.h>

class Object
{
    public:
        // function pointer acting as virtual function call
        void (*funcPtr) (void *this_ptr);
};

class Button : public Object
{
    public:
        Button();
        static void virtualFunc(void *this_ptr);

        int counter;
};

Button::Button()
    :counter(0)
{
    // set object function pointer to our "virtual function"
    funcPtr = &Button::virtualFunc;
}

void Button::virtualFunc(void *this_ptr)
{
    Button *button_ptr = reinterpret_cast<Button*>(this_ptr);
    button_ptr->counter++;
}

int main()
{
    Object *button = new Button();

    // virtual call using a function pointer
    button->funcPtr(button);

    printf("button counter = %d\n", static_cast<Button*>(button)->counter);

    return 0;
}
于 2013-03-21T15:39:03.477 に答える
2

最初のコードは、メンテナンスと設計の悪夢です。新しいタイプを追加する必要があるときの状況を考えてください!
パフォーマンスも。プロファイリングだけが、それぞれのプラットフォームと環境に対して明確な答えを与えることができます。

于 2013-03-21T14:39:19.977 に答える
2

性能差は無視できるほど小さいと思います。ただし、保守性、テスト容易性、および設計が大きく損なわれるため、最初のバージョンは使用しません。

私の経験によると、このような小さな最適化を使用してコンパイラーの裏をかこうとするたびに、ホストが実際には小さくなってしまいます。

于 2013-03-21T14:38:54.210 に答える
0

ポリモーフィズムではオブジェクトをキャッシュに移動する必要があるため、スイッチ付きのバージョンの方が高速であるはずですが、これは実際にはマイクロ最適化です。

コードをより適切に設計できるため、ポリモーフィズムを使用します。

于 2013-03-21T14:40:12.950 に答える
0

速いとはどういう意味ですか?

ポリモーフィズムの使用は、

  • 動作をより適切にカプセル化しました
  • 保守性は確かに優れています(したがって、あなたにとってはより高速です。これは重要です)
  • シンプルな方法でより複雑な設計が可能になります

パフォーマンスの単なる観点から、ポリモーフィズムの実装はルックアップテーブルを介して行われ、必要なのは、動的に呼び出されたメソッドのアドレスを取得するための二重の間接参照です。これは、メソッドをキャッシュにロードできなかったために些細なことではない場合でも、ポリモーフィズムの時間が一定であることを意味します。

適切なメソッドを呼び出すためのスイッチがあるということは、OOPを忘れているだけです。それをするのは意味がありません。キャッシュのヒット/ミスのために、CPUサイクルによっては高速になる可能性がありますが、ほとんど実行する価値はありません。

于 2013-03-21T14:40:18.697 に答える