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


929

我在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会产生同样好的代码。


20
这里是问题“比C慢夫特100倍”另一:stackoverflow.com/questions/24102609/...
的Jukka Suomela

16
以下是与Swift的出色排序相关的苹果营销材料的讨论:programmers.stackexchange.com/q/242816/913
Jukka Suomela 2014年

2
您可以使用进行编译xcrun --sdk macosx swift -O3。更短
南部的好客

3
链接显示了与Objective-C相比的其他一些基本操作。
2014年

4
使用Beta 5,Swift的速度有了实质性的提高- 有关更多详细信息,请参阅Jesse Squires的这篇文章
内特·库克

Answers:


460

tl; dr使用默认的发行优化级别[-O],在此基准下,Swift 1.0现在与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

这是Swift的内置sort(),其n = 10_000

Swift_builtin:    0.77865783

这是[-O]n = 10_000

Swift:            0.045478346
C:                0.000784666
Swift_builtin:    0.032513488

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

根据mweathers的回答,设置[-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]n = 1_000_000

Swift:            142.659763258
C:                0.162065333
Swift_sort:       114.095478272

因此,在开发的这个阶段,没有优化的Swift几乎比该基准测试中的C慢1000倍。另一方面,将两个编译器都设置为[-Ofast],即使实际上不比C稍好,Swift的实际效果也至少一样好。

已经指出,[-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

25
使用-emit-sil输出中间的SIL代码可显示所保留的内容(啊,堆栈溢出使得这无法格式化)。它是数组中的内部缓冲区对象。这绝对听起来像是优化程序错误,ARC优化程序应该能够在不使用-Ofast的情况下删除保留。
Catfish_Man 2014年

只是不同意,如果要使用Ofast优化,我们必须使用另一种语言。如果选择其他语言,如C,它将必须类似地处理边界检查和其他次要问题。swift的确很酷,因为默认情况下它是安全的,并且在需要时可以选择快速而又不安全。这使程序员也可以调试您的代码,以确保一切正常并使用Ofast进行编译。使用现代标准并拥有像C这样的“不安全”语言的能力的可能性非常高。
Wallacy

2
如果您可以告诉我如何可能无效,请执行。我一直喜欢学习更多
约瑟夫·马克

3
进行了最后一次更新,通过使用标准优化,Swift在该基准测试中现在与C一样快。
约瑟夫·马克

4
提示:如果您首先在最小分区上递归,则可以改善quicksort的Swift和C实现!(而不是总是先在左分区上递归。)在最坏的情况下使用简单的枢轴选择实现的快速排序需要O(n ^ 2)时间,但即使在这种最坏的情况下,您也只需递归O(log n)堆栈空间首先在较小的分区上。
Macneil Shonle

108

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

根据我在SIL和LLVM IR中看到的内容,似乎他们需要大量优化来删除保留和释放,这可以在Clang中实现(对于Objective-C),但是它们还没有移植。这就是我要遵循的理论(目前……我仍然需要确认Clang对此做了什么),因为在此问题的最后一个测试用例上运行的探查器会产生“漂亮”的结果:

在-O3上进行时间分析 在-Ofast上进行时间分析

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

-O3我们得到了一堆swift_retainswift_release电话的是,说实话,并不像他们应该为这个例子在那里。该优化器应该(大部分)省略了AFAICT,因为它知道有关数组的大多数信息,并且知道(至少)对其有很强的引用。

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

但是,编译器可能无法对数组或数组元素进行拆箱,因为它们将传递给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")

PS:我不是Objective-C的专家,也不是Cocoa,Objective-C或Swift运行时的所有工具。我可能还会假设一些我没有写的东西。


但是,编译器可能无法将数组或数组元素拆箱,因为它们将被传递给sort(),后者是一个外部函数,必须获取其期望的参数。对于一个相对好的编译器来说,这无关紧要。传递有关实际数据的元数据(在指针中-64位提供大量堤坝),并将其分支到调用的函数中。
bestsss

3
究竟是什么使-Ofast“完全不安全”?假设您知道如何测试代码并排除溢出。
约瑟夫·马克

@sjeohp:这实际上是很多假设:-)很难检查代码并排除溢出。根据我的经验(我从事过编译器工作,并检查了一些大型代码库),并且从那些在大型公司从事过编译器工作的人那里听到的消息,很难使溢出和其他未定义的行为正确。甚至Apple关于修复UB的建议(仅作为示例)有时也是错误的(randomascii.wordpress.com/2014/04/17/…)。-Ofast也更改了语言语义,但是我不能为此提供任何文档。您如何自信地知道它在做什么?
filcab 2014年

