it-swarm.com.de

Swift Beta-Leistung: Arrays sortieren

Ich habe in Swift Beta einen Algorithmus implementiert und festgestellt, dass die Leistung sehr schlecht ist. Nachdem ich tiefer gegraben hatte, wurde mir klar, dass einer der Engpässe so einfach war wie das Sortieren von Arrays. Der relevante Teil ist hier:

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

In C++ dauert eine ähnliche Operation .06s auf meinem Computer.

In Python dauert es ,6s (keine Tricks, nur y = sortiert (x) für eine Liste von ganzen Zahlen).

In Swift dauert es 6s, wenn ich es mit dem folgenden Befehl kompiliere:

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

Und es dauert so viel wie 88s, wenn ich es mit dem folgenden Befehl kompiliere:

xcrun Swift -O0 -sdk `xcrun --show-sdk-path --sdk macosx`

Timings in Xcode mit "Release" vs. "Debug" Builds sind ähnlich.

Was ist hier falsch? Ich konnte einen gewissen Leistungsverlust im Vergleich zu C++ verstehen, aber keine 10-fache Verlangsamung im Vergleich zu reinem Python.


Edit: Das Wetter hat bemerkt, dass durch Ändern von -O3 in -Ofast dieser Code fast so schnell ausgeführt wird wie die C++ - Version! Allerdings ändert -Ofast die Semantik der Sprache sehr - in meinen Tests die Überprüfung auf Integer-Überläufe und Array-Indizierungsüberläufe wurde deaktiviert. Beispiel: Mit -Ofast wird der folgende Swift Code ohne Absturz ausgeführt (und gibt einen Müll aus):

let n = 10000000
print(n*n*n*n*n)
let x =  [Int](repeating: 10, count: n)
print(x[n])

Also ist -Ofast nicht das, was wir wollen; Der springende Punkt von Swift ist, dass wir die Sicherheitsnetze an Ort und Stelle haben. Natürlich haben die Sicherheitsnetze einen gewissen Einfluss auf die Leistung, aber sie sollten die Programme nicht 100-mal langsamer machen. Denken Sie daran, dass Java bereits nach Array-Grenzen sucht, und in typischen Fällen ist die Verlangsamung um einen Faktor geringer als 2. Und in Clang und GCC haben wir -ftrapv zum Prüfen von (vorzeichenbehafteten) Ganzzahlen überläuft, und es ist auch nicht so langsam.

Daher die Frage: Wie können wir in Swift eine angemessene Leistung erzielen, ohne die Sicherheitsnetze zu verlieren?


Edit 2: Ich habe noch ein wenig Benchmarking gemacht, mit sehr einfachen Loops nach dem Vorbild von

for i in 0..<n {
    x[i] = x[i] ^ 12345678
}

(Hier ist die xor-Operation nur vorhanden, damit ich die relevante Schleife im Assembly-Code leichter finden kann. Ich habe versucht, eine Operation auszuwählen, die leicht zu erkennen, aber auch "harmlos" ist, in dem Sinne, dass keine Prüfungen erforderlich sind zu ganzzahligen Überläufen.)

Wieder gab es einen großen Unterschied in der Leistung zwischen -O3 und -Ofast. Also habe ich mir den Assembly Code angesehen:

  • Mit -Ofast bekomme ich so ziemlich das, was ich erwarten würde. Der relevante Teil ist eine Schleife mit 5 Anweisungen in Maschinensprache.

  • Mit -O3 bekomme ich etwas, das meine wildesten Vorstellungen übersteigt. Die innere Schleife umfasst 88 Zeilen Assembly-Code. Ich habe nicht versucht, alles zu verstehen, aber die verdächtigsten Teile sind 13 Aufrufe von "callq _Swift_retain" und weitere 13 Aufrufe von "callq _Swift_release". Das heißt, 26 Unterprogrammaufrufe in der inneren Schleife!


Edit 3: In Kommentaren hat Ferruccio nach Benchmarks gefragt, die in dem Sinne fair sind, dass sie nicht auf eingebauten Funktionen beruhen (z. B. sortieren). Ich denke, das folgende Programm ist ein ziemlich gutes Beispiel:

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

Es gibt keine Arithmetik, sodass wir uns keine Gedanken über Ganzzahlüberläufe machen müssen. Das einzige, was wir tun, sind nur viele Array-Referenzen. Und die Ergebnisse sind hier - Swift -O3 verliert um einen Faktor von fast 500 im Vergleich zu -Ofast:

  • C++ -O3: ,05 s
  • C++ -O0: 0,4 s
  • Java: ,2 s
  • Python mit PyPy: 0,5 s
  • Python: 12 s
  • Schnell-Schnell: 0,05 s
  • Swift -O3: 23 s
  • Swift -O0: 443 s

