swift - 将函数包装在闭包中以将其作为参数传递是否有任何开销
问题描述
在 swift 中,您可以将函数作为参数传递给接受闭包的函数。这对于避免在使用运算符时在语法上污染代码特别有用。例如,你可以写一个总和如下:
let values = 0 ..< 10
let sum = values.reduce(0, +)
不幸的是,当 Swift 的推理无法从其他参数中确定预期闭包的类型时,重载函数可能会导致模棱两可的情况。例如,考虑下面的代码。最后一行无法编译,因为 Swift 无法确定+
我所指的“版本”。
func castAndCombine<T, U>(_ pair: (Any, Any), with fn: (T, T) -> U) -> U? {
guard let first = pair.0 as? T, let second = pair.1 as? T
else { return nil }
return fn(first, second)
}
// The following line cannot compile.
let x = castAndCombine((1, 2), with: +)
不幸的是,没有(或至少我不知道)任何方式来指定+
我的意思。尽管如此,我还是想出了两个解决这个问题的方法:
- 向函数添加参数以消除歧义:
func castAndCombine<T, U>(_ pair: (Any, Any), toType: T.Type, with fn: (T, T) -> U) -> U? {
// ...
}
let x = castAndCombine((1, 2), toType: Int.self, with: +)
- 保持函数的签名不变,并使用带有显式类型注释的闭包:
func castAndCombine<T, U>(_ pair: (Any, Any), with fn: (T, T) -> U) -> U? {
// ...
}
let x = castAndCombine((1, 2), with: { (a: Int, b: Int) in a + b })
我个人不喜欢第一个解决方案,因为我觉得它不美观且使用起来不自然。但是,我想知道第二个是否会增加任何性能开销,因为创建了一个基本上包装单个函数的闭包,而不添加任何行为。
有谁知道这种性能开销是否确实存在和/或在某种程度上很重要?
解决方案
如果您使用优化进行编译,则不应该有任何开销,因为编译器很可能会内联您的闭包。
您可以通过比较 Swift 编写的 LLVM 代码,用您的第一个解决方案(因为它支持两种样式)验证这个假设。LLVM 是编译器在创建实际机器代码之前使用的中间表示。
直接使用操作符写一个文件,即:
let x = castAndCombine((1, 2), toType: Int.self, with: +)
使用闭包编写第二个文件,即:
let x = castAndCombine((1, 2), toType: Int.self, with: { (a: Int, b: Int) in a + b })
现在用优化编译这两个,要求 Swift 的编译器生成 LLVM IR。假设您的文件名为main1.swift
and main2.swift
,您可以运行以下命令:
swift -O -emit-ir main1.swift 1>main1.ll
swift -O -emit-ir main2.swift 1>main2.ll
两个生成的文件应该是相同的。
diff main1.ll main2.ll
# No output
请注意,评论中建议的解决方案也不会增加任何性能开销,因为静态保证的强制转换不会花费任何操作。
推荐阅读
- spring-boot - 如何为同一项目的不同api创建两个不同的链接
- python - 如何在配置/构建(linux)期间微调二进制文件在哪里查找其共享库?
- go - osquery-go binding - 在远程机器上执行 osquery 而不在远程系统中运行服务器(假设我们有 ssh 密钥)
- excel - RecordCount 函数给我一个运行时错误“1004”:对象“_Global”的方法“范围”在 excel 中失败
- r - 多重geom_sf颜色美学(离散+连续)
- android - Android删除方法不起作用原因:无法访问主线程上的数据库,因为它可能会长时间锁定UI
- c# - 如何从 Json 文件中填充 collectionview?
- c# - 使用 EF 审核登录和注销
- html - 根据文本区域大小扩展背景
- javascript - 如何仅在单击按钮时启动功能