手动使用多种类型比多态更快?

时间:2013-03-21 14:35:56

标签: c++

我需要制作一个要渲染的对象列表......现在我开始怀疑,无视风格或个人偏好,只考虑性能,有什么选择更快?

多种类型/开关声明:

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:这部分代码实际上是处理要转储到GPU中的顶点和UV列表,GPU比CPU快,我想尽可能快地抛出顶点和UV。渲染不受CPU限制。它也将具有多边形对象,而不仅仅是简单的类似操作系统的GUI。

5 个答案:

答案 0 :(得分:3)

我运行以下代码来比较两者。正如我所料,虚拟功能稍快一些(我将在下面解释原因)。

#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;
}

答案 1 :(得分:2)

我认为性能差异足够小,可以忽略不计。但是可维护性,可测试性和设计会受到很大影响,因此我不会使用第一个版本。

我的经验表明,每当我尝试使用如此小的优化来超越编译器时,我最终会让主机实际上更小。

答案 2 :(得分:2)

您的第一个代码是维护和设计的噩梦。在需要添加新类型时考虑一下情况!
至于表现。只有分析才能为您的平台和环境提供明确的答案。

答案 3 :(得分:0)

带开关的版本应该更快,因为多态性要求将对象移动到缓存,但这实际上是一种微优化。

我会选择多态,因为代码可以更好地设计。

答案 4 :(得分:0)

你的意思更快?

使用多态更快只是因为

  • 它更好地封装了行为
  • 可维护性肯定更好(所以对你来说更快,这很重要)
  • 它允许以简单的方式进行更复杂的设计

从性能的角度来看,多态性的实现是通过查找表完成的,所需要的是双重间接引用以获取动态调用方法的地址。 这意味着多态性的时间是常量,即使它不是微不足道的,因为该方法可能无法加载到缓存中。

切换到调用适当的方法只是忘记了OOP。这样做是没有意义的。一些CPU周期可能因为缓存命中/未命中而更快,但几乎从不这样做。