(Wenn Sie befürchten, der Compiler könnte die sinnlosen Schleifen vollständig optimieren, können Sie sie in x[i] ^= x[j] ändern und eine print-Anweisung hinzufügen, die x[0] ausgibt. Dies ändert nichts; die Zeitabläufe ändern sich sehr ähnlich sein.)

Und ja, hier war die Implementierung von Python eine blöde reine Implementierung von Python mit einer Liste von ints und verschachtelten for-Schleifen. Es sollte viel langsamer sein als nicht optimiertes Swift. Mit Swift und der Array-Indizierung scheint etwas ernsthaft kaputt zu sein.


Edit 4: Diese Probleme (sowie einige andere Leistungsprobleme) scheinen in Xcode 6 Beta 5 behoben worden zu sein.

Zum Sortieren habe ich jetzt folgende Zeiten:

  • clang ++ -O3: 0,06 s
  • swiftc -Ofast: 0,1 s
  • swiftc-O: 0,1 s
  • schnell: 4 s

Für verschachtelte Schleifen:

  • clang ++ -O3: 0,06 s
  • swiftc -Ofast: 0,3 s
  • swiftc -O: 0,4 s
  • swiftc: 540 s

Es scheint, dass es keinen Grund mehr gibt, den unsicheren -Ofast (a.k.a. -Ounchecked) zu verwenden; plain -O erzeugt gleich guten Code.

908
Jukka Suomela

tl; dr Swift 1.0 ist nach diesem Benchmark jetzt so schnell wie C, wobei die Standard-Release-Optimierungsebene [-O] verwendet wird.


Hier ist eine In-Place-Schnellübersicht in 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)
}

Und das gleiche in 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);
}

Beide arbeiten:

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]

Beide werden im selben Programm wie geschrieben aufgerufen.

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();

Dies konvertiert die absoluten Zeiten in Sekunden:

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

Hier ist eine Zusammenfassung der Optimierungsstufen des Compilers:

