为什么C中的结构指针(方法)比普通函数慢很多?

为什么C中的结构指针(方法)比普通函数慢很多?,c,pointers,struct,C,Pointers,Struct,最近,我在越来越多的项目中使用C语言,几乎最终用结构指针创建了自己的“对象实现”。然而,我很好奇纯函数风格(带结构)和以更现代的面向对象风格调用函数指针的结构之间的速度差异 我已经创建了一个示例程序,不确定为什么时间上的差异如此之大 该程序使用两个计时器并记录完成每个任务(一个接一个)所需的时间。这不包括内存分配/取消分配,两种技术的设置方式相似(每个结构都有三个整数作为结构的指针) 代码本身只是在for循环中重复地将三个数字相加,持续时间在宏循环\u LEN中指定 请注意,我对函数进行了内联

最近,我在越来越多的项目中使用C语言,几乎最终用结构指针创建了自己的“对象实现”。然而,我很好奇纯函数风格(带结构)和以更现代的面向对象风格调用函数指针的结构之间的速度差异


我已经创建了一个示例程序,不确定为什么时间上的差异如此之大

该程序使用两个计时器并记录完成每个任务(一个接一个)所需的时间。这不包括内存分配/取消分配,两种技术的设置方式相似(每个结构都有三个整数作为结构的指针)

代码本身只是在for循环中重复地将三个数字相加,持续时间在宏循环\u LEN中指定

请注意,我对函数进行了内联测试,编译器优化从无到完全优化(/Ox)(我在Visual Studio中以纯.c文件的形式运行)


对象样式代码
功能(传统)样式代码
定时器测试和主程序入口点
#包括
#包括
#包括
#定义循环长度100000000
//主要入口点
内部主(空){
//第一个任务的启动计时器
时钟开始1,结束1,开始2,结束2;
双cpu使用时间1,cpu使用时间2;
//计时器之前的初始化实例
magic*object1=new_m(1,2,3);
//启动任务1时钟
start1=时钟();
for(int i=0;isum(object1);
}
//停止任务1时钟
end1=时钟();
//从内存中删除
删除(对象1);
//计算task1执行时间
cpu使用时间1=((双精度)(end1-start1))/每秒时钟数;
//计时器之前的初始化实例
str_magic*object2=new_m_str(1,2,3);
//启动任务2时钟
start2=时钟();
for(int i=0;i

每次运行程序时,函数编程总是执行得更快。我认为唯一的原因是它必须通过结构访问额外的指针层才能调用该方法,但我认为内联将减少这种延迟


尽管随着优化的增加,延迟会越来越小,但我想知道为什么在低优化/无优化的级别上会有如此大的差异,因此这被认为是一种有效的编程风格吗?

您的第二个循环和
/O2
循环被编译成:

    call     clock
    mov      edi, eax ; this is used later to calculate time
    call     clock
根本没有代码。编译器能够理解
sum\u str
函数的结果是未使用的,所以它会完全删除它。编译器无法对第一种情况执行相同的操作

因此,启用优化时没有真正的比较

如果没有优化,只需要执行更多的代码

第一个循环编译为:

    cmp      DWORD PTR i$1[rsp], 1000000000
    jge      SHORT $LN3@main                 ; loop exit
    mov      rcx, QWORD PTR object1$[rsp]
    mov      rax, QWORD PTR object1$[rsp]    ; extra instruction
    call     QWORD PTR [rax+32]              ; indirect call
    mov      DWORD PTR result1$3[rsp], eax
    jmp      SHORT $LN2@main                 ; jump to the next iteration
第二个循环:

    cmp      DWORD PTR i$2[rsp], 1000000000
    jge      SHORT $LN6@main                 ; loop exit
    mov      rcx, QWORD PTR object2$[rsp]
    call     sum_str
    mov      DWORD PTR result2$4[rsp], eax
    jmp      SHORT $LN5@main                 ; jump to the next iteration
sum
sum\u str
编译为等效的指令序列


不同之处在于循环中的一条指令,加上间接调用速度较慢。总的来说,没有优化的两个版本之间不应该有太大的差异-两个版本都应该很慢。

正如您所说,前一种情况有额外的间接指针引用。尽管您将
sum
声明为内联函数,但由于
sum
函数指针被放入对象成员中,因此它不能轻松内联


我建议您将生成的汇编代码与
-O0
~
-O3

进行比较。我认为Ivan和您已经提供了答案。我只想添加关于内联函数的内容。即使将函数声明为内联,编译器也不必总是将其视为内联。基于复杂性,编译器可以将其视为正常函数。 你所说的“都是内联的”是什么意思?MSVC无法内联您的OO代码。同时,它可以通过在第二种情况下完全不生成代码来击败您的基准测试。您可以尝试在关闭optimizer的情况下编译它,看看是否在性能上有类似的差异吗?
self=NULL是多余的,可能是一个bug。它不确定,不确定它是否相关,但这看起来很像用C++语言比较虚拟和非虚拟方法调用。您可以通过搜索“虚拟方法”和“性能”来查找相关资料。无论如何,您的代码没有副作用,因此我希望两个循环都能为这两种情况生成一个大的胖
NOP
。投票关闭as无法复制,因为这种基准测试没有意义。现在,您需要做的是将结果复制到分配的数组中,然后打印或访问这些数组中基准测试之外的一些随机点。
    call     clock
    mov      edi, eax ; this is used later to calculate time
    call     clock
    cmp      DWORD PTR i$1[rsp], 1000000000
    jge      SHORT $LN3@main                 ; loop exit
    mov      rcx, QWORD PTR object1$[rsp]
    mov      rax, QWORD PTR object1$[rsp]    ; extra instruction
    call     QWORD PTR [rax+32]              ; indirect call
    mov      DWORD PTR result1$3[rsp], eax
    jmp      SHORT $LN2@main                 ; jump to the next iteration
    cmp      DWORD PTR i$2[rsp], 1000000000
    jge      SHORT $LN6@main                 ; loop exit
    mov      rcx, QWORD PTR object2$[rsp]
    call     sum_str
    mov      DWORD PTR result2$4[rsp], eax
    jmp      SHORT $LN5@main                 ; jump to the next iteration