【发布时间】:2014-07-28 21:32:50
【问题描述】:
我在 Swift Beta 中实现了一个算法,发现性能很差。在深入挖掘之后,我意识到瓶颈之一就是对数组进行排序这样简单的事情。相关部分在这里:
let n = 1000000
var x = [Int](repeating: 0, count: n)
for i in 0..<n {
x[i] = random()
}
// start clock here
let y = sort(x)
// stop clock here
在 C++ 中,类似的操作在我的计算机上需要 0.06s。
在 Python 中,它需要 0.6s(没有技巧,只是 y = sorted(x) 用于整数列表)。
在 Swift 中,如果我使用以下命令编译它需要 6s:
xcrun swift -O3 -sdk `xcrun --show-sdk-path --sdk macosx`
如果我使用以下命令编译它,它需要多达 88s:
xcrun swift -O0 -sdk `xcrun --show-sdk-path --sdk macosx`
Xcode 中“发布”与“调试”构建的时序相似。
这里有什么问题?与 C++ 相比,我可以理解一些性能损失,但与纯 Python 相比,速度不会下降 10 倍。
编辑: Weather 注意到将 -O3 更改为 -Ofast 使得这段代码的运行速度几乎与 C++ 版本一样快!然而,-Ofast 极大地改变了语言的语义——在我的测试中,它禁用了对整数溢出和数组索引溢出的检查。例如,使用-Ofast,以下 Swift 代码会静默运行而不会崩溃(并打印出一些垃圾):
let n = 10000000
print(n*n*n*n*n)
let x = [Int](repeating: 10, count: n)
print(x[n])
所以-Ofast 不是我们想要的; Swift 的全部意义在于我们有安全网。当然,安全网对性能有一些影响,但它们不应该使程序慢 100 倍。请记住,Java 已经检查了数组边界,并且在典型情况下,减速比 2 小得多。在 Clang 和 GCC 中,我们有 -ftrapv 用于检查(有符号)整数溢出,它并没有那么慢,或者。
因此问题是:我们如何在 Swift 中获得合理的性能而不失去安全网?
编辑 2:我做了更多的基准测试,沿线非常简单的循环
for i in 0..<n {
x[i] = x[i] ^ 12345678
}
(这里的异或操作只是为了让我可以更容易地在汇编代码中找到相关的循环。我试图选择一个易于发现但在不需要的意义上“无害”的操作任何与整数溢出相关的检查。)
同样,-O3 和 -Ofast 之间的性能存在巨大差异。所以我看了一下汇编代码:
有了
-Ofast,我得到的几乎是我所期望的。相关部分是一个包含 5 条机器语言指令的循环。有了
-O3,我得到了超出我想象的东西。内部循环跨越 88 行汇编代码。我并没有试图理解所有这些,但最可疑的部分是 13 次“callq _swift_retain”调用和另外 13 次“callq _swift_release”调用。也就是内循环中有26个子程序调用!
编辑 3: 在 cmets 中,Ferruccio 要求在不依赖内置函数(例如排序)的意义上公平的基准。我认为下面的程序是一个很好的例子:
let n = 10000
var x = [Int](repeating: 1, count: n)
for i in 0..<n {
for j in 0..<n {
x[i] = x[j]
}
}
没有算术,所以我们不需要担心整数溢出。我们唯一要做的就是大量的数组引用。结果就在这里——与 -Ofast 相比,Swift -O3 损失了近 500 倍:
- C++ -O3:0.05 秒
- C++ -O0:0.4 秒
- Java:0.2 秒
- Python 与 PyPy:0.5 秒
- Python:12 秒
- Swift -Ofast:0.05 秒
- Swift -O3:23 秒
- 斯威夫特 -O0:443 秒
(如果您担心编译器可能会完全优化无意义的循环,您可以将其更改为例如x[i] ^= x[j],并添加一个输出x[0] 的打印语句。这不会改变任何内容;时间将是非常相似。)
是的,这里的 Python 实现是一个愚蠢的纯 Python 实现,带有一个整数列表和嵌套的 for 循环。它应该比未优化的 Swift 慢 很多。 Swift 和数组索引似乎严重破坏了某些东西。
编辑 4:这些问题(以及其他一些性能问题)似乎已在 Xcode 6 beta 5 中得到修复。
对于排序,我现在有以下时间安排:
- clang++ -O3:0.06 秒
- swiftc -Ofast:0.1 秒
- swiftc -O: 0.1 秒
- swiftc:4 秒
对于嵌套循环:
- clang++ -O3:0.06 秒
- swiftc -Ofast:0.3 秒
- swiftc -O:0.4 秒
- swiftc:540 秒
似乎没有理由再使用不安全的-Ofast(又名-Ounchecked);普通的-O 产生同样好的代码。
【问题讨论】:
-
这是另一个“Swift 比 C 慢 100 倍”的问题:stackoverflow.com/questions/24102609/…
-
这里是关于苹果营销材料与 Swift 在排序方面的良好表现相关的讨论:programmers.stackexchange.com/q/242816/913
-
你可以编译:
xcrun --sdk macosx swift -O3。它更短。 -
This 链接显示了与 Objective-C 相比的其他一些基本操作。
-
在 Beta 5 中,Swift 的速度有了显着提高——更多细节请参阅this post by Jesse Squires。
标签: swift performance sorting xcode6 compiler-optimization