C 类方法的执行时间取决于什么

C++ What does the execution time of a class method depend on?

本文关键字:取决于 什么 执行时间 类方法      更新时间:2023-10-16

我正在处理蒙特卡洛模拟代码,每个班级约有一个CPP文件。在为每对相邻粒子执行checkOverlap函数时,这对于执行时间非常重要。我注意到,与立即返回该功能相比,我在执行时间上发表了巨大差异。在进行进一步调查时,我发现类方法的评估速度取决于从中调用的类。

目前,我有一个类似于此伪代码的类布局

class CollisionLogic{
public:
    bool testCall(){
        return false;
    }
    bool checkOverlap(void particle1, void particle2){
        //do something...
        return value;
    }
};
class InformationContainer{
public:
    bool testCall(){
        return false;
    }
    CollisionLogic CL;
};

在主代码中执行以下

InformationContainer IC;
checkCollisionOn( particle P )
{
    for( 'each neighbouring particle P_n' )
    {
        if( IC.CL.checkOverlap(P,P_n) )
            return true;
        /* Marker */
    }
}

为了测试目的,checkOverlapfalse作为第一个调用。然后,对框架的评估需要953ms。如果我将标记评论替换为IC.CL.testCall(),则时间增加到1232ms,如果替换为IC.testCall(),则将增加到1305ms。

由于两个函数的执行量完全相同,所以我认为我可以排除CPU计算时间。所以我的问题是:发生了什么原因?

谢谢!

q/a编译:
我将每个代码文件编译到带有标志的" -o3 -std = gnu 11"的对象文件中,然后用相同的标志将它们链接在一起。

ps:我发现了有关不同C 速度问题的几个问题。[1,2,3,4]但不是这样。

[1] C程序执行速度
[2]为什么在单独的循环中添加元素比在组合循环中更快?
[3] C 速度比较迭代器与索引
[4]为什么在单独的循环中添加元素比在组合循环中更快?

代码缓存局部性

此答案假设您的程序包含的代码不仅仅是您发布的代码,并且处理帧执行足够的代码以驱逐一些页面,至少是从处理器的最低级别指令缓存。

在这种情况下,我假设执行时间的差异与指令缓存位置有关。从第一级缓存中获取代码非常快,更高级别的缓存逐渐变慢,但也更大,因此驱逐缓存线的可能性较小。访问主存储器会在现代桌面处理器上需要数百个周期,因为专家的速度更快,而内存延迟仅减少了非常慢。

在您的呼叫之前,您称为CollisionLogic :: CheckoverLap,它可能更接近CollisionLogic :: TestCall,而不是InformationContainer :: TestCall。也许甚至在同一缓存线上,或者在缓存线上,处理器在投机中提到了(控制缓存的逻辑在高性能处理器上非常复杂(。因此,处理器可以快速获取InformationContainer :: TestCall的说明。

您可以检查调试器中不同方法的指令地址以检查此假设。

尤其是当CollisionLogic和InformationContainer在不同的源文件中时,他们的方法可能最终会进一步删除。

改进代码缓存局部性

我不知道在二进制的代码部分中保持功能彼此靠近的便携式和可维护方法。

内线

解决此问题的一个选项,我用于较小,非常"热"的方法和函数是使它们在标题中具有定义的内联函数,并让它们在调用函数中绑定。除非编译器决定无论如何都不会对函数进行内联(其出于各种原因,例如功能太大(。这不仅摆脱了呼叫的开销,而且还可以保证特定呼叫的高速缓存位置。

,但是您必须衡量这是否改善了实际绩效。过多的插入将增加整体代码大小并引起相反的效果,因为这意味着经常访问的代码的"工作集"可能不再适合特定的缓存级别,并导致更多的Chache错过了整体。

在二进制中影响代码位置

我还看到了一个旧的高性能项目,原始作者确保通常访问的代码在同一.CPP文件中。该程序运行非常快。不利的一面是,每当更改程序的无关部分时,代码很难阅读和维护,并且性能很容易进行回归。如果您必须以速度可维护性交易,我不建议这样一种方法。

如果您愿意了解链接脚本,则可以编写一个将特定常见访问函数放在一起的(您可能必须将它们分配给具有编译器特定属性的代码中的特定部分(。

无论您选择改进的方法如何,您都必须确定哪些功能实际上是有助于执行时间(通过像您一样对实验更改进行分析和基准测试(,查看呼叫图和呼叫计数,并做很多基准测试以检查更改的实际绩效含义。