我看到了这个链接,但我并不是要求使用“extern”的代码降低性能。我的意思是没有“extern”,在 C++ 中使用 C 库时是否存在“上下文切换”? 在 C++ 应用程序中使用纯 C(非类包装)函数时是否有任何问题?
3 回答
C 和 C++ 都是编程语言规范(用英语编写,参见例如n1570以了解 C11 的规范)并且不谈论性能(而是关于程序的行为,即关于语义)。
但是,您可能会使用不会带来任何性能损失的编译器,例如GCC或Clang,因为它为 C 和 C++ 语言构建了相同类型的中间内部表示(例如 GCC 的 GIMPLE 和 Clang 的 LLVM) ,并且因为 C 和 C++ 代码使用兼容的ABI和调用约定。
在实践extern "C"
中,不要更改任何调用约定,但会禁用name mangling。但是,它对编译器的确切影响是特定于该编译器的。它可能(或不)禁用内联(但考虑-flto
在 GCC 中进行链接时间优化)。
一些 C 编译器(例如tinycc)生成的代码性能很差。即使是GCC或Clang,在使用-O0
或不使用显式启用优化时(例如通过传递 -O1
或-O2
等...)可能会产生缓慢的代码(并且默认情况下禁用优化)。
顺便说一句,C++ 被设计为与 C 互操作(这种强约束解释了 C++ 的大部分缺陷)。
在某些情况下,正版 C++ 代码可能比相应的正版 C 代码稍快。例如,要对数字数组进行排序,您将在真正的 C++ 中使用std::array和std::sort,并且排序中的比较操作可能会被内联。使用 C 代码,您只需使用qsort并且每次比较都通过间接函数调用(因为编译器不是 inlining qsort
,即使理论上它可以......)。
在其他一些情况下,真正的 C++ 代码可能会稍微慢一些;例如,几个(但不是全部)实现::operator new
只是简单地调用malloc
(然后检查失败)但没有内联。
在实践中,从 C++ 代码调用 C 代码或从 C 代码调用 C++ 代码没有任何损失,因为调用约定是兼容的。
C longjmp工具可能比抛出 C++ 异常更快,但它们没有相同的语义(请参阅堆栈展开)并且longjmp
不能很好地混合 C++ 代码。
如果您非常关心性能,那么(用真正的 C 和真正的 C++)编写两倍的代码和基准测试。您可能会观察到 C 和 C++ 之间的微小变化(最多几个百分点),所以我根本不会打扰(而且您的性能问题实际上是不合理的)。
上下文切换是与操作系统和多任务相关的概念,发生在抢占期间运行机器代码可执行文件的进程上。如何获得该可执行文件(从 C 编译器、C++ 编译器、Go 编译器、SBCL 编译器或作为 Perl 或字节码 Python 等其他语言的解释器)完全无关紧要(因为可能会发生上下文切换在任何机器指令中,在中断期间)。阅读一些书籍,例如操作系统:三件易事。
在基本层面上,不,从 C++ 代码调用 C 库时,您不会看到任何类型的“切换”性能损失。例如,从 C++ 调用在另一个翻译单元中定义的 C 方法应该与在另一个翻译单元中调用在 C++ 中实现的相同方法(以相同的类似 C 的方式)具有大致相同的性能。
这是因为 C 和 C++ 编译器的常见实现最终将源代码编译为本机代码,并且使用C++ 调用可能发生extern "C"
的相同类型有效地支持调用函数。call
调用约定通常基于平台 ABI,并且在任何一种情况下都相似。
撇开这个基本事实不谈,调用 C 函数而不是在 C++ 中实现相同的函数时,可能仍然存在一些性能劣势:
- 用 C 实现并从 C++ 代码声明
extern "C"
和调用的函数通常不会被内联(因为根据定义,它们不是在标头中实现的),这会抑制大量可能非常强大的优化0。 - C++ 代码1中使用的大多数数据类型不能被 C 代码直接使用,因此例如,如果您
std::string
的 C++ 代码中有一个,则需要选择不同的类型将其传递给 C 代码 -char *
很常见但会丢失有关显式长度的信息,这可能比 C++ 解决方案慢。许多类型没有直接的 C 等价物,因此您可能会遇到代价高昂的转换。 - C 代码使用
malloc
andfree
进行动态内存管理,而 C++ 代码通常使用new
anddelete
(并且通常更喜欢将这些调用尽可能隐藏在其他类后面)。如果您需要以一种语言分配内存,而该语言将在另一种语言中释放,这可能会导致不匹配,您需要回调“其他”语言来进行释放,或者可能是不必要的副本等。 - C 代码经常大量使用 C 标准库例程,而 C++ 代码通常使用 C++ 标准库中的方法。由于有很多功能重叠,C 和 C++ 的混合可能比纯 C++ 代码具有更大的代码占用空间,因为使用了更多的 C 库方法2。
上述问题仅在将纯 C++ 实现与 C 进行对比时才适用,并不意味着在调用 C 时性能会下降:它实际上是在回答“为什么可以混合使用 C 和C++ 比纯 C++ 慢吗?”。此外,上述问题主要是对非常短的通话的关注,其中上述开销可能很大。如果您在 C 中调用冗长的函数,则问题不大。“数据类型不匹配”可能仍然会咬你,但这可以在 C++ 端进行设计。
0有趣的是,链接时优化实际上允许在 C++ 代码中内联C 方法,这是 LTO 的一个鲜为人知的好处。当然,这通常取决于使用适当的 LTO 选项从源代码自己构建 C 库。
1例如,除了标准布局类型之外的几乎任何东西。
2许多 C++ 标准库调用最终委托给 C 库例程以进行“繁重”的提升,例如如何std::copy
调用memcpy
或memset
在可能的情况下以及大多数new
实现最终如何调用malloc
.
C++ 自诞生以来已经发展和改变了很多,但在设计上它与 C 向后兼容。C++ 编译器通常是从 C 编译器构建的,但通过链接时优化更加现代化。我想很多软件都可以在用户空间和使用的库中可靠地混合 C 和 C++ 代码。我最近回答了一个问题,涉及将 C++ 类成员函数指针传递给 C 实现的库函数。海报说这对他有用。因此,C++ 可能比任何程序员或用户想象的更兼容 C。
然而,C++ 在许多不同的范式中工作,而 C 没有,因为它是面向对象的,并实现了一系列抽象、新数据类型和运算符。某些数据类型很容易翻译(char *
C 字符串到 a std::string
),而另一些则不是。GNU.org 上关于 C++ 编译器选项的这一部分可能会引起一些兴趣。
混合两种语言时,我不会太担心或担心性能下降。最终用户,甚至程序员,几乎不会注意到任何可测量的性能变化,除非他们正在处理大量的数据抽象。