我在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.6秒(绝招,仅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倍。
编辑:天气注意到更改-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
}
(这里只有xor操作,以便我可以更容易地在汇编代码中找到相关的循环。我试图选择一个易于发现但又“无害”的操作,因为它不需要任何相关的检查到整数溢出。)
同样,-O3
和之间的性能存在巨大差异-Ofast
。所以我看了一下汇编代码:
有了
-Ofast
我,我得到了我所期望的。相关部分是一个包含5条机器语言指令的循环。随着
-O3
我得到的东西,出乎我的想象。内部循环跨越88行汇编代码。我没有试图理解所有内容,但是最可疑的部分是“ callq _swift_retain”的13个调用和“ callq _swift_release”的另外13个调用。也就是说,内部循环中有26个子例程调用!
编辑3:在评论中,费鲁奇奥要求提供不依赖于内置函数(例如排序)的公平基准。我认为以下程序是一个很好的示例:
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秒
- Java:0.2秒
- 带有PyPy的Python:0.5秒
- Python:12秒
- 迅捷-Ofast:0.05 s
- Swift -O3:23秒
- 迅捷-O0:443秒
(如果您担心编译器可能会完全优化无意义的循环,则可以将其更改为eg x[i] ^= x[j]
,并添加一个输出输出的print语句x[0]
。这不会改变任何内容;时序将非常相似。)
是的,这里的Python实现是一个愚蠢的纯Python实现,带有一个整数列表和嵌套的for循环。这应该是很多比未优化雨燕慢。Swift和数组索引似乎严重破坏了某些东西。
编辑4:这些问题(以及其他一些性能问题)似乎已在Xcode 6 beta 5中得到修复。
为了进行排序,我现在有以下时间安排:
- lang ++ -O3:0.06 s
- swiftc -Ofast:0.1 s
- swiftc -O:0.1秒
- swiftc:4秒
对于嵌套循环:
- lang ++ -O3:0.06 s
- swiftc -Ofast:0.3秒
- swiftc -O:0.4秒
- swiftc:540秒
似乎没有理由再使用不安全的-Ofast
(aka -Ounchecked
)了;Plain -O
会产生同样好的代码。
xcrun --sdk macosx swift -O3
。更短