是的,写得好的 C++ 速度要快得多。如果您正在编写性能关键的程序并且您的 C++ 不如 C 快(或在几个百分点之内),那么就有问题了。如果您的 ObjC 实现与 C 一样快,那么通常出了点问题——即该程序可能是 ObjC OOD 的一个坏例子,因为它可能使用一些“肮脏”的技巧来低于它的抽象层在内部进行操作,例如直接 ivar 访问。
Mike Ash 的“比较”非常具有误导性——我绝不会推荐这种方法来比较您编写的程序的执行时间,或者推荐它来比较 C、C++ 和 ObjC。呈现的结果来自编译器优化禁用的测试。当您测量执行时间时,在禁用优化的情况下编译的程序很少相关。将其视为将 C++ 与 Objective-C 进行比较的基准是有缺陷的。该测试还比较了单个功能,而不是整个、现实世界的优化实现——单个功能以非常不同的方式与两种语言结合。这远非优化实现的现实性能基准。示例:启用优化后,IMP 缓存与虚拟函数调用一样慢。静态分派(与动态分派相反,例如使用virtual)和对已知 C++ 类型的调用(可以绕过动态分派)可能会被积极优化。这个过程称为去虚拟化,使用时,声明为virtual的成员函数甚至可能是inlined。在 Mike Ash 测试的情况下,对已声明为 virtual 并且具有空主体的成员函数进行了许多调用:当类型已知时,这些调用被优化掉 完全,因为编译器看到实现并且能够确定动态调度是不必要的。编译器还可以消除在优化构建中对malloc 的调用(有利于堆栈存储)。因此,在任何 C、C++ 或 Objective-C 中启用编译器优化都会在执行时间上产生巨大差异。
这并不是说呈现的结果完全没有用。如果您想确定他们在一个平台或架构上使用pthread_create 或+[NSObject alloc] 与另一个平台或架构所花费的时间之间是否存在可测量的差异,您可以获得有关外部 API 的一些有用信息。当然,这两个示例将在您的测试中使用优化的实现(除非您碰巧正在开发它们)。但是对于在您编译的程序中将一种语言与另一种语言进行比较……显示的结果在禁用优化的情况下毫无用处。
对象创建
还要考虑在 ObjC 中创建对象 - 每个对象都是动态分配的(例如在堆上)。使用 C++,对象可以在堆栈上创建(例如,在许多情况下,与创建 C 结构和调用简单函数的速度差不多)、在堆上或作为抽象数据类型的元素。每次你分配和释放(例如通过 malloc/free),你可能会引入一个锁。当您在堆栈上创建 C 结构或 C++ 对象时,不需要锁(尽管内部成员可能使用堆分配),并且通常只需要几条指令或几条指令加上一个函数调用。
同样,ObjC 对象是引用计数的实例。在性能关键的 C++ 中,对象成为std::shared_ptr 的实际需求非常罕见。在 C++ 中,使每个实例都成为共享的、引用计数的实例是不必要或不可取的。使用 C++,您可以更好地控制所有权和生命周期。
数组和集合
C 和 C++ 中的数组和许多集合也使用强类型容器和连续内存。由于下一个元素的成员的地址通常是已知的,优化器可以做更多的事情,并且你有很好的缓存和内存局部性。使用 ObjC,这对于标准对象(例如 NSObject)来说远非现实。
派送
关于方法,许多 C++ 实现很少使用虚拟/动态调用,特别是在高度优化的程序中。这些是优化器的静态方法调用和素材。
使用 ObjC 方法,每个方法调用(objc 消息发送)都是动态的,因此是优化器的防火墙。最终,这会导致许多限制或不便,即在编写性能关键的 ObjC 时,您可以做什么和不可以做什么以将性能保持在最低水平。这可能会导致更大的方法、IMP 缓存、频繁使用 C。
某些实时应用程序无法在其渲染路径中使用任何 ObjC 消息传递。无——音频渲染就是一个很好的例子。 ObjC 调度根本不是为实时目的而设计的;消息传递对象时可能会在幕后发生分配和锁定,从而使 objc 消息传递的复杂性/时间难以预测,以至于音频渲染可能会错过其截止日期。
其他功能
C++ 还为其许多库提供泛型/模板实现。这些优化得很好。它们是类型安全的,并且可以使用模板进行许多内联和优化(考虑它在编译时发生的多态性、优化和特化)。 C++ 增加了一些在严格的 ObjC 中不可用或可比的特性。试图直接比较非常不同的语言、对象和库并不是那么有用——它只是实际实现的一小部分。考虑到设计和实现的许多方面,最好将问题扩展到库/框架或实际程序。
其他要点
在构建的各个阶段(剥离、死代码消除、内联和早期内联,以及链接时间优化),可以更轻松地删除和优化 C 和 C++ 符号。这样做的好处包括减少二进制文件大小、减少启动/加载时间、减少内存消耗等。对于单个应用程序来说,这可能没什么大不了的;但是如果你重用了很多代码,而且你应该重用,那么如果实现了 ObjC,那么你的共享库可能会给程序增加很多不必要的重量——除非你准备好跳过一些燃烧的箍。因此,可扩展性和重用也是大中型项目和重用率高的群体的因素。
包含的库
ObjC 库实现者也针对环境进行了优化,因此其库实现者可以利用一些语言和环境特性来提供优化的实现。尽管在纯 ObjC 中编写优化程序时存在一些相当大的限制,但 Cocoa 中存在一些高度优化的实现。这是 Cocoa 的强项之一,尽管 C++ 标准库(有些人称之为 STL)也没有懈怠。 Cocoa 在比 C++ 高得多的抽象级别上运行——如果您不太清楚自己在做什么(或应该做什么),更接近金属的操作真的会花费您。如果你不是某个领域的专家,回到一个好的库实现是一件好事,除非你真的准备好学习。同样,Cocoa 的环境也是有限的。您可以找到更好地利用操作系统的实现/优化。
如果您正在编写优化程序并且在 C++ 和 ObjC 方面都有这样做的经验,clean C++ 实现通常会比 clean ObjC 快两倍或更快(是的,您可以与 Cocoa 进行比较)。如果您知道如何优化,您通常可以比更高级别的通用抽象做得更好。虽然,一些优化的 C++ 实现将与 Cocoa 的一样快或慢(例如,我在文件 I/O 上的初始尝试比 Cocoa 的慢——主要是因为 C++ 实现初始化了它的内存)。
很大程度上取决于您熟悉的语言功能。我使用两种语言,它们都有不同的优势和模型/模式。它们很好地互补,并且两者都有很好的库。如果您正在实现一个复杂的、性能关键的程序,正确使用 C++ 的特性和库将为您提供更多的控制权并为优化提供显着优势,因此在正确的手中,“快几倍”是一个很好的默认期望(但是,不要期望每次都赢,或者不做一些工作)。请记住,要充分理解 C++ 才能真正达到这一点需要数年时间。
我将大部分性能关键路径保留为 C++,但也认识到 ObjC 对于某些问题也是一个非常好的解决方案,并且有一些非常好的库可用。