dynamic_cast的性能表现

3
我之前提出了一个问题:为什么dynamic_cast被认为是邪恶的?回答让我写了一些关于dynamic_cast性能的代码。我编译并测试了这段代码,使用dynamic_cast会稍微比不使用dynamic_cast花费更多的时间。我没有看到dynamic_cast造成时间消耗的证据。我的代码正确吗?
代码如下:
class Animal
{
public:
    virtual ~Animal(){};
};

class Cat : public Animal
{
public:
    std::string param1;
    std::string param2;
    std::string param3;
    std::string param4;
    std::string param5;
    int param6;
    int param7;
};

bool _process(Cat* cat)
{
    cat->param1 = "abcde";
    cat->param2 = "abcde";
    cat->param3 = "abcde";
    cat->param4 = "abcde";
    cat->param5 = "abcde";
    cat->param6 = 1;
    cat->param7 = 2;
    return true;
}

bool process(Animal *ptr)
{
    Cat *cat = dynamic_cast<Cat*>(ptr);
    if (cat == NULL)
    {
        return false;
    } 
    _process(cat);
    return true;
}
int main(int argc, char* argv[])
{
    /*
    argv[1] : object num
    */

    if (argc != 2)
    {
        std::cout << "Error: invalid argc " << std::endl;
        return -1;
    }

    int obj_num = atoi(argv[1]);
    if (obj_num <= 0)
    {
        std::cout << "Error: object num" << std::endl;
    }

    int c = 0;
    for (; c < obj_num; c++)
    {
        Cat cat;
        #ifdef _USE_CAST
        if (!process(&cat))
        {
            std::cout << "Error: failed to process " << std::endl;
            return -3;
        }
        #else
        if (!_process(&cat))
        {
            std::cout << "Error: failed to process " << std::endl;
            return -3;
        }

        #endif
    }

    return 0;
}

使用以下方式编译:

g++ -D_USE_CAST -o dynamic_cast_test  dynamic_cast_benchmark.c
g++ -o dynamic_cast_no_test dynamic_cast_benchmark.c

使用数字1、10、100等执行它们:

$time ./dynamic_cast_test num
$time ./dynamic_cast_no_test num

结果如下:
                 dynamic_cast               non_dynamic_cast
num  10,000   
                real    0m0.010s            real    0m0.008s
                user    0m0.006s            user    0m0.006s
                sys     0m0.001s            sys     0m0.001s

     100,000 
                real    0m0.059s            real    0m0.056s
                user    0m0.054s            user    0m0.054s
                sys     0m0.001s            sys     0m0.001s

     1,000,000
                real    0m0.523s            real    0m0.519s
                user    0m0.517s            user    0m0.511s
                sys     0m0.001s            sys     0m0.004s

     10,000,000
                real    0m6.050s            real    0m5.126s
                user    0m5.641s            user    0m4.986s
                sys     0m0.036s            sys     0m0.019s

     100,000,000
                real    0m52.962s           real    0m51.178s
                user    0m51.697s           user    0m50.226s
                sys     0m0.173s            sys     0m0.092s

硬件和操作系统:
OS:Linux
CPU:Intel(R) Xeon(R) CPU E5607  @ 2.27GHz  (4 cores)

2
首先,您没有使用优化进行编译。请添加“-O3”编译器标志。 - David Brown
5
dynamic_cast通常被反对是因为它表明糟糕的设计,而不是因为它“慢”(这是相对的) 。 - Neil Kirk
如果您尝试使用多重虚拟继承会怎样呢? - greatwolf
你应该尝试使用更深的继承树(也许还有一些多重继承)进行基准测试。然后你会看到显著的差异。 - user1233963
2
以下是一些基准测试结果:http://tinodidriksen.com/2010/04/14/cpp-dynamic-cast-performance/ 还要注意现代编译器有时可以优化掉 dynamic_cast。 - Nicolas Louis Guillemot
@Nicolas 在我看来,这是迄今为止最好的答案。 - TobiMcNamobi
2个回答

1
你写的代码是正确的,尽管我不会硬编码类型为Cat。为了保险起见,你可以使用命令行参数来决定构建猫还是狗(你也应该实现这一点)。尝试禁用优化,以查看它是否起到了重要作用。
最后,需要谨慎提醒一下。性能分析并不像在计算机上进行测量那么简单,所以你必须意识到你所做的只能带你走到一定程度。它确实给你一个想法,但不要认为你得到了一个全面的答案。

-1

我会重新表述我的帖子。

你的代码是正确的,而且编译也很顺利。

由于虚拟方法和dynamic_cast运算符是相关问题,请查看维基百科上的信息,希望对你有用。

维基百科:

虚函数调用需要至少一个额外的索引间接寻址,并且有时还需要一个“fixup”加法,与非虚函数调用相比,后者只是跳转到编译时内置的指针。因此,调用虚函数固有地比调用非虚函数慢。1996 年的一项实验表明,大约 6-13% 的执行时间仅用于分派到正确的函数,尽管开销可能高达 50%。由于缓存更大、支持更好的分支预测,现代 CPU 架构上虚函数的成本可能不会那么高。
此外,在不使用 JIT 编译的环境中,通常无法内联虚函数调用。虽然编译器可以将查找和间接调用替换为每个内联体的条件执行,但这样的优化并不常见。
为避免这种开销,编译器通常会在编译时避免使用 vtable,只要调用可以在编译时解析就可以了。
因此,上面对 f1 的调用可能不需要 vtable 查找,因为编译器可以知道此时 d 只能容纳一个 D,而 D 不会覆盖 f1。或者编译器(或优化器)可以检测到程序中没有任何子类覆盖 f1 的 B1。对 B1::f1 或 B2::f2 的调用可能不需要 vtable 查找,因为实现已经明确指定了(虽然仍需要“this”指针修复)。
此外,正如您可能已经了解到的那样,在类中声明虚方法时,取决于实现,但几乎总是,编译器都会隐式地添加虚方法表作为新成员添加到您的类中,因此这个类的每个实例将占用更多的内存空间,请尝试在带有虚方法表和不带虚方法表的类上使用 sizeof。

这个问题与调用虚函数的成本无关。 - Stefano Falasca
@Stefano Falasca,我认为是的,因为dynamic_cast和虚方法是相关的,不是吗? - kvv
我不会说它们没有关联,因为这很难证明。然而,请注意,dynamic_cast需要启用RTTI(好吧,不是禁用),而如果禁用RTTI,您确实可以使用虚拟派发。 - Stefano Falasca
我倾向于给这个答案一个+1,只是为了让它不再有那个丑陋的减号。至少这是有用的信息,我看到虚拟函数和dynamic_cast有关联(不知道怎么说)。也许是我的错,抱歉,但我仍然看不出这个答案如何回答问题的一部分。 - TobiMcNamobi
@TobiMcNamobi,嗯,我不确定dynamic_cast运算符的工作原理,你知道吗?我会假设,dynamic_cast(当然,“RTTI关闭”)仍然通过vtable指针进行查找,所以维基百科说vtable查找的成本很高,但在现代计算机上并不那么重要。这就是为什么我认为作者进行的测试有点让他困惑了(“每个人都说dynamic_cast运算符是邪恶的,我的天啊,但我的测试显示它还好!”)。也许我错了。 - kvv
@TobiMcNamobi,也许我应该再读一遍问题。在我看来,当我考虑dynamic_cast运算符的邪恶时,首先想到的是vtable指针问题:实例大小和在查找vtable期间的时间成本。 - kvv

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接