为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

喵喔喔 2020-02-03 13:54:52
我在Swift Beta中实现一种算法,发现性能非常差。深入研究后,我意识到瓶颈之一就是对数组进行排序一样简单。相关部分在这里:let n = 1000000var x =  [Int](repeating: 0, count: n)for i in 0..<n {    x[i] = random()}// start clock herelet 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 = 10000000print(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 = 10000var 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 sC ++ -O0:0.4秒Java:0.2秒带有PyPy的Python:0.5秒Python:12秒迅捷-Ofast:0.05 sSwift -O3:23秒迅捷-O0:443秒(如果担心编译器可能会完全优化无意义的循环,则可以将其更改为eg x[i] ^= x[j],并添加一个输出输出的print语句x[0]。这不会改变任何内容;时序将非常相似。)是的,这里的Python实现是一个愚蠢的纯Python实现,带有一个整数列表和嵌套的for循环。这应该是很多比未优化雨燕慢。Swift和数组索引似乎严重破坏了某些东西。
查看完整描述

3 回答

?
江户川乱折腾

TA贡献1851条经验 获得超5个赞

使用默认的发行优化级别[-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放宽了一些保守的限制,大多数情况下对于浮点运算来说是安全的,这对大多数代码来说都是安全的。它可以从编译器中获得明显的高性能优势。


他们全都主张。无论是不是明智,我都不能说,但是据我所知,如果您不进行高精度浮点运算并且您确信没有整数或整数,那么在发行版中使用[-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


查看完整回答
反对 回复 2020-02-03
?
慕标5832272

TA贡献1966条经验 获得超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

斯威夫特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 ++的排序还不及C ++的排序。使用-Ounchecked,它似乎与Swift 4.0.2和Apple LLVM 9.0.0中的C ++一样快。


查看完整回答
反对 回复 2020-02-03
  • 3 回答
  • 0 关注
  • 711 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信