我在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中"Release"版本与"Debug"版本的时间安排类似.
这里怎么了?与C++相比,我可以理解一些性能损失,但与纯Python相比,它的速度并不是10倍.
Edit:的天气注意到,-O3
到-Ofast
的变化使得这个代码运行的速度和C++版本差不多快!然而,-Ofast
极大地改变了语言的语义——在我的测试中,它是disabled the checks for integer overflows and array indexing overflows.例如,对于-Ofast
,下面的Swift代码在没有崩溃的情况下silent运行(并打印出一些垃圾):
let n = 10000000
print(n*n*n*n*n)
let x = [Int](repeating: 10, count: n)
print(x[n])
所以-Ofast
不是我们想要的;Swift的全部意义在于我们已经建立了安全网.当然,安全网对性能有一定影响,但它们不应使程序的速度降低-Ofast
倍.请记住,Java已经判断了数组边界,在典型情况下,速度的降低要比2小得多.在Clang和GCC中,我们有-ftrapv
个用于判断(有符号)整数溢出,而且速度也不慢.
因此产生了一个问题:我们如何在不失go 安全网的情况下在Swift中获得合理的性能?
Edit 2:我做了更多的基准测试,沿着
for i in 0..<n {
x[i] = x[i] ^ 12345678
}
(这里有xor操作,这样我就可以更容易地在汇编代码中找到相关的循环.我试图 Select 一个容易发现但"无害"的操作,因为它不需要任何与整数溢出相关的判断.)
同样,-O3
和-Ofast
之间的表现存在巨大差异.所以我看了一下汇编代码:
有了
-Ofast
美元,我得到的几乎是我所期望的.相关部分是一个包含5条机器语言指令的循环.有了
-O3
英镑,我得到了超出我想象的东西.内部循环跨越88行汇编代码.我并没有试图理解所有的内容,但最可疑的部分是13次调用"callq_swift_retain"和另外13次调用"callq_swift_release".就是26 subroutine calls in the inner loop!
Edit 3:在 comments 中,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 s
- C++ -O0:0.4 s
- Java :0.2 s
- 带PyPy的Python:0.5s
- Python :12 s
- Swift-Ofast:0.05秒
- Swift -O3:23 s
- Swift-O0:443秒
(如果您担心编译器可能会完全优化无意义的循环,您可以将其更改为例如x[i] ^= x[j]
,并添加输出x[0]
的打印语句.这不会改变任何内容;计时将非常相似.)
是的,这里的Python实现是一个愚蠢的纯Python实现,有一个int列表和嵌套for循环.它应该比未优化的Swift慢much.Swift和数组索引似乎严重 destruct 了某些功能.
Edit 4:这些问题(以及其他一些性能问题)似乎已在Xcode 6 beta 5中修复.
对于排序,我现在有以下时间安排:
- 叮当声++-O3:0.06秒
- swiftc-Ofast:0.1秒
- swiftc-O:0.1秒
- swiftc:4秒
对于嵌套循环:
- 叮当声++-O3:0.06秒
- swiftc-Ofast:0.3秒
- swiftc-O:0.4秒
- swiftc:540秒
似乎没有理由再使用不安全的-Ofast
(又称-Ounchecked
);普通-O
产生同样好的代码.