Swift Beta 性能:对数组进行排序

Posted

技术标签:

【中文标题】Swift Beta 性能:对数组进行排序【英文标题】:Swift Beta performance: sorting arrays 【发布时间】: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 倍”的问题:***.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。 【参考方案1】:

来自The Swift Programming Language

排序函数 Swift 的标准库提供了一个名为 sort,它对已知类型的值数组进行排序,基于 您提供的排序闭包的输出。一旦完成 排序过程中,sort函数返回一个相同的新数组 类型和大小与旧版本相同,其元素已正确排序 顺序。

sort 函数有两个声明。

允许您指定比较闭包的默认声明:

func sort<T>(array: T[], pred: (T, T) -> Bool) -> T[]

第二个声明只接受一个参数(数组)并且“硬编码以使用小于比较器”。

func sort<T : Comparable>(array: T[]) -> T[]

Example:
sort( _arrayToSort_ )  $0 > $1 

我在一个添加了闭包的操场上测试了你的代码的修改版本,这样我就可以更仔细地监控函数,我发现当 n 设置为 1000 时,闭包被调用了大约 11,000 次。

let n = 1000
let x = Int[](count: n, repeatedValue: 0)
for i in 0..n 
    x[i] = random()

let y = sort(x)  $0 > $1 

这不是一个高效的函数,我建议使用更好的排序函数实现。

编辑:

我查看了 Quicksort ***页面并为它编写了一个 Swift 实现。这是我使用的完整程序(在操场上)

import Foundation

func quickSort(inout array: Int[], begin: Int, end: Int) 
    if (begin < end) 
        let p = partition(&array, begin, end)
        quickSort(&array, begin, p - 1)
        quickSort(&array, p + 1, end)
    


func partition(inout array: Int[], left: Int, right: Int) -> Int 
    let numElements = right - left + 1
    let pivotIndex = left + numElements / 2
    let pivotValue = array[pivotIndex]
    swap(&array[pivotIndex], &array[right])
    var storeIndex = left
    for i in left..right 
        let a = 1 // <- Used to see how many comparisons are made
        if array[i] <= pivotValue 
            swap(&array[i], &array[storeIndex])
            storeIndex++
        
    
    swap(&array[storeIndex], &array[right]) // Move pivot to its final place
    return storeIndex


let n = 1000
var x = Int[](count: n, repeatedValue: 0)
for i in 0..n 
    x[i] = Int(arc4random())


quickSort(&x, 0, x.count - 1) // <- Does the sorting

for i in 0..n 
    x[i] // <- Used by the playground to display the results

将它与 n=1000 一起使用,我发现

    quickSort() 被调用了大约 650 次, 进行了大约 6000 次交换, 大约有 10,000 次比较

似乎内置的排序方法是(或接近于)快速排序,而且真的很慢......

【讨论】:

也许我完全错了,但是根据en.wikipedia.org/wiki/Quicksort,Quicksort 中的平均比较次数是2*n*log(n)。这是对 n = 1000 个元素进行排序的 13815 次比较,所以如果比较函数被调用大约 11000 次,那似乎还不错。 Apple 还声称“复杂对象排序”(无论是什么)在 Swift 中比在 Python 中快 3.9 倍。因此,没有必要找到“更好的排序功能”。 - 但 Swift 仍在开发中... 确实指的是自然对数。 log(n) 算法复杂度通常是指 log base-2。没有说明底数的原因是对数的底数变化定律只引入了一个常数乘数,为了 O 表示法的目的而将其丢弃。 关于自然对数与以 2 为底的对数的讨论:***页面上的精确声明是,n 个元素所需的平均比较次数为 C(n) = 2n ln n ≈ 1.39n log₂ n。对于 n = 1000,这给出 C(n) = 13815,它不是“大 O 表示法”。【参考方案2】:

tl;dr Swift 1.0 现在在使用默认发布优化级别 [-O] 的基准测试中与 C 一样快。


这是 Swift Beta 中的就地快速排序:

func quicksort_swift(inout a:CInt[], start:Int, end:Int) 
    if (end - start < 2)
        return
    
    var p = a[start + (end - start)/2]
    var l = start
    var r = end - 1
    while (l <= r)
        if (a[l] < p)
            l += 1
            continue
        
        if (a[r] > p)
            r -= 1
            continue
        
        var t = a[l]
        a[l] = a[r]
        a[r] = t
        l += 1
        r -= 1
    
    quicksort_swift(&a, start, r + 1)
    quicksort_swift(&a, r + 1, end)

