文章摘要
这篇文章探讨了C语言中闭包(closure)的性能成本问题。作者指出,当前C和C++扩展中闭包的设计方案存在诸多性能缺陷。闭包是一种包含额外数据的编程语言构造,可以视为函数概念的泛化,但作者认为现有实现方式不够优化,带来了不必要的性能开销。
文章总结
C语言中闭包的性能成本分析
背景与问题
闭包(Closures)是编程语言中一种将数据与指令绑定的结构,允许函数访问非直接输入(参数)或返回值的外部数据。尽管现代语言普遍支持闭包,但C和C++的闭包实现方案在性能和设计上存在显著不足。
闭包的核心问题
以C标准库的qsort为例,传统方法通过静态变量(static)传递额外数据存在以下问题:
- 无法创建多份独立副本,导致状态管理复杂;
- 多线程环境下易引发竞争条件;
- 无法直接引用局部数据(如数组本身)。
现有解决方案
- 修改函数签名:如BSD的
qsort_r或C11 Annex K的qsort_s,通过void*传递用户数据。 - GNU嵌套函数:直接引用外部变量,但依赖可执行栈,存在安全和性能隐患。
- Apple Blocks:通过
__block修饰符捕获变量,但依赖运行时内存管理。 - C++ Lambda:结合模板和类型擦除(如
std::function),灵活性高但可能引入额外开销。
性能测试:Knuth的Man-or-Boy测试
通过递归深度测试不同闭包方案的性能,结果如下(基于Apple Clang 17和GCC 15):
- 最优方案:未类型擦除的C++ Lambda,编译器可深度优化。
- 次优方案:轻量级类型擦除(如std::function_ref),性能接近手写C++类。
- 最差方案:GNU嵌套函数(因可执行栈限制优化)和std::function(堆分配成本高)。
关键发现
- 编译器信息越充分,性能越好:Lambda的模板实例化允许内联和优化。
- 类型擦除的成本可控:
std::function_ref等非分配型擦除器性能损失较小。 - 现有扩展的缺陷:
- GNU嵌套函数因栈执行限制拖累优化;
- Apple Blocks的ARC机制导致固定开销。
未来方向
- 宽函数指针:提议在C中引入类似
{函数指针, 上下文}的低成本闭包类型。 - 标准化需求:当前方案(如GNU嵌套函数或Apple Blocks)均未达到理想性能,需语言级支持。
结论
闭包设计需平衡灵活性与性能。C++ Lambda展示了静态优化的潜力,而C的扩展方案(如GNU嵌套函数)因实现限制表现不佳。未来C标准应考虑引入零成本抽象的闭包机制,同时兼容现有扩展。
(注:原文中的代码示例、图表及详细数据可参考原始链接)
评论总结
以下是评论内容的总结:
对C语言扩展功能的看法
- 有开发者喜欢使用GNU扩展中的蹦床函数(评论1:"I actually enjoy trampoline functions in C a bit")
- 有建议为C语言添加"state"关键字来管理函数状态(评论2:"having a 'state' keyword for declaring variables in a 'stateful' function")
关于lambda实现和性能的讨论
- 认为测试结果受内联设置影响,lambda选项在无内存分配时性能相当(评论3:"all lambda options except for the one involving allocation are equivalent modulo inlining")
- 指出C++ lambda的优势在于编译器可以优化静态链接(评论8:"the modern C++ 'Lambda' approach...is effectively a compile-time calculated static link")
对C语言改进的建议
- 呼吁C标准应包含完整的函数指针和闭包支持(评论4:"include a straightforward, first class wide function pointer along with a closure story")
- 认为类似C++引用捕获的局部函数扩展最适合C语言(评论7:"local functions...that behave like C++ byref(&) capturing lambdas makes the most sense for C")
代码风格讨论
- 对参数解析代码风格提出简化建议(评论5:"Why not if (argc > 1 && strcmp(argv[1], "-r") == 0)")
语言选择考量
- 有开发者因lambda和RAII特性选择C++而非Rust(评论6:"using C++...specifically for the lambdas and RAII")
对文章内容的指正
- 指出作者对static关键字的理解有误(评论10:"in this usage...'static' has nothing to do with persistence")
其他
- 简单表达对C++的支持(评论9:"c++ for the win!!")
- 发现文章中的拼写错误(评论11:"the only typo is...the word son")
关键引用保留: - 评论3:"all lambda options except for the one involving allocation are equivalent modulo inlining"("除了涉及内存分配的选项外,所有lambda实现在内联方面都是等效的") - 评论8:"the modern C++ 'Lambda' approach...is effectively a compile-time calculated static link"("现代C++的Lambda方法...本质上是编译时计算的静态链接") - 评论10:"in this usage...'static' has nothing to do with persistence"("在这种用法中...'static'与持久性无关")