Swift Betaのパフォーマンス:配列の並べ替え


929

Swift Betaでアルゴリズムを実装していたところ、パフォーマンスが非常に低いことに気付きました。さらに深く掘り下げた後、ボトルネックの1つが配列の並べ替えと同じくらい単純なものであることに気付きました。関連する部分はここにあります:

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.06秒かかります。

Pythonでは、0.6秒かかります(トリックのない、整数のリストの場合はy = Sorted(x)のみ)。

Swiftでは、次のコマンドでコンパイルすると6秒かかります。

xcrun swift -O3 -sdk `xcrun --show-sdk-path --sdk macosx`

また、次のコマンドでコンパイルすると、88秒もかかります。

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:コメントで、Ferluccioは組み込み関数(たとえば、並べ替え)に依存しないという意味で公平なベンチマークを求めました。次のプログラムはかなり良い例だと思います:

let n = 10000
var x = [Int](repeating: 1, count: n)
for i in 0..<n {
    for j in 0..<n {
        x[i] = x[j]
    }
}

算術演算はないので、整数オーバーフローを心配する必要はありません。私たちが行う唯一のことは、配列参照の多くだけです。そして結果がここにあります-Swift -O3は-Ofastと比較してほぼ500倍の損失があります。

  • C ++ -O3:0.05秒
  • C ++ -O0:0.4秒
  • Java:0.2秒
  • PyPyを使用したPython:0.5秒
  • Python:12秒
  • Swift -Ofast:0.05秒
  • Swift -O3:23秒
  • Swift -O0:443秒

(コンパイラが無意味なループを完全に最適化するのではないかと心配している場合は、ループをeg x[i] ^= x[j]に変更し、出力するprintステートメントを追加できますx[0]。これにより何も変更されません。タイミングは非常に似ています。)

そして、はい、ここでのPythonの実装は、intのリストとforループの入れ子を持つ愚かな純粋なPythonの実装でした。最適化されていないSwiftよりもかなり遅くなるはずです。Swiftと配列のインデックス付けで何かが深刻に壊れているようです。


編集4:これらの問題(および他のいくつかのパフォーマンスの問題)は、Xcode 6ベータ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秒

unsafe -Ofast(別名-Ounchecked)を使用する理由はもうないようです。plain -Oも同様に良いコードを生成します。


20
ここでは別の「スウィフト100倍遅くCよりも」質問です:stackoverflow.com/questions/24102609/...
ユッカSuomela

16
そして、ここでのソートにスウィフトの優れた性能に関連したAppleのマーケティング資料に関する議論です:programmers.stackexchange.com/q/242816/913は
ユッカSuomela

2
以下を使用してコンパイルできますxcrun --sdk macosx swift -O3。短いです。
サザンホスピタリティ

3
このリンクは、Objective-Cと比較した他のいくつかの基本的な操作を示しています。
2014年

4
Beta 5では、Swiftの速度が大幅に改善されています。詳細については、Jesse Squiresによるこの投稿を参照してください。
ネイト・クック

回答:


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.

n-10_000の[-Onone]を使用した秒単位の時間:

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'答え、設定[-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

比較のために、これはn = 1_000_000の[-Onone]使用したものです。

Swift:            142.659763258
C:                0.162065333
Swift_sort:       114.095478272

したがって、最適化なしのSwiftは、開発のこの段階では、このベンチマークのCよりもほぼ1000倍遅くなりました。一方、両方のコンパイラを[-Ofast]に設定すると、Swiftは実際にはCよりもわずかに優れていませんが、少なくとも同等のパフォーマンスを発揮します。

[-Ofast]は言語のセマンティクスを変更し、安全でない可能性があることが指摘されています。これはXcode 5.0リリースノートでAppleが述べていることです。

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

[-チェックなし]

Swift:   0.000827764
C:       0.001078914

25
-emit-silを使用して中間SILコードを出力すると、何が保持されているかがわかります(argh、スタックオーバーフローが原因でこれをフォーマットできなくなっています)。これは、配列の内部バッファオブジェクトです。これは間違いなくオプティマイザのバグのように聞こえます。ARCオプティマイザは-Ofastなしで保持を削除できるはずです。
Catfish_Man 2014年

Ofast最適化を使用したい場合は、別の言語を使用する必要があることに同意しません。Cのような別の言語を選択した場合、境界チェックの問題やその他の軽微な問題にも同様に対処する必要があります。Swiftはデフォルトで安全であり、必要に応じてオプションで高速かつ安全でないため、正確にクールです。これにより、プログラマーはコードをデバッグして、すべてが正常であることを確認し、Ofastを使用してコンパイルすることができます。Cのような「安全でない」言語の能力を備えながらも、最新の標準を使用できる可能性は非常に優れています。
Wallacy 14年

2
それがどのように無効であるかを教えていただける場合は、どうぞ。私はいつももっと学びたいです
ジョセフ・マーク

3
最終更新を行い、Swiftは標準の最適化を使用したこのベンチマークでCと同じくらい高速になりました。
ジョセフマーク

4
ヒント:最初に最小のパーティションで再帰すると、クイックソートの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_retainし、swift_release彼らはこの例であるはずのように、正直、見ていないの呼び出しを。オプティマイザは配列に関するほとんどの情報を知っており、(少なくとも)それに強い参照があることを知っているため、AFAICT(のほとんど)を省略しているはずです。

