我在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倍 .
Edit: mweathers注意到将 -O3
更改为 -Ofast
使得此代码的运行速度几乎与C版本一样快!但是, -Ofast
更改了语言的语义 - 在我的测试中,它是 disabled the checks for integer overflows and array indexing overflows . 例如,使用 -Ofast
,以下Swift代码以静默方式运行而不会崩溃(并打印出一些垃圾):
let n = 10000000
print(n*n*n*n*n)
let x = [Int](repeating: 10, count: n)
print(x[n])
所以 -Ofast
不是我们想要的;斯威夫特的全部意义在于我们有安全网 . 当然,安全网对性能有一些影响,但它们不应该使程序慢100倍 . 请记住,Java已经检查了数组边界,并且在典型情况下,减速是一个远小于2的因素 . 在Clang和GCC中,我们有 -ftrapv
用于检查(签名)整数溢出,并且它也不是那么慢 .
因此,问题是:如何在不丢失安全网的情况下在Swift中获得合理的性能?
Edit 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 subroutine calls in the inner loop !
Edit 3: 在评论中,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秒
-
Java: 0.2 s
使用PyPy的 -
Python:0.5秒
-
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和数组索引似乎严重破坏了某些东西 .
Edit 4: 这些问题(以及其他一些性能问题)似乎已在Xcode 6 beta 5中得到修复 .
为了排序,我现在有以下时间:
-
clang -O3:0.06 s
-
swiftc -Ofast:0.1秒
-
swiftc -O:0.1 s
-
swiftc:4秒
对于嵌套循环:
-
clang -O3:0.06 s
-
swiftc -Ofast:0.3秒
-
swiftc -O:0.4 s
-
swiftc:540 s
似乎没有理由再使用不安全的 -Ofast
(a.k.a . -Ounchecked
); plain -O
产生同样好的代码 .
8 回答
来自The Swift Programming Language:
sort
函数有两个声明 .允许您指定比较闭包的默认声明:
并且第二个声明只接受一个参数(数组)并且“硬编码以使用小于比较器” .
我在操场上测试了代码的修改版本并添加了闭包因此,我可以更密切地监视函数,并且我发现当n设置为1000时,闭包被调用大约11,000次 .
它不是一个有效的功能,我建议使用更好的排序功能实现 .
EDIT:
我看了一下Quicksort维基百科页面并为它编写了一个Swift实现 . 这是我用过的完整程序(在操场上)
使用n = 1000,我发现了
quickSort()被召唤约650次,
约6000次互换,
并且大约有10,000次比较
似乎内置的排序方法是(或接近)快速排序,而且非常慢......
tl; Dr Swift 1.0现在使用默认版本优化级别[-O]通过此基准测试与C一样快 .
这是Swift Beta中的就地快速排序:
在C中也一样:
两者都有效:
两者都在与编写的程序中调用 .
这会将绝对时间转换为秒:
以下是编译器优化级别的摘要:
使用 [-Onone] for n=10_000 的时间(以秒为单位):
这是Swift对 n=10_000 的内置排序():
这是 [-O] for n=10_000 :
如您所见,Swift的性能提高了20倍 .
根据mweathers' answer,设置 [-Ofast] 会产生真正的差异,导致 n=10_000 的这些时间:
对于 n=1_000_000 :
为了比较,这是 [-Onone] for n=1_000_000 :
因此,在此开发阶段,没有优化的Swift在此基准测试中比C慢近1000倍 . 另一方面,两个编译器设置为[-Ofast] Swift实际上至少表现得相当好,如果不是比C略好 .
有人指出[-Ofast]会改变语言的语义,使其可能不安全 . 这就是Apple在Xcode 5.0发行说明中所说的:
他们都提倡它 . 是否's wise or not I couldn' t说,但是从我所知道的,如果你确信在你的程序中没有整数或数组溢出,那么在一个版本中使用[-Ofast]似乎是合理的 . 如果您确实需要高性能和溢出检查/精确算术,那么现在就选择另一种语言 .
BETA 3更新:
n=10_000 与 [-O] :
一般来说Swift有点快,看起来Swift的内置排序已经发生了很大变化 .
FINAL UPDATE:
[-Onone] :
[-O] :
[-Ounchecked] :
其他人提到的主要问题却没有得到充分说明,
-O3
在Swift中根本没有做任何事情(从来没有),因此在编译时它实际上是非优化的(-Onone
) .选项名称随着时间的推移而发生了变化,因此其他一些答案的构建选项都有过时的标志 . 正确的当前选项(Swift 2.2)是:
整个模块优化具有较慢的编译,但可以跨模块内的文件优化,即在每个框架内和实际应用程序代码内但不在它们之间 . 您应该将此用于任何性能关键)
您还可以禁用安全检查以获得更高的速度,但所有断言和前置条件不仅被禁用,而且在它们正确的基础上进行优化 . 如果您遇到过断言,则意味着您处于未定义的行为 . 请谨慎使用,并且只有在确定速度提升对您有用时(通过测试) . 如果您确实发现它对某些代码很有 Value ,我建议将该代码分离到一个单独的框架中,并且只禁用该模块的安全检查 .
从Xcode 7开始,您可以打开
Fast, Whole Module Optimization
. 这应该会立即提高您的表现 .重新审视Swift Array性能:
我编写了自己的基准,将Swift与C / Objective-C进行了比较 . 我的基准计算了素数 . 它使用先前素数的数组来寻找每个新候选者中的素因子,因此它非常快 . 但是,它确实可以读取数组,而且可以减少对数组的写入 .
我最初针对Swift 1.2做了这个基准测试 . 我决定更新项目并针对Swift 2.0运行它 .
该项目允许您在使用普通swift数组和使用数组语义的Swift不安全内存缓冲区之间进行选择 .
对于C / Objective-C,您可以选择使用NSArrays或C malloc的数组 .
测试结果似乎与最快,最小的代码优化([-0s])或最快,激进([ - 0fast])优化非常相似 .
Swift 2.0的性能仍然很糟糕,代码优化关闭,而C / Objective-C性能只是适度慢 .
最重要的是C malloc的基于数组的计算是最快的,是适度的余量
使用最快,最小的代码优化时,使用不安全缓冲区的Swift比C malloc阵列长约1.19倍 - 1.20倍 . 通过快速,积极的优化,差异似乎略微减少(Swift比C长1.18倍到1.16倍更长)
如果使用常规的Swift数组,与C的差异会略大一些 . (斯威夫特需要大约1.22到1.23 . )
常规的Swift数组比它们在Swift 1.2 / Xcode 6中的速度快97.97.7 . 它们的性能非常接近Swift不安全的基于缓冲区的数组,使用不安全的内存缓冲区似乎不值得再麻烦了,这很大 .
BTW,Objective-C NSArray表现很糟糕 . 如果你打算在两种语言中使用本机容器对象,那么Swift的速度要快得多_97758 .
您可以在SwiftPerformanceBenchmark上查看我在github上的项目
它有一个简单的用户界面,可以很容易地收集统计数据 .
有趣的是,Swift中的排序似乎比现在的C稍快,但是这个素数算法在Swift中仍然更快 .
我决定看看这个很有趣,以下是我得到的时间:
斯威夫特
结果:
Swift 1.1
Swift 1.2
Swift 2.0
如果我用
-Ounchecked
编译,它似乎是相同的性能 .Swift 3.0
似乎从Swift 2.0到Swift 3.0的性能回归,我也第一次看到了
-O
和-Ounchecked
之间的差异 .Swift 4.0
Swift 4再次改善了性能,同时保持了
-O
和-Ounchecked
之间的差距 .-O -whole-module-optimization
似乎没有什么区别 .C.
结果:
Apple Clang 6.0
Apple Clang 6.1.0
Apple Clang 7.0.0
Apple Clang 8.0.0
Apple Clang 9.0.0
判决
截至本文撰写时,使用
-O
与上述编译器和库编译时,Swift 's sort is fast, but not yet as fast as C++'的排序 . 使用-Ounchecked
,它似乎与Swift 4.0.2和Apple LLVM 9.0.0中的C一样快 .这是关于快速排序的博客Github sample Quick-Sort
您可以在分区列表中查看Lomuto的分区算法 . 用Swift写的
TL;DR :是的,目前唯一的Swift语言实现很慢 . 如果您需要快速的数字(以及其他类型的代码,可能是代码)代码,请使用另一个代码 . 将来,您应该重新评估您的选择 . 但是,对于大多数应用程序代码而言,它可能已经足够好了 .
从我在SIL和LLVM IR中看到的情况来看,似乎他们需要一堆优化来删除保留和释放,这可能是在Clang(针对Objective-C)中实现的,但他们却没有't ported them yet. That' s理论我是(现在......我还需要确认Clang对此做了些什么),因为在这个问题的最后一个测试用例上运行的探查器产生了这个“漂亮”的结果:
正如许多其他人所说,
-Ofast
完全不安全并且改变了语言语义 . 对我来说,'s at the “If you'将继续使用它,只需使用另一种语言“舞台 . 如果它发生变化,我将在稍后重新评估该选择 .-O3
给我们带来了一堆swift_retain
和swift_release
调用,老实说,看起来他们不应该在这个例子中 . 优化器应该将它们(大部分)省略为AFAICT,因为它知道有关该数组的大部分信息,并且知道它(至少)有一个强引用它 .它甚至不应该调用可能释放对象的函数 . 我不认为数组构造函数可以返回一个小于所要求的数组,这意味着发出的大量检查都是无用的 . 它也知道整数永远不会超过10k,所以可以优化溢出检查(不是因为
-Ofast
古怪,但是由于语言的语义(没有其他任何东西改变了var也无法访问它,并且加起来10k对于Int
)类型是安全的 .但是,编译器可能无法取消装入数组或数组元素,因为它们被传递给
sort()
,这是一个外部函数,必须得到它所期望的参数 . 这将使我们必须间接使用Int
值,这会使它变得有点慢 . 如果编译器可以使用sort()
泛型函数(不是以多方法方式)并且内联,则可能会发生这种情况 .这是一种非常新的(公开)语言,它正在经历我认为的很多变化,因为有人(大量)参与Swift语言请求反馈,他们都说语言没有完成,并且会更改 .
使用的代码:
P.S:我不是Objective-C的专家,也不是Cocoa,Objective-C或Swift运行时的所有工具 . 我也可能会假设一些我没写过的东西 .