在 C 语言中也是如此:

void quicksort_c(int *a, int n) 
    if (n < 2)
        return;
    int p = a[n / 2];
    int *l = a;
    int *r = a + n - 1;
    while (l <= r) 
        if (*l < p) 
            l++;
            continue;
        
        if (*r > p) 
            r--;
            continue;
        
        int t = *l;
        *l++ = *r;
        *r-- = t;
    
    quicksort_c(a, r - a + 1);
    quicksort_c(l, a + n - l);

两者都有效:

var a_swift:CInt[] = [0,5,2,8,1234,-1,2]
var a_c:CInt[] = [0,5,2,8,1234,-1,2]

quicksort_swift(&a_swift, 0, a_swift.count)
quicksort_c(&a_c, CInt(a_c.count))

// [-1, 0, 2, 2, 5, 8, 1234]
// [-1, 0, 2, 2, 5, 8, 1234]

两者都在编写的同一程序中调用。

var x_swift = CInt[](count: n, repeatedValue: 0)
var x_c = CInt[](count: n, repeatedValue: 0)
for var i = 0; i < n; ++i 
    x_swift[i] = CInt(random())
    x_c[i] = CInt(random())


let swift_start:UInt64 = mach_absolute_time();
quicksort_swift(&x_swift, 0, x_swift.count)
let swift_stop:UInt64 = mach_absolute_time();

let c_start:UInt64 = mach_absolute_time();
quicksort_c(&x_c, CInt(x_c.count))
let c_stop:UInt64 = mach_absolute_time();

这会将绝对时间转换为秒:

static const uint64_t NANOS_PER_USEC = 1000ULL;
static const uint64_t NANOS_PER_MSEC = 1000ULL * NANOS_PER_USEC;
static const uint64_t NANOS_PER_SEC = 1000ULL * NANOS_PER_MSEC;

mach_timebase_info_data_t timebase_info;

uint64_t abs_to_nanos(uint64_t abs) 
    if ( timebase_info.denom == 0 ) 
        (void)mach_timebase_info(&timebase_info);
    
    return abs * timebase_info.numer  / timebase_info.denom;


double abs_to_seconds(uint64_t abs) 
    return abs_to_nanos(abs) / (double)NANOS_PER_SEC;

以下是编译器优化级别的摘要:

[-Onone] no optimizations, the default for debug.
[-O]     perform optimizations, the default for release.
[-Ofast] perform optimizations and disable runtime overflow checks and runtime type checks.

[-Onone] n=10_000 的时间(以秒为单位):

Swift:            0.895296452
C:                0.001223848

这里是 n=10_000 的 Swift 内置 sort():

Swift_builtin:    0.77865783

对于 n=10_000,这里是 [-O]

Swift:            0.045478346
C:                0.000784666
Swift_builtin:    0.032513488

如您所见,Swift 的性能提高了 20 倍。

根据mweathers' answer,设置 [-Ofast] 会产生真正的影响,导致 n=10_000 的这些时间:

Swift:            0.000706745
C:                0.000742374
Swift_builtin:    0.000603576

对于 n=1_000_000

Swift:            0.107111846
C:                0.114957179
Swift_sort:       0.092688548

为了比较,这是 [-Onone] for n=1_000_000

Swift:            142.659763258
C:                0.162065333
Swift_sort:       114.095478272

因此,在这个基准测试中,没有优化的 Swift 在其开发的这个阶段几乎比 C 慢 1000 倍。另一方面,当两个编译器都设置为 [-Ofast] 时,Swift 实际上的性能至少与 C 一样好。

有人指出,[-Ofast] 改变了语言的语义,使其可能不安全。这是 Apple 在 Xcode 5.0 发行说明中声明的内容:

LLVM 中提供了一个新的优化级别 -Ofast,可实现积极的优化。 -Ofast 放宽了一些保守的限制,主要是针对浮点运算,这对大多数代码都是安全的。它可以从编译器中获得显着的高性能优势。

他们几乎都提倡它。这是否明智我不能说,但据我所知,如果您不进行高精度浮点运算并且您确信没有整数或程序中可能出现数组溢出。如果您确实需要高性能溢出检查/精确算术,那么现在选择另一种语言。

测试版 3 更新:

n=10_000[-O]

Swift:            0.019697268
C:                0.000718064
Swift_sort:       0.002094721