@bestsss:有可能,但是可能没有用。它为对Int []的每次访问添加检查。这取决于是否大量使用了Int数组和其他一些原始类型(最多只有3位)(尤其是在需要时可以降低到C的情况下)。如果最终要添加非ARC GC,它还会用掉一些他们可能想要使用的位。同样,它也不适合具有多个参数的泛型。由于它们具有所有类型,因此将所有接触Int [](但不是Int?[])的代码专用于使用内联Int会容易得多。但是随后您需要担心Obj-C互操作性。
filcab 2014年

@filcab,非ARC(即真实)GC实际上很有用,但是如果他们想要真正的并发,非STW GC,则需要与C不兼容的东西。我不必担心“每次访问Int[]”,因为这取决于编译器可以内联的级别,并且在某些指导下/之后,它应该能够内联紧密循环。
bestsss 2014年

53

我决定看看这个很有趣,这是我得到的时间:

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

斯威夫特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

雨燕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

斯威夫特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

似乎是一个性能回归从雨燕2.0至3.0雨燕,而且我也看到之间的差异-O,并-Ounchecked首次。

迅捷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;
}

结果:

苹果C6.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

苹果C 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

苹果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

苹果铛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

苹果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 ++一样快。


2
实际上,永远不要在插入一千万个元素之前不调用vector :: reserve()
BJovke

也许!目前仅排序时间。
学习OpenGL ES

34

来自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,我发现

  1. quickSort()被调用约650次,
  2. 进行了大约6000次互换,
  3. 大约有10,000个比较

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


17
也许我完全错了,但是根据en.wikipedia.org/wiki/Quicksort,Quicksort中的平均比较数为2*n*log(n)。那是对n = 1000个元素进行排序的13815个比较,因此,如果调用比较函数约11000次,这似乎还不错。
Martin R

6
苹果还声称,Swift中的“复杂对象排序”(无论是哪种)比Python中快3.9倍。因此,不必找到“更好的排序功能”。-但是Swift仍在开发中……
Martin R

6
确实指自然对数。
马丁R

24
log(n)对于算法复杂性,传统上指对数为2。未说明基数的原因是,对数的基数变化定律仅引入一个常数乘数,出于O标记的目的,将其舍弃。
minutesman3 2014年

3
关于自然对数与以2为底的对数的讨论:Wikipedia页面上的精确陈述是,n个元素所需的平均比较数为C(n) = 2n ln n ≈ 1.39n log₂ n。对于n = 1000,这将得出C(n)= 13815,这不是 “ big-O表示法”。
Martin R

18

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

在此处输入图片说明


12

再谈Swift阵列的性能:

我编写了自己的基准,将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倍。

如果使用常规的斯威夫特阵列,与C不同的是稍微更大。(快速操作所需的时间要长〜1.22至1.23。)

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

顺便说一句,Objective-C NSArray的性能很差。如果你要使用本机的容器对象在两种语言中,斯威夫特是DRAMATICALLY更快。

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

它具有一个简单的UI,可以非常轻松地收集统计信息。

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


8

其他人提到但没有引起足够重视的主要问题是,-O3它在Swift 中根本不执行任何操作(并且从未执行过任何操作),因此在进行编译时实际上未进行优化(-Onone)。

选项名称已随时间更改,因此某些其他答案的生成选项已过时标志。正确的当前选项(Swift 2.2)是:

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

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

您还可以禁用安全检查,以提高速度,但不仅可以禁用所有断言和前提条件,还可以根据它们的正确性对其进行优化。如果您遇到断言,则意味着您陷入不确定的行为中。仅当您确定速度提升值得(通过测试)时,请格外小心。如果您发现它对某些代码有价值,建议您将该代码分成一个单独的框架,并仅禁用该模块的安全检查。


该答案现在已过期。从Swift 4.1开始,整个模块优化选项是一个单独的布尔值,可以与其他设置结合使用,现在有一个-O用于优化大小。当我有时间检查确切的选项标志时,我可能会更新。
约瑟夫·罗德

7
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示例快速排序

您可以在对列表进行分区中查看Lomuto的分区算法。用Swift编写。


4

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

在Swift 4.1中,编译器现在支持新的优化模式,该模式可以进行专门的优化以减小代码大小。

Swift编译器带有强大的优化功能。使用-O进行编译时,编译器将尝试转换代码,以便以最高性能执行。但是,运行时性能的这种改进有时可以通过增加代码大小来权衡。使用新的-Osize优化模式,用户可以选择以最小的代码大小而不是最大的速度进行编译。

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

进一步阅读:https : //swift.org/blog/osize/

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.