[-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.

Zeit in Sekunden mit [- Onone] für n = 10_0:

Swift:            0.895296452
C:                0.001223848

Hier ist Swifts eingebautes sort () für n = 10_0:

Swift_builtin:    0.77865783

Hier ist [- O] für n = 10_0:

Swift:            0.045478346
C:                0.000784666
Swift_builtin:    0.032513488

Wie Sie sehen, hat sich die Leistung von Swift um den Faktor 20 verbessert.

Gemäß mweathers 'answer macht die Einstellung [- Ofast] den wirklichen Unterschied, woraus sich diese Zeiten für n = 10_0 ergeben:

Swift:            0.000706745
C:                0.000742374
Swift_builtin:    0.000603576

Und für n = 1_000_0:

Swift:            0.107111846
C:                0.114957179
Swift_sort:       0.092688548

Zum Vergleich ist dies mit [- Onone] für n = 1_000_0:

Swift:            142.659763258
C:                0.162065333
Swift_sort:       114.095478272

Daher war Swift ohne Optimierungen in diesem Benchmark zu diesem Zeitpunkt in seiner Entwicklung fast 1000x langsamer als C. Auf der anderen Seite schnitten beide Compiler mit [-Ofast] Swift mindestens genauso gut ab, wenn auch nicht geringfügig besser als C.

Es wurde darauf hingewiesen, dass [-Ofast] die Semantik der Sprache ändert und sie möglicherweise unsicher macht. Das sagt Apple in den Versionshinweisen zu Xcode 5.0:

Eine neue Optimierungsstufe -Ofast, die in LLVM verfügbar ist, ermöglicht aggressive Optimierungen. -Ofast lockert einige konservative Einschränkungen, hauptsächlich für Gleitkommaoperationen, die für die meisten Codes sicher sind. Der Compiler kann signifikante High-Performance-Gewinne erzielen.

Sie alle aber befürworten es. Ob das klug ist oder nicht, konnte ich nicht sagen, aber nach allem, was ich sagen kann, scheint es vernünftig genug zu sein, [-Ofast] in einem Release zu verwenden, wenn Sie keine hochpräzise Gleitkomma-Arithmetik ausführen und sicher sind, dass keine Ganzzahl oder In Ihrem Programm sind Array-Überläufe möglich. Wenn Sie leistungsstarke und Überlaufprüfungen/präzise Arithmetik benötigen, wählen Sie zunächst eine andere Sprache.

BETA 3 UPDATE:

n = 10_0 mit [- O]:

Swift:            0.019697268
C:                0.000718064
Swift_sort:       0.002094721

Swift ist im Allgemeinen etwas schneller und es sieht so aus, als ob sich Swifts eingebaute Sortierung erheblich geändert hat.

FINAL UPDATE:

[- Onone]:

Swift:   0.678056695
C:       0.000973914

[- O]:

Swift:   0.001158492
C:       0.001192406

[- nicht überprüft]:

Swift:   0.000827764
C:       0.001078914
451
Joseph Mark

TL; DR : Ja, die einzige Swift Sprachimplementierung ist derzeit langsam . Wenn Sie schnellen, numerischen (und vermutlich auch anderen) Code benötigen, wählen Sie einfach einen anderen. In Zukunft sollten Sie Ihre Wahl überdenken. Es könnte jedoch für die meisten Anwendungen, die auf einer höheren Ebene geschrieben wurden, ausreichend sein.

Nach dem, was ich in SIL und LLVM IR sehe, scheinen sie eine Reihe von Optimierungen zum Entfernen von Retains und Releases zu benötigen, die möglicherweise in Clang (für Objective-C) implementiert sind, aber sie haben keine Chance habe sie noch nicht portiert. Das ist die Theorie, nach der ich vorerst vorgehen werde (ich muss noch bestätigen, dass Clang etwas dagegen unternimmt), da ein Profiler, der im letzten Testfall dieser Frage ausgeführt wird, dieses „hübsche“ Ergebnis liefert:

Time profiling on -O3Time profiling on -Ofast

Wie von vielen anderen gesagt, ist -Ofast völlig unsicher und ändert die Sprachsemantik. Für mich ist es die Phase „Wenn Sie das verwenden wollen, verwenden Sie einfach eine andere Sprache“. Ich werde diese Wahl später erneut prüfen, falls sie sich ändert.

-O3 bringt uns ein paar Swift_retain und Swift_release, die ehrlich gesagt nicht so aussehen, als sollten sie für dieses Beispiel da sein. Das Optimierungsprogramm hätte ihnen AFAICT (die meisten) ersparen sollen, da es die meisten Informationen über das Array kennt und weiß, dass es (zumindest) einen starken Bezug dazu hat.

Es sollte nicht mehr Retains ausgeben, wenn es nicht einmal Funktionen aufruft, die die Objekte freigeben könnten. Ich glaube nicht, dass ein Array-Konstruktor ein Array zurückgeben kann, das kleiner ist als gewünscht, was bedeutet, dass viele ausgegebene Prüfungen unbrauchbar sind. Es ist auch bekannt, dass die Ganzzahl niemals über 10k liegen wird, so dass die Überlaufprüfungen optimiert werden können (nicht wegen -Ofast, sondern wegen der Semantik der Sprache (Nichts anderes ändert diese Variable, noch kann es darauf zugreifen, und das Hinzufügen von bis zu 10 KB ist für den Typ Int ungefährlich.).

Der Compiler kann das Array oder die Array-Elemente möglicherweise nicht entpacken, da sie an sort() übergeben werden, eine externe Funktion, die die erwarteten Argumente abrufen muss. Dies wird uns veranlassen, die Int -Werte indirekt zu verwenden, was zu einer Verlangsamung führen würde. Dies könnte sich ändern, wenn die generische Funktion sort() (nicht in der Multi-Methoden-Art) dem Compiler zur Verfügung steht und eingebunden wird.

Dies ist eine sehr neue (öffentliche) Sprache, und ich gehe davon aus, dass sie viele Änderungen durchläuft, da es Leute gibt, die (stark) mit der Sprache Swift zu tun haben und um Feedback bitten und alle die Sprache sagen ist noch nicht fertig und wird sich ändern.

Code verwendet:

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: Ich bin kein Experte für Objective-C und auch nicht für alle Einrichtungen von Cocoa , Objective-C oder den Swift Laufzeiten. Ich könnte auch einige Dinge annehmen, die ich nicht geschrieben habe.

107
filcab

Ich habe beschlossen, mir das zum Spaß anzuschauen, und hier sind die Zeiten, die ich bekomme:

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

Swift

// 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()

Ergebnisse:

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.

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

Es scheint die gleiche Leistung zu sein, wenn ich mit -Ounchecked kompiliere.

Swift 3.

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

Es scheint einen Leistungsrückgang von Swift 2.0 zu Swift 3.0 gegeben zu haben, und ich sehe auch einen Unterschied zwischen -O und -Ounchecked zum ersten Mal Zeit.

Swift 4.

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 verbessert die Leistung erneut, während eine Lücke zwischen -O und -Ounchecked beibehalten wird. -O -whole-module-optimization schien keinen Unterschied zu machen.

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

Ergebnisse:

Apple Clang 6.

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.

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.

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.

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.

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

Urteil

Zum Zeitpunkt des Schreibens ist Swifts Sortierung schnell, aber noch nicht so schnell wie C++, wenn es mit -O kompiliert wird, mit den oben genannten Compilern und Bibliotheken. Mit -Ounchecked scheint es so schnell wie C++ in Swift 4.0.2 und Apple LLVM 9.0.0 zu sein.

50
Learn OpenGL ES

Von The Swift Programming Language :

Die Standardbibliothek der Sortierfunktion Swift bietet eine Funktion namens sort, die ein Array von Werten eines bekannten Typs sortiert, basierend auf der Ausgabe eines von Ihnen bereitgestellten Sortierabschlusses. Sobald der Sortiervorgang abgeschlossen ist, gibt die Sortierfunktion ein neues Array mit demselben Typ und derselben Größe wie das alte zurück, wobei die Elemente in der richtigen Reihenfolge sortiert sind.

Die Funktion sort hat zwei Deklarationen.

Die Standarddeklaration, mit der Sie einen Vergleichsabschluss angeben können:

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

Und eine zweite Deklaration, die nur einen einzigen Parameter (das Array) akzeptiert und "hartcodiert ist, um den Komparator kleiner als zu verwenden".

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

Example:
sort( _arrayToSort_ ) { $0 > $1 }
_

Ich habe eine modifizierte Version Ihres Codes auf einem Spielplatz mit dem hinzugefügten Closure getestet, damit ich die Funktion etwas genauer überwachen kann. Bei n auf 1000 wurde der Closure ungefähr 11.000 Mal aufgerufen.

_let n = 1000
let x = Int[](count: n, repeatedValue: 0)
for i in 0..n {
    x[i] = random()
}
let y = sort(x) { $0 > $1 }
_

Es ist keine effiziente Funktion, und ich würde empfehlen, eine bessere Implementierung der Sortierfunktion zu verwenden.

EDIT:

Ich habe mir die Wikipedia-Seite von Quicksort angesehen und eine Swift Implementierung dafür geschrieben. Hier ist das vollständige Programm, das ich verwendet habe (auf einem Spielplatz)

_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
}
_

Wenn ich dies mit n = 1000 benutze, habe ich das gefunden

  1. quickSort () wurde ungefähr 650 mal aufgerufen,
  2. es wurden etwa 6000 Swaps getätigt,
  3. und es gibt ungefähr 10.000 Vergleiche

Es scheint, dass die eingebaute Sortiermethode eine schnelle Sortierung ist (oder dieser nahekommt) und sehr langsam ist ...

33
David Skrundz

Ab Xcode 7 kannst du Fast, Whole Module Optimization einschalten. Dies sollte Ihre Leistung sofort erhöhen.

enter image description here

18
Antoine

Swift Array Performance überarbeitet:

Ich habe meinen eigenen Benchmark geschrieben, der Swift mit C/Objective-C vergleicht. Mein Benchmark berechnet Primzahlen. Es verwendet das Array vorheriger Primzahlen, um in jedem neuen Kandidaten nach Primfaktoren zu suchen, also ist es ziemlich schnell. Es werden jedoch Tonnen von Arrays gelesen und weniger in Arrays geschrieben.

Ich habe diesen Benchmark ursprünglich gegen Swift 1.2 durchgeführt. Ich habe beschlossen, das Projekt zu aktualisieren und es unter Swift 2.0 auszuführen.

In diesem Projekt können Sie zwischen der Verwendung von normalen Swift - Arrays und der Verwendung von Swift - unsicheren Speicherpuffern mit Array-Semantik wählen.

Für C/Objective-C können Sie entweder NSArrays oder C-malloced Arrays verwenden.

Die Testergebnisse scheinen mit der schnellsten, kleinsten Codeoptimierung ([-0s]) oder der schnellsten, aggressiven ([-0fast]) Optimierung ziemlich ähnlich zu sein.

Die Leistung von Swift 2.0 ist bei deaktivierter Codeoptimierung immer noch schrecklich, während die Leistung von C/Objective-C nur mäßig langsamer ist.

Die Quintessenz ist, dass Array-basierte Berechnungen von C malloc mit bescheidenem Abstand die schnellsten sind

Bei Verwendung der schnellsten und kleinsten Codeoptimierung dauert ein schneller Vorgang mit unsicheren Puffern etwa 1,19 - 1,20-mal länger als bei C-malloc-Arrays. Bei einer schnellen, aggressiven Optimierung scheint der Unterschied etwas geringer zu sein (Swift dauert etwa 1,18x bis 1,16x länger als C.

Wenn Sie reguläre Swift - Arrays verwenden, ist der Unterschied zu C geringfügig größer. (Swift dauert ca. 1,22 bis 1,23 länger.)

Reguläre Swift Arrays sind DRAMATICALLY schneller als in Swift 1.2/Xcode 6. Ihre Leistung ist so nahe an Swift unsicheren pufferbasierten Arrays, dass unsichere Speicherpuffer dies nicht tun Scheint die Mühe wirklich nicht mehr wert zu sein, was groß ist.

Übrigens stinkt die Leistung von Objective-C NSArray. Wenn Sie die nativen Containerobjekte in beiden Sprachen verwenden möchten, ist Swift DRAMATISCH schneller.

Sie können mein Projekt auf Github unter SwiftPerformanceBenchmark auschecken

Es hat eine einfache Benutzeroberfläche, die das Sammeln von Statistiken ziemlich einfach macht.

Es ist interessant, dass die Sortierung in Swift etwas schneller zu sein scheint als in C, aber dass dieser Primzahlalgorithmus in Swift immer noch schneller ist.

11
Duncan C

Das Hauptproblem, das von anderen erwähnt, aber nicht ausreichend angesprochen wird, ist, dass -O3 überhaupt nichts in Swift tut (und dies niemals getan hat), wenn es damit kompiliert wird, ist es praktisch nicht optimiert (-Onone).

Optionsnamen haben sich im Laufe der Zeit geändert, sodass einige andere Antworten veraltete Flags für die Erstellungsoptionen aufweisen. Die korrekten aktuellen Optionen (Swift 2.2) sind:

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

Die gesamte Moduloptimierung ist langsamer kompiliert, kann jedoch dateiübergreifend innerhalb des Moduls optimiert werden, d. H. Innerhalb jedes Frameworks und innerhalb des tatsächlichen Anwendungscodes, jedoch nicht zwischen diesen. Sie sollten dies für alles verwenden, was für die Leistung kritisch ist.)

Sie können Sicherheitsüberprüfungen auch deaktivieren, um die Geschwindigkeit zu erhöhen. Dabei werden jedoch alle Behauptungen und Voraussetzungen nicht nur deaktiviert, sondern auf der Grundlage der Richtigkeit optimiert. Wenn Sie jemals auf eine Behauptung stoßen, bedeutet dies, dass Sie in ein undefiniertes Verhalten verfallen sind. Verwenden Sie es mit äußerster Vorsicht und nur, wenn Sie feststellen, dass sich die Geschwindigkeitssteigerung für Sie lohnt (durch Testen). Wenn Sie es für einen Code wertvoll finden, empfehle ich, diesen Code in ein separates Framework zu unterteilen und nur die Sicherheitsüberprüfungen für dieses Modul zu deaktivieren.

8
Joseph Lord
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) 

Dies ist mein Blog über Quick Sort- Github-Beispiel Quick-Sort

Informationen zum Partitionierungsalgorithmus von Lomuto finden Sie unter Partitionieren der Liste. Geschrieben in Swift

7
Abo3atef

Swift 4.1 führt den neuen Optimierungsmodus -Osize ein.

In Swift 4.1 unterstützt der Compiler jetzt einen neuen Optimierungsmodus, der dedizierte Optimierungen zur Reduzierung der Codegröße ermöglicht.

Der Compiler Swift enthält leistungsstarke Optimierungen. Beim Kompilieren mit -O versucht der Compiler, den Code so zu transformieren, dass er mit maximaler Leistung ausgeführt wird. Diese Verbesserung der Laufzeitleistung kann jedoch manchmal mit einem Kompromiss einer erhöhten Codegröße einhergehen. Mit dem neuen Optimierungsmodus -Osize hat der Benutzer die Wahl, die Codegröße auf ein Minimum zu beschränken und nicht auf eine maximale Geschwindigkeit.

Verwenden Sie -Osize anstelle von -O, um den Größenoptimierungsmodus in der Befehlszeile zu aktivieren.

Weitere Informationen: https://Swift.org/blog/osize/

4
casillas