总的来说,Swift 快了一点,看起来 Swift 的内置排序发生了很大变化。

最终更新:

[-Onone]

Swift:   0.678056695
C:       0.000973914

[-O]

Swift:   0.001158492
C:       0.001192406

[-Ounchecked]

Swift:   0.000827764
C:       0.001078914

【讨论】:

使用 -emit-sil 输出中间 SIL 代码显示保留的内容(啊,堆栈溢出导致无法格式化)。它是数组中的一个内部缓冲区对象。这听起来像是一个优化器错误,ARC 优化器应该能够在没有 -Ofast 的情况下删除保留。 只是不同意如果要使用 Ofast 优化,我们必须使用另一种语言。如果选择像 C 这样的另一种语言,它将不得不类似地处理边界检查和其他小问题。swift 很酷,正是因为它默认是安全的,如果需要,它可以选择快速和不安全。这也允许程序员调试您的代码,以确保一切正常并使用 Ofast 进行编译。使用现代标准但又拥有像 C 这样的“不安全”语言的强大功能的可能性非常酷。 如果你能告诉我它怎么可能是无效的,请做。我总是喜欢了解更多 进行了最后一次更新,使用标准优化,在这个基准测试中,Swift 现在与 C 一样快。 提示:如果您首先在 smallest 分区上进行递归,则可以改进快速排序的 Swift 和 C 实现! (而不是总是首先在左分区上递归。)在最坏的情况下使用简单的枢轴选择实现的快速排序需要 O(n^2) 时间,但即使在最坏的情况下,您也只需要 O(log n) 的堆栈空间通过递归首先在较小的分区上。【参考方案3】:

TL;DR:是的,唯一的 Swift 语言实现很慢,现在。如果您需要快速的数字(可能还有其他类型的代码)代码,只需使用另一个代码即可。将来,您应该重新评估您的选择。不过,对于大多数以更高级别编写的应用程序代码来说,这可能已经足够了。

从我在 SIL 和 LLVM IR 中看到的情况来看,他们似乎需要一系列优化来删除保留和发布,这可能在 Clang(针对 Objective-C)中实现,但他们还没有移植他们呢。这就是我要采用的理论(现在......我仍然需要确认 Clang 对此做了什么),因为在这个问题的最后一个测试用例上运行的分析器会产生这个“漂亮”的结果:

正如许多其他人所说,-Ofast 完全不安全并且会改变语言语义。对我来说,它处于“如果你要使用它,就使用另一种语言”阶段。如果它发生变化,我稍后会重新评估该选择。

-O3 给我们带来了一堆swift_retainswift_release 调用,老实说,看起来它们不应该出现在这个例子中。优化器应该已经忽略(大部分)它们 AFAICT,因为它知道关于数组的大部分信息,并且知道它(至少)对它有一个强引用。