オブジェクトを解放する可能性のある関数を呼び出していない場合でも、より多くの保持を発行するべきではありません。配列コンストラクターは要求されたものよりも小さい配列を返すことができないと思います。つまり、発行された多くのチェックは役に立たないということです。また、整数が10kを超えることは決してないので、オーバーフローチェック最適化できます(-Ofast奇妙さのためではなく、言語のセマンティクスのため)(varを変更したり、変数にアクセスしたりして、最大10kを追加することはできません)タイプに対して安全ですInt)。

ただし、sort()外部関数であり、期待する引数を取得する必要があるに渡されているため、コンパイラーは配列または配列要素のボックス化を解除できない場合があります。これにより、Int値を間接的に使用する必要があり、少し遅くなります。sort()コンパイラーが(マルチメソッドではなく)汎用関数を使用してインライン化した場合、これは変わる可能性があります。

これは非常に新しい(公的)言語であり、そしてそれは私が仮定何を通過しているのフィードバックを求めスウィフト言語に関与(多額の)人々があるので、多くの変更であり、彼らはすべての言語が完成していないと言う意志変化する。

使用されるコード:

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:実際には多くのことを想定しています:-)コードをチェックしてオーバーフローを排除するのは難しいことです。私の経験から(私はコンパイラー作業を行い、いくつかの大きなコードベースをチェックしました)、コンパイラーを大企業で作業している人々から、オーバーフローやその他の未定義の動作を正しく取得するのを聞くのは難しいです。UBの修正に関するAppleのアドバイス(単なる例)でも間違っている場合があります(randomascii.wordpress.com/2014/04/17/…)。-Ofast言語セマンティクスも変更しますが、そのためのドキュメントに資金を提供することはできません。それが何をしているのかを確実に知るにはどうすればよいですか?
filcab 2014年

@bestsss:可能ですが、役に立たない可能性があります。Int []へのアクセスごとにチェックを追加します。Intの配列と他のいくつかのプリミティブ型(多くても3ビット)が頻繁に使用されるかどうかによって異なります(特に、必要に応じてCに下げることができる場合)。また、最終的に非ARC GCを追加したい場合に使用する可能性のあるビットを使い果たします。また、2つ以上の引数を持つジェネリックにもスケーリングしません。それらはすべてのタイプを持っているので、インライン化された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()

結果:

Swift 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

そこスウィフト2.0から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

-Oとの間のギャップを維持しながら、Swift 4は再びパフォーマンスを向上させ-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 ++と同じくらい高速に見えます。


2
実際には、1000万個の要素を挿入する前にvector :: reserve()を呼び出さないでください。
BJovke 2018年

たぶん!現在、ソートのみが時間計測されています。
OpenGL ESを学ぶ

34

からThe Swift Programming Language

ソート関数Swiftの標準ライブラリは、sortという関数を提供します。これは、指定したソートクロージャーの出力に基づいて、既知のタイプの値の配列をソートします。並べ替えプロセスが完了すると、sort関数は、元の配列と同じタイプとサイズの新しい配列を返します。その配列は、正しい並べ替え順序で要素が配置されます。

sortこの関数は2つの宣言があります。

比較クロージャーを指定できるデフォルトの宣言:

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

そして、単一のパラメーター(配列)のみを取り、「小なり比較演算子を使用するようにハードコードされています」という2番目の宣言。

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
また、Appleは、「複雑なオブジェクトのソート」(それが何であれ)はPythonよりもSwiftの方が3.9倍速いと主張しました。したがって、「より良いソート機能」を見つける必要はありません。-しかしSwiftはまだ開発中です...
Martin R

6
それはない自然対数を参照してください。
マーティンR

24
log(n)アルゴリズムの複雑さのために、慣習的には、log base-2を指します。底を述べない理由は、対数の底の変化の法則は定数乗数を導入するだけであり、O表記の目的で破棄されているためです。
minuteman3

3
自然対数と2を底とする対数に関する議論について:ウィキペディアのページからの正確な説明は、n個の要素に必要な比較の平均数はであるということですC(n) = 2n ln n ≈ 1.39n log₂ n。n = 1000の場合、これはC(n)= 13815となり、「big-O表記」ではありません
マーティン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.19X-1.20X長くかかります。高速で積極的な最適化では、その差はわずかに小さくなります(SwiftはCよりも1.18倍から1.16倍長くかかります。

通常のSwift配列を使用する場合、Cとの違いはわずかです大きくなります。(Swiftは、1.22〜1.23長くかかります。)

通常のSwiftアレイは DRAMATICALLY、Swift 1.2 / Xcode 6の場合よりも高速です。そのパフォーマンスはSwiftの安全でないバッファーベースの配列に非常に近いため、安全でないメモリバッファーを使用しても問題はないようです。

ところで、Objective-C NSArrayのパフォーマンスは悪臭を放ちます。両方の言語でネイティブコンテナーオブジェクトを使用する場合、Swiftは劇的に高速です。

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のパーティショニングアルゴリズムを確認できます。スウィフトで書かれました。


4

Swift 4.1では、新しい-Osize最適化モードが導入されています。

Swift 4.1では、コンパイラーは新しい最適化モードをサポートし、専用の最適化によりコードサイズを削減できるようになりました。

Swiftコンパイラには強力な最適化機能が備わっています。-Oを指定してコンパイルすると、コンパイラーはコードを変換して、最大のパフォーマンスで実行できるようにします。ただし、このランタイムパフォーマンスの向上には、コードサイズの増加というトレードオフが伴う場合があります。新しい-Osize最適化モードでは、ユーザーは最大速度ではなく最小コードサイズでコンパイルすることを選択できます。

コマンドラインでサイズ最適化モードを有効にするには、-Oの代わりに-Osizeを使用します。

さらに読む:https : //swift.org/blog/osize/

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.