在AI应用程序中,我用C ++编写,
在这种情况下,有没有优化技术?虽然我现在不打算优化应用程序,但为项目选择C ++而不是Java的一个方面是提供更多的优势,以便能够使用非面向对象的方法(模板,过程,重载)。
特别是,与虚拟功能相关的优化技术是什么?虚函数通过内存中的虚拟表实现。有没有办法将这些虚拟表预取到L2缓存上(从内存/ L2缓存中获取的成本正在增加)?
除此之外,C ++中的数据局部技术是否有很好的参考?这些技术将减少数据提取到计算所需的L2高速缓存的等待时间。
更新:另请参阅以下相关论坛:Performance Penalty for Interface,Several Levels of Base Classes
答案 0 :(得分:28)
虚拟功能非常高效。假设32位指针,内存布局大约是:
classptr -> [vtable:4][classdata:x]
vtable -> [first:4][second:4][third:4][fourth:4][...]
first -> [code:x]
second -> [code:x]
...
classptr指向通常在堆上的内存,偶尔在堆栈上,并以指向该类的vtable的四字节指针开始。但要记住的重要一点是vtable本身没有分配内存。它是一个静态资源,同一类类型的所有对象都将指向其vtable数组的完全相同的内存位置。调用不同的实例不会将不同的内存位置拉入L2缓存。
这个example from msdn显示了具有虚拟func1,func2和func3的A类的vtable。不超过12个字节。很有可能不同类的vtable也会在编译库中物理上相邻(你需要验证这是你特别关注的),这可以在显微镜下提高缓存效率。
CONST SEGMENT
??_7A@@6B@
DD FLAT:?func1@A@@UAEXXZ
DD FLAT:?func2@A@@UAEXXZ
DD FLAT:?func3@A@@UAEXXZ
CONST ENDS
另一个性能问题是调用vtable函数的指令开销。这也非常有效。几乎与调用非虚函数相同。再次来自example from msdn:
; A* pa;
; pa->func3();
mov eax, DWORD PTR _pa$[ebp]
mov edx, DWORD PTR [eax]
mov ecx, DWORD PTR _pa$[ebp]
call DWORD PTR [edx+8]
在此示例ebp中,堆栈帧基指针具有零偏移量的变量A* pa
。寄存器eax加载了位置[ebp]的值,因此它具有A *,edx加载了位置[eax]的值,因此它具有A类vtable。然后ecx加载[ebp],因为ecx表示“this”它现在保持A *,最后调用位置[edx + 8]的值,这是vtable中的第三个函数地址。
如果此函数调用不是虚拟的,则不需要mov eax和mov edx,但性能上的差异将是无法估量的。
答案 1 :(得分:11)
draft Technical Report on C++ Performance的第5.3.3节完全专注于虚函数的开销。
答案 2 :(得分:3)
您是否实际剖析并找到了哪里以及需要优化的内容?
当您发现它们实际上是瓶颈时,实际优化虚拟函数调用。
答案 3 :(得分:3)
我能想到的唯一优化是Java的JIT编译器。如果我理解正确,它会在代码运行时监视调用,如果大多数调用仅转到特定实现,它会在类正确时将条件跳转插入实现。这种方式,大多数情况下,没有vtable查找。当然,对于我们通过不同类的罕见情况,仍然使用vtable。
我不知道任何使用这种技术的C ++编译器/运行时。
答案 4 :(得分:3)
虚函数往往是查找和间接函数调用。在某些平台上,这很快。在其他方面,例如,在游戏机中使用的一种流行的PPC架构,这不是那么快。
优化通常围绕在callstack中表达更高的可变性,因此您不需要在热点内多次调用虚函数。
答案 5 :(得分:2)
您可以使用虚函数在运行时实现多态性,并在编译时使用模板实现多态性。您可以使用模板替换虚拟功能。有关详细信息,请参阅此文章 - http://www.codeproject.com/KB/cpp/SimulationofVirtualFunc.aspx
答案 6 :(得分:2)
动态多态的解决方案可以是静态多态性,如果您的类型在编译类型中是已知的,则可以使用:CRTP(奇怪的重复模板模式)。
http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
对维基百科的解释很清楚,如果您确实虚拟方法调用是性能瓶颈的来源,也许可以帮助您。
答案 7 :(得分:2)
虚拟调用与正常函数相比不会产生更大的开销。虽然,最大的损失是,多态调用时的虚函数无法内联。在许多情况下,内联将代表性能的一些实际收益。
在某些情况下,您可以采取措施防止浪费该设施,即将内联虚拟函数声明为。
Class A {
inline virtual int foo() {...}
};
当您处于代码点时,您确定要调用的对象的类型,您可以进行内联调用,以避免多态系统并启用编译器的内联。
class B : public A {
inline virtual int foo()
{
//...do something different
}
void bar()
{
//logic...
B::foo();
// more logic
}
};
在此示例中,对foo()
的调用将变为非多态并绑定到B
foo()
的实现。但只有当您确切知道实例类型是什么时才这样做,因为自动多态性功能将会消失,这对于后来的代码阅读器来说并不是很明显。
答案 8 :(得分:2)
我正在强化所有有效的答案:
您想知道的是:
某些分析器可以间接地向您提供此信息。他们需要在声明级别进行总结,但不包括在方法本身中花费的时间。
我最喜欢的技术是在调试器下暂停一下。
如果在虚函数调用过程中花费的时间很重要,比如说20%,则平均每5个样本中就有1个会在调用堆栈的底部显示,在反汇编窗口中,指示跟随虚函数指针。
如果你实际上没有看到,那不是问题。
在这个过程中,您可能会看到调用堆栈中更高的其他内容,实际上并不需要它们,可以为您节省大量时间。
答案 9 :(得分:2)
正如其他答案所述,虚函数调用的实际开销相当小。它可能会在一个紧密的循环中发挥作用,它被称为每秒数百万次,但它很少是一个大问题。
然而,它可能仍会产生更大的影响,因为编译器更难以优化。它不能内联函数调用,因为它在编译时不知道将调用哪个函数。这也使得一些全球优化变得更加困难。这需要多少性能?这取决于。通常没有什么可担心的,但有些情况下可能意味着重大的性能损失。
当然,它还取决于CPU架构。在某些情况下,它会变得相当昂贵。
但值得注意的是,任何类型的运行时多态性都会带来或多或少相同的开销。通过switch语句或类似功能实现相同的功能,以便在许多可能的功能之间进行选择可能并不便宜。
优化这一点的唯一可靠方法是,如果您可以将一些工作转移到编译时。如果可以将其中的一部分实现为静态多态,则可能会有一些加速。
但首先,请确保您遇到问题。代码实际上是否太慢而无法接受? 其次,找出通过分析器使它变慢的原因。 第三,解决它。
答案 10 :(得分:2)
静态多态,正如一些用户在这里回答的那样。例如,WTL使用此方法。可以在http://www.codeproject.com/KB/wtl/wtl4mfc1.aspx#atltemplates
找到有关WTL实现的明确说明答案 11 :(得分:1)
你很少担心这些常用物品的缓存,因为它们被取出并保存在那里。
在处理大型数据结构时,缓存通常只是一个问题:
像Vtables这样的东西通常不会成为性能/缓存/内存问题;通常每个对象类型只有一个Vtable,并且该对象包含指向Vtable而不是Vtable本身的指针。因此,除非你有几千种类型的对象,否则我认为Vtables不会破坏你的缓存。
顺便说一下,1)就像memcpy这样的函数使用缓存绕过流指令(如movnt(dq | q))来处理超大(兆字节)的数据输入。
答案 12 :(得分:1)
现在最近的CPUS成本与正常功能大致相同,但不能内联。如果你将函数调用数百万次,那么影响可能很大(尝试调用数百万次相同的函数,例如,一次没有内联一次,如果函数本身做的很简单,你会发现它可能慢两倍;这个不是一个理论上的例子:很多数值计算很常见。)
答案 13 :(得分:1)
使用现代的,前瞻性的,多调度的CPU,虚拟功能的开销可能为零。纳达。拉链。
答案 14 :(得分:0)
如果 AI 应用程序不需要大量的数字运算,我不会担心虚拟功能的性能劣势。只有当它们出现在重复计算的复杂计算中时,才会出现边际性能损失。我认为您不能强制虚拟表保留在L2缓存中。
有几种可用于虚拟功能的优化,