当它甚至不调用可能释放对象的函数时,它不应该发出更多的保留。我不认为数组构造函数可以返回一个小于要求的数组,这意味着发出的许多检查都是无用的。它还知道整数永远不会超过 10k,因此溢出检查 可以 被优化(不是因为 -Ofast 的怪异,而是因为语言的语义(没有其他东西会改变那个 var也无法访问它,并且对于Int 类型而言,最多添加 10k 是安全的。

不过,编译器可能无法拆箱数组或数组元素,因为它们被传递给sort(),这是一个外部函数,必须获取它所期望的参数。这将使我们不得不间接使用Int 值,这会使其运行速度慢一些。如果 sort() 泛型函数(不是以多方法方式)可供编译器使用并被内联,这可能会改变。

这是一种非常新的(公开的)语言,我认为它正在经历很多变化,因为有人(大量)参与 Swift 语言寻求反馈,他们都说该语言不是完成并且改变。

使用的代码:

import Cocoa

let swift_start = NSDate.timeIntervalSinceReferenceDate();
let n: Int = 10000
let x = Int[](count: n, repeatedValue: 1)
for i in 0..n 
    for j in 0..n 
        let tmp: Int = x[j]
        x[i] = tmp
    

let y: Int[] = sort(x)
let swift_stop = NSDate.timeIntervalSinceReferenceDate();

println("\(swift_stop - swift_start)s")

P.S:我不是 Objective-C 方面的专家,也不是 Cocoa、Objective-C 或 Swift 运行时的所有工具。我可能还假设了一些我没有写的东西。

【讨论】:

不过,编译器可能无法拆箱数组或数组元素,因为它们被传递给 sort(),这是一个外部函数,必须获取参数这是预期的。 对于一个相对较好的编译器来说,这应该无关紧要。传递有关实际数据的元数据(在指针中 - 64 位提供很多堤坝)并将其分支到被调用的函数中。 究竟是什么让-Ofast“完全不安全”?假设您知道如何测试代码并排除溢出。 @sjeohp:这实际上假设了很多 :-) 检查代码并排除溢出是很难做到的。根据我的经验(我从事编译器工作并检查了一些大型代码库),而且我从在大公司从事编译器工作的人那里听说,正确处理溢出和其他未定义行为是困难。甚至 Apple 关于修复 UB 的建议(只是一个示例)有时也是错误的(randomascii.wordpress.com/2014/04/17/…)。 -Ofast 也改变了语言语义,但我不能为此资助任何文档。你怎么能确信你知道它在做什么? @bestsss:有可能,但可能没用。它添加了对 Int[] 的每次访问的检查。这取决于是否大量使用 Int 数组和其他一些基本类型(最多 3 位)(尤其是当您需要时可以降低到 C 时)。如果最终他们想要添加非 ARC GC,它还会使用一些他们可能想要使用的位。它也不能扩展到具有多个参数的泛型。由于它们具有所有类型,因此将所有涉及 Int[](但不是 Int?[])的代码专门化以使用内联 Int 会容易得多。但是你有 Obj-C 互操作需要担心。 @filcab,非 ARC(即真正的)GC 实际上很有用,但如果他们想要一个真正并发的非 STW GC,他们需要一些与 C 不兼容的东西。我不担心“对Int[] 的每次访问”,因为这取决于编译器可以内联的级别,并且它应该能够通过/在一些指导下内联紧密循环。【参考方案4】:

我决定看看这个是为了好玩,以下是我得到的时间:

Swift 4.0.2           :   0.83s (0.74s with `-Ounchecked`)
C++ (Apple LLVM 8.0.0):   0.74s

斯威夫特

// Swift 4.0 code
import Foundation

func doTest() -> Void 
    let arraySize = 10000000
    var randomNumbers = [UInt32]()

    for _ in 0..<arraySize 
        randomNumbers.append(arc4random_uniform(UInt32(arraySize)))
    

    let start = Date()
    randomNumbers.sort()
    let end = Date()

    print(randomNumbers[0])
    print("Elapsed time: \(end.timeIntervalSince(start))")


doTest()

结果:

斯威夫特 1.1

xcrun swiftc --version
Swift version 1.1 (swift-600.0.54.20)
Target: x86_64-apple-darwin14.0.0

xcrun swiftc -O SwiftSort.swift
./SwiftSort     
Elapsed time: 1.02204304933548

Swift 1.2

xcrun swiftc --version
Apple Swift version 1.2 (swiftlang-602.0.49.6 clang-602.0.49)
Target: x86_64-apple-darwin14.3.0

xcrun -sdk macosx swiftc -O SwiftSort.swift
./SwiftSort     
Elapsed time: 0.738763988018036

Swift 2.0

xcrun swiftc --version
Apple Swift version 2.0 (swiftlang-700.0.59 clang-700.0.72)
Target: x86_64-apple-darwin15.0.0

xcrun -sdk macosx swiftc -O SwiftSort.swift
./SwiftSort     
Elapsed time: 0.767306983470917

如果我用-Ounchecked 编译,性能似乎是一样的。

Swift 3.0

xcrun swiftc --version
Apple Swift version 3.0 (swiftlang-800.0.46.2 clang-800.0.38)
Target: x86_64-apple-macosx10.9

xcrun -sdk macosx swiftc -O SwiftSort.swift
./SwiftSort     
Elapsed time: 0.939633965492249

xcrun -sdk macosx swiftc -Ounchecked SwiftSort.swift
./SwiftSort     
Elapsed time: 0.866258025169373

从 Swift 2.0 到 Swift 3.0 似乎出现了性能倒退,我还第一次看到 -O-Ounchecked 之间的差异。

Swift 4.0

xcrun swiftc --version
Apple Swift version 4.0.2 (swiftlang-900.0.69.2 clang-900.0.38)
Target: x86_64-apple-macosx10.9

xcrun -sdk macosx swiftc -O SwiftSort.swift
./SwiftSort     
Elapsed time: 0.834299981594086

xcrun -sdk macosx swiftc -Ounchecked SwiftSort.swift
./SwiftSort     
Elapsed time: 0.742045998573303

Swift 4 再次提高了性能,同时保持-O-Ounchecked 之间的差距。 -O -whole-module-optimization 似乎没有什么不同。

C++

#include <chrono>
#include <iostream>
#include <vector>
#include <cstdint>
#include <stdlib.h>

using namespace std;
using namespace std::chrono;

int main(int argc, const char * argv[]) 
    const auto arraySize = 10000000;
    vector<uint32_t> randomNumbers;

    for (int i = 0; i < arraySize; ++i) 
        randomNumbers.emplace_back(arc4random_uniform(arraySize));
    

    const auto start = high_resolution_clock::now();
    sort(begin(randomNumbers), end(randomNumbers));
    const auto end = high_resolution_clock::now();

    cout << randomNumbers[0] << "\n";
    cout << "Elapsed time: " << duration_cast<duration<double>>(end - start).count() << "\n";

    return 0;

结果:

Apple Clang 6.0

clang++ --version
Apple LLVM version 6.0 (clang-600.0.54) (based on LLVM 3.5svn)
Target: x86_64-apple-darwin14.0.0
Thread model: posix

clang++ -O3 -std=c++11 CppSort.cpp -o CppSort
./CppSort     
Elapsed time: 0.688969

Apple Clang 6.1.0

clang++ --version
Apple LLVM version 6.1.0 (clang-602.0.49) (based on LLVM 3.6.0svn)
Target: x86_64-apple-darwin14.3.0
Thread model: posix

clang++ -O3 -std=c++11 CppSort.cpp -o CppSort
./CppSort     
Elapsed time: 0.670652

Apple Clang 7.0.0

clang++ --version
Apple LLVM version 7.0.0 (clang-700.0.72)
Target: x86_64-apple-darwin15.0.0
Thread model: posix

clang++ -O3 -std=c++11 CppSort.cpp -o CppSort
./CppSort     
Elapsed time: 0.690152

Apple Clang 8.0.0

clang++ --version
Apple LLVM version 8.0.0 (clang-800.0.38)
Target: x86_64-apple-darwin15.6.0
Thread model: posix

clang++ -O3 -std=c++11 CppSort.cpp -o CppSort
./CppSort     
Elapsed time: 0.68253

Apple Clang 9.0.0

clang++ --version
Apple LLVM version 9.0.0 (clang-900.0.38)
Target: x86_64-apple-darwin16.7.0
Thread model: posix

clang++ -O3 -std=c++11 CppSort.cpp -o CppSort
./CppSort     
Elapsed time: 0.736784

判决

在撰写本文时,Swift 的排序速度很快,但在使用-O 和上述编译器和库编译时,还不如 C++ 的排序快。使用-Ounchecked,它似乎与 Swift 4.0.2 和 Apple LLVM 9.0.0 中的 C++ 一样快。

【讨论】:

实际上,在插入一千万个元素之前,您永远不应该不调用vector::reserve() 也许吧!目前只有排序正在计时。【参考方案5】:

从 Xcode 7 开始,您可以打开 Fast, Whole Module Optimization。这应该会立即提高您的表现。

【讨论】:

【参考方案6】:

Swift Array 性能重温:

我编写了自己的基准测试来比较 Swift 和 C/Objective-C。我的基准计算素数。它使用先前素数的数组来查找每个新候选中的素数,因此速度非常快。但是,它会读取大量数组,而写入数组的次数更少。

我最初针对 Swift 1.2 进行了这个基准测试。我决定更新项目并针对 Swift 2.0 运行它。

该项目允许您在使用普通 swift 数组和使用数组语义的 Swift 不安全内存缓冲区之间进行选择。

对于 C/Objective-C,您可以选择使用 NSArrays 或 C malloc'ed 数组。

测试结果似乎与最快、最小的代码优化 ([-0s]) 或最快、激进的 ([-0fast]) 优化非常相似。

在关闭代码优化的情况下,Swift 2.0 的性能仍然很糟糕,而 C/Objective-C 的性能只是稍微慢了一点。

底线是 C malloc 的基于数组的计算是最快的,但幅度不大

在使用最快、最小的代码优化时,带有不安全缓冲区的 Swift 比 C malloc 的数组长大约 1.19X - 1.20X。通过快速、积极的优化,差异似乎略小一些(Swift 比 C 花费的时间多 1.18 到 1.16 倍。

如果你使用常规的 Swift 数组,与 C 的区别更大。 (Swift 需要大约 1.22 到 1.23 的时间。)

常规 Swift 数组比在 Swift 1.2/Xcode 6 中更快DRAMATICALLY。它们的性能非常接近基于 Swift 不安全缓冲区的数组,以至于使用不安全内存缓冲区似乎不再值得麻烦了,这很大.

顺便说一句,Objective-C NSArray 的性能很糟糕。如果您要在这两种语言中使用本机容器对象,Swift 会戏剧性地更快。

你可以在 github 上查看我的项目SwiftPerformanceBenchmark

它有一个简单的用户界面,可以很容易地收集统计数据。

有趣的是,现在在 Swift 中排序似乎比在 C 中稍快,但这种素数算法在 Swift 中仍然更快。

【讨论】:

【参考方案7】:

其他人提到但没有充分指出的主要问题是-O3 在 Swift 中什么都不做(而且从来没有)所以当用它编译时它实际上是非优化的(-Onone)。

选项名称随着时间的推移而变化,因此其他一些答案具有构建选项的过时标志。正确的当前选项(Swift 2.2)是:

-Onone // Debug - slow
-O     // Optimised
-O -whole-module-optimization //Optimised across files

整个模块优化的编译速度较慢,但​​可以跨模块内的文件进行优化,即在每个框架内和实际应用程序代码内,但不能在它们之间进行优化。您应该将其用于任何对性能至关重要的事情)

您还可以禁用安全检查以提高速度,但所有断言和先决条件不仅被禁用,而且在它们正确的基础上进行了优化。如果你曾经遇到过断言,这意味着你陷入了未定义的行为。仅当您确定速度提升对您来说是值得的(通过测试)时,请谨慎使用。如果您确实发现它对某些代码有价值,我建议将该代码分离到一个单独的框架中,并且只禁用该模块的安全检查。

【讨论】:

这个答案现在已经过时了。从 Swift 4.1 开始,整个模块优化选项是一个单独的布尔值,可以与其他设置结合使用,现在有一个 -Os 来优化大小。当我有时间检查确切的选项标志时,我可能会更新。【参考方案8】:
func partition(inout list : [Int], low: Int, high : Int) -> Int 
    let pivot = list[high]
    var j = low
    var i = j - 1
    while j < high 
        if list[j] <= pivot
            i += 1
            (list[i], list[j]) = (list[j], list[i])
        
        j += 1
    
    (list[i+1], list[high]) = (list[high], list[i+1])
    return i+1


func quikcSort(inout list : [Int] , low : Int , high : Int) 

    if low < high 
        let pIndex = partition(&list, low: low, high: high)
        quikcSort(&list, low: low, high: pIndex-1)
        quikcSort(&list, low: pIndex + 1, high: high)
    


var list = [7,3,15,10,0,8,2,4]
quikcSort(&list, low: 0, high: list.count-1)

var list2 = [ 10, 0, 3, 9, 2, 14, 26, 27, 1, 5, 8, -1, 8 ]
quikcSort(&list2, low: 0, high: list2.count-1)

var list3 = [1,3,9,8,2,7,5]
quikcSort(&list3, low: 0, high: list3.count-1) 

这是我关于快速排序的博客-Github sample Quick-Sort

您可以在 Partitioning list 中查看 Lomuto 的分区算法。 用 Swift 编写。

【讨论】:

【参考方案9】:

Swift 4.1 引入了新的-Osize 优化模式。

在 Swift 4.1 中,编译器现在支持一种新的优化模式,该模式 启用专门的优化以减少代码大小。

Swift 编译器具有强大的优化功能。编译时 使用 -O 编译器会尝试转换代码以使其执行 以最大的性能。然而,这种运行时的改进 性能有时会以增加代码大小为代价。 使用新的 -Osize 优化模式,用户可以选择 编译最小代码大小而不是最大速度。

要在命令行启用大小优化模式,请使用 -Osize 而不是 -O。

延伸阅读:https://swift.org/blog/osize/

【讨论】:

以上是关于Swift Beta 性能:对数组进行排序的主要内容,如果未能解决你的问题,请参考以下文章

排序数组字母数字Swift3

Swift vs Kotlin 在排序数组上的表现

Swift:按排序描述符对数组进行排序

Swift 对数组进行排序并返回索引和元素的数组

Swift2 - 根据另一个 INT 数组的排序顺序对多个数组进行排序

使用 Swift 对两个数组进行排序