Schnelle Beta-Leistung: Sortieren von Arrays

929

Ich habe einen Algorithmus in Swift Beta implementiert und festgestellt, dass die Leistung sehr schlecht war. Nachdem ich tiefer gegraben hatte, stellte ich fest, 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 ein ähnlicher Vorgang auf meinem Computer 0,06 Sekunden .

In Python dauert es 0,6 Sekunden (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 bis zu 88 Sekunden, 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.


Bearbeiten: Wetter bemerkte, dass das Ändern -O3in -Ofastdiesen Code fast so schnell laufen lässt wie die C ++ - Version! Allerdings -Ofaständert sich die Semantik der Sprache viel - in meinen Tests, es die Kontrollen für Integer - Überläufe und Array - Indizierung Überlauf deaktiviert . Mit -Ofastdem folgenden Swift-Code wird beispielsweise lautlos und ohne Absturz ausgeführt (und es wird etwas Müll ausgedruckt):

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

Also -Ofastist es nicht das, was wir wollen; Der springende Punkt bei Swift ist, dass wir die Sicherheitsnetze installiert 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 die Verlangsamung um einen Faktor von weniger als 2 beträgt. In Clang und GCC müssen wir -ftrapv(signierte) Ganzzahlüberläufe überprüfen, 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 mehr Benchmarking durchgeführt, mit sehr einfachen Schleifen 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 Assembler-Code leichter finden kann. Ich habe versucht, eine Operation auszuwählen, die leicht zu erkennen, aber auch "harmlos" ist, da keine Überprüfungen erforderlich sind zu ganzzahligen Überläufen.)

Auch hier gab es einen großen Unterschied in der Leistung zwischen -O3und -Ofast. Also habe ich mir den Assembler-Code angesehen:

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

  • Mit -O3bekomme ich etwas, das jenseits meiner wildesten Vorstellungskraft lag. Die innere Schleife umfasst 88 Zeilen Assembler-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 !


Bearbeiten 3: In Kommentaren fragte Ferruccio nach Benchmarks, 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, daher müssen wir uns keine Gedanken über ganzzahlige Überläufe machen. Das einzige, was wir tun, sind nur viele Array-Referenzen. Und die Ergebnisse sind da - Swift -O3 verliert im Vergleich zu -Ofast um einen Faktor von fast 500:

  • C ++ -O3: 0,05 s
  • C ++ -O0: 0,4 s
  • Java: 0,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, dass der Compiler die sinnlosen Schleifen vollständig optimiert, können Sie sie in z. B. ändern x[i] ^= x[j]und eine print-Anweisung hinzufügen x[0], die ausgegeben wird. Dies ändert nichts; die Timings sind sehr ähnlich.)

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


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

Zum Sortieren habe ich jetzt folgende Timings:

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

Für verschachtelte Schleifen:

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

Es scheint, dass es keinen Grund mehr gibt, das unsichere -Ofast(aka -Ounchecked) zu verwenden; plain -Oerzeugt gleich guten Code.

Jukka Suomela
quelle
20
Hier ist eine weitere "Swift 100-mal langsamer als C" -Frage
Jukka Suomela
16
Und hier ist eine Diskussion über Apples Marketingmaterial im Zusammenhang mit Swifts guter
Sortierleistung
2
Sie können kompilieren mit : xcrun --sdk macosx swift -O3. Es ist kürzer.
Southern Hospitality
3
Dieser Link zeigt einige andere grundlegende Operationen im Vergleich zu Objective-C.
Wold
4
Mit Beta 5 hat sich die Geschwindigkeit von Swift erheblich verbessert - siehe diesen Beitrag von Jesse Squires für weitere Details.
Nate Cook

Antworten:

460

tl; dr Swift 1.0 ist nach diesem Benchmark mit der Standardoptimierungsstufe [-O] jetzt so schnell wie C.


Hier ist eine In-Place-Quicksortierung 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_000 :

Swift:            0.895296452
C:                0.001223848

Hier ist Swifts eingebaute sort () für n = 10_000 :

Swift_builtin:    0.77865783

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

Swift:            0.045478346
C:                0.000784666
Swift_builtin:    0.032513488

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

Wie pro mweathers' Antwort , Einstellung [-Ofast] macht den wirklichen Unterschied, in diesen Zeiten resultierende für n = 10_000 :

Swift:            0.000706745
C:                0.000742374
Swift_builtin:    0.000603576

Und für n = 1_000_000 :

Swift:            0.107111846
C:                0.114957179
Swift_sort:       0.092688548

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

Swift:            142.659763258
C:                0.162065333
Swift_sort:       114.095478272

Daher war Swift ohne Optimierungen in diesem Benchmark zu diesem Zeitpunkt seiner Entwicklung fast 1000-mal langsamer als C. Auf der anderen Seite lief Swift mit beiden auf [-Ofast] eingestellten Compilern mindestens genauso gut, wenn nicht sogar etwas besser als C.

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

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. Es kann zu signifikanten Hochleistungsgewinnen des Compilers führen.

Sie alle befürworten es fast. Ob das klug ist oder nicht, kann ich nicht sagen, aber soweit ich das beurteilen kann, erscheint es vernünftig genug, [-Ofast] in einer Version zu verwenden, wenn Sie keine hochpräzise Gleitkomma-Arithmetik ausführen und sicher sind, keine Ganzzahl oder Array-Überläufe sind in Ihrem Programm möglich. Wenn Sie Hochleistungs- und Überlaufprüfungen / präzise Arithmetik benötigen, wählen Sie vorerst eine andere Sprache.

BETA 3 UPDATE:

n = 10_000 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 die integrierte Sortierung von Swift erheblich geändert hat.

ENDGÜLTIGES UPDATE:

[-Onone] :

Swift:   0.678056695
C:       0.000973914

[-O] :

Swift:   0.001158492
C:       0.001192406

[-Ungeprüft] :

Swift:   0.000827764
C:       0.001078914
Joseph Mark
quelle
25
Die Verwendung von -emit-sil zur Ausgabe des SIL-Zwischencodes zeigt an, was beibehalten wird (argh, ein Stapelüberlauf macht das Formatieren unmöglich). Es ist ein internes Pufferobjekt im Array. Dies klingt definitiv nach einem Optimierungsfehler. Der ARC-Optimierer sollte in der Lage sein, die Retains ohne -Ofast zu entfernen.
Catfish_Man
Ich bin nur anderer Meinung, dass wir eine andere Sprache verwenden müssen, wenn wir Ofast-Optimierungen verwenden möchten. Ähnlich muss es mit der Frage der Grenzüberprüfung und anderer kleinerer Probleme umgehen, wenn eine andere Sprache wie C gewählt wird. Der Swift ist cool, gerade weil er standardmäßig sicher und optional schnell und bei Bedarf unsicher sein soll. Auf diese Weise kann der Programmierer auch Ihren Code debuggen, um sicherzustellen, dass alles in Ordnung ist, und mit Ofast kompilieren. Die Möglichkeit, moderne Standards zu verwenden und dennoch die Macht einer "unsicheren" Sprache wie C zu haben, ist sehr cool.
Wallacy
2
Wenn Sie mir sagen können, wie es ungültig sein könnte, tun Sie es bitte. Ich lerne immer gerne mehr
Joseph Mark
3
Swift wurde nach diesem Benchmark mit Standardoptimierungen so schnell wie C aktualisiert.
Joseph Mark
4
Tipp: Sowohl Ihre Swift- als auch Ihre C-Implementierung von Quicksort können verbessert werden, wenn Sie zuerst auf die kleinste Partition zurückgreifen ! (Anstatt immer zuerst auf der linken Partition zu rekursieren.) Quicksort, das im schlimmsten Fall mit einer einfachen Pivot-Auswahl implementiert wird, benötigt O (n ^ 2) Zeit, aber selbst in diesem schlimmsten Fall benötigen Sie nur O (log n) -Stack-Speicherplatz durch Rekursion auf der kleineren Partition zuerst.
Macneil Shonle
108

TL; DR : Ja, die einzige Swift Sprache Implementierung ist langsam, gerade jetzt . Wenn Sie schnellen, numerischen Code (und vermutlich auch andere Codetypen) benötigen, wählen Sie einfach einen anderen. In Zukunft sollten Sie Ihre Wahl neu bewerten. Es könnte jedoch für die meisten Anwendungscodes gut genug sein, die auf einer höheren Ebene geschrieben sind.

Nach dem, was ich in SIL und LLVM IR sehe, scheinen sie eine Reihe von Optimierungen zum Entfernen von Aufbewahrungen und Releases zu benötigen, die möglicherweise in Clang (für Objective-C) implementiert sind , aber noch nicht portiert wurden. Das ist die Theorie, mit der ich gehe (vorerst… ich muss noch bestätigen, dass Clang etwas dagegen unternimmt), da ein Profiler, der den letzten Testfall dieser Frage ausführt, dieses „hübsche“ Ergebnis liefert:

Zeitprofilierung auf -O3 Zeitprofilierung auf -Ofast

Wie von vielen anderen gesagt, -Ofastist völlig unsicher und ändert die Sprachsemantik. Für mich ist es in der Phase "Wenn Sie das verwenden wollen, verwenden Sie einfach eine andere Sprache". Ich werde diese Auswahl später neu bewerten, wenn sie sich ändert.

-O3holt uns ein paar swift_retainund swift_releasenennt das ehrlich gesagt nicht so, als ob sie für dieses Beispiel da sein sollten. Der Optimierer sollte (die meisten) AFAICT eliminiert haben, da er die meisten Informationen über das Array kennt und weiß, dass es (zumindest) einen starken Bezug dazu hat.

Es sollte keine weiteren Retains ausgeben, wenn nicht einmal Funktionen aufgerufen werden, 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 nutzlos sind. Er weiß auch , dass die ganze Zahl wird nie über 10k sein, so dass die Überlaufprüfungen können (nicht wegen optimiert werden -OfastSeltsamkeit, sondern wegen der Semantik der Sprache (nichts anderes , dass var verändert sich noch darauf zugreifen können, und das Hinzufügen von bis zu 10k ist sicher für den Typ Int).

Der Compiler kann das Array oder die Array-Elemente möglicherweise nicht entpacken, da sie an übergeben werden. Dies sort()ist eine externe Funktion und muss die erwarteten Argumente abrufen. Dadurch müssen wir die IntWerte indirekt verwenden, wodurch es etwas langsamer wird. Dies könnte sich ändern, wenn die sort()generische Funktion (nicht auf Multi-Methoden-Weise) dem Compiler zur Verfügung stand und inline wurde.

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

Verwendeter Code:

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

Filcab
quelle
Der Compiler ist jedoch möglicherweise nicht in der Lage, das Array oder die Array-Elemente zu entpacken, da sie an sort () übergeben werden, eine externe Funktion, die die erwarteten Argumente abrufen muss. Für einen relativ guten Compiler sollte das keine Rolle spielen. Übergeben von Metadaten (im Zeiger - 64 Bit bieten viel Deich) über die tatsächlichen Daten und Verzweigen in der aufgerufenen Funktion.
Bests
3
Was genau macht -Ofast"total unsicher"? Angenommen, Sie wissen, wie Sie Ihren Code testen und Überläufe ausschließen.
Joseph Mark
@sjeohp: Das setzt eigentlich viel voraus :-) Es ist schwierig, den Code zu überprüfen und Überläufe auszuschließen. Aus meiner Erfahrung (ich mache Compilerarbeit und habe einige große Codebasen überprüft) und was ich von Leuten gehört habe, die Compilerarbeit in großen Unternehmen leisten, ist es schwierig , Überläufe und anderes undefiniertes Verhalten richtig zu machen . Sogar Apples Rat (nur ein Beispiel) zur Behebung von UB ist manchmal falsch ( randomascii.wordpress.com/2014/04/17/… ). -Ofaständert auch die Sprachsemantik, aber ich kann keine Dokumente dafür finanzieren. Wie können Sie sicher sein, dass Sie wissen, was es tut?
Filcab
@bestsss: Es ist möglich, aber möglicherweise nicht nützlich. Es fügt Überprüfungen bei jedem Zugriff auf ein Int [] hinzu. Es hängt davon ab, ob Arrays von Int und einige andere primitive Typen (Sie haben höchstens 3 Bits) häufig verwendet werden (insbesondere, wenn Sie bei Bedarf auf C senken können). Es verbraucht auch einige Bits, die sie möglicherweise verwenden möchten, wenn sie schließlich Nicht-ARC-GC hinzufügen möchten. Es lässt sich auch nicht auf Generika mit mehr als einem Argument skalieren. Da sie alle Typen haben, wäre es viel einfacher, den gesamten Code, der Int [] berührt hat (aber nicht Int? []), Auf die Verwendung von Inline-Int zu spezialisieren. Aber dann müssen Sie sich um Obj-C Interop Sorgen machen.
Filcab
@filcab, Nicht-ARC-GC (dh echte GC) wäre tatsächlich nützlich, aber sie benötigen etwas, das nicht C-kompatibel ist, wenn sie eine wirklich gleichzeitige Nicht-STW-GC wünschen. Ich würde mir keine Sorgen um "jeden Zugriff auf Int[]" machen, da dies von der Ebene abhängt, die der Compiler einbinden kann, und er sollte in der Lage sein, die engen Schleifen mit / nach einer gewissen Anleitung einzubinden.
Bests
53

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

Schnell

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

Es scheint die gleiche Leistung zu sein, wenn ich mit kompiliere -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

Es scheint eine Leistungsregression von Swift 2.0 auf Swift 3.0 gegeben zu haben, und ich sehe auch einen Unterschied zwischen -Ound -Ouncheckedzum ersten Mal.

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 verbessert die Leistung erneut und behält gleichzeitig eine Lücke zwischen -Ound bei -Ounchecked. -O -whole-module-optimizationschien 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.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

Urteil

Zum Zeitpunkt dieses Schreibens ist Swifts Sortierung schnell, aber noch nicht so schnell wie die Sortierung von C ++, wenn sie -Omit den oben genannten Compilern und Bibliotheken kompiliert wird . Mit -Ouncheckedscheint es so schnell wie C ++ in Swift 4.0.2 und Apple LLVM 9.0.0 zu sein.

Lernen Sie OpenGL ES
quelle
2
In Wirklichkeit sollten Sie vector :: Reserve () niemals aufrufen, bevor Sie zehn Millionen Elemente eingefügt haben.
BJovke
Vielleicht! Derzeit wird nur die Sortierung zeitlich festgelegt.
Lernen Sie OpenGL ES
34

Von The Swift Programming Language:

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

Die sortFunktion 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 "fest codiert 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 Verschluss getestet, damit ich die Funktion etwas genauer überwachen kann, und festgestellt, dass der Verschluss bei n auf 1000 ungefähr 11.000 Mal aufgerufen wurde.

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.

BEARBEITEN:

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
}

Als ich dies mit n = 1000 verwendete, fand ich das

  1. quickSort () wurde ungefähr 650 Mal aufgerufen,
  2. es wurden ungefähr 6000 Swaps gemacht,
  3. und es gibt ungefähr 10.000 Vergleiche

Es scheint, dass die eingebaute Sortiermethode eine schnelle Sortierung ist (oder nahe daran liegt) und sehr langsam ist ...

David Skrundz
quelle
17
Vielleicht irre ich mich völlig, aber laut en.wikipedia.org/wiki/Quicksort beträgt die durchschnittliche Anzahl der Vergleiche in Quicksort 2*n*log(n). Das sind 13815 Vergleiche zum Sortieren von n = 1000 Elementen. Wenn die Vergleichsfunktion also ungefähr 11000 Mal aufgerufen wird, scheint dies nicht so schlecht zu sein.
Martin R
6
Auch Apple behauptete, dass eine "komplexe Objektsortierung" (was auch immer das ist) in Swift 3,9-mal schneller ist als in Python. Daher sollte es nicht notwendig sein, eine "bessere Sortierfunktion" zu finden. - Aber Swift ist noch in der Entwicklung ...
Martin R
6
Es bezieht sich auf den natürlichen Logarithmus.
Martin R
24
log(n)für algorithmische Komplexität bezieht sich herkömmlicherweise auf log base-2. Der Grund dafür, dass die Basis nicht angegeben wird, besteht darin, dass das Basisänderungsgesetz für Logarithmen nur einen konstanten Multiplikator einführt, der für die Zwecke der O-Notation verworfen wird.
Minuteman3
3
In Bezug auf die Diskussion über den natürlichen Logarithmus im Vergleich zum Logarithmus zur Basis 2: Die genaue Aussage auf der Wikipedia-Seite lautet, dass die durchschnittliche Anzahl der für n Elemente erforderlichen Vergleiche beträgt C(n) = 2n ln n ≈ 1.39n log₂ n. Für n = 1000 ergibt dies C (n) = 13815, und es ist keine "Big-O-Notation".
Martin R
18

Ab Xcode 7 können Sie einschalten Fast, Whole Module Optimization. Dies sollte Ihre Leistung sofort steigern.

Geben Sie hier die Bildbeschreibung ein

Antoine
quelle
12

Die Leistung von Swift Array wurde ü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, daher 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 gegen Swift 2.0 auszuführen.

Im Projekt können Sie zwischen der Verwendung normaler schneller Arrays und der Verwendung unsicherer Swift-Speicherpuffer mithilfe der Array-Semantik wählen.

Für C / Objective-C können Sie entweder NSArrays oder C malloc'ed 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.

Das Fazit ist, dass C malloc'd Array-basierte Berechnungen mit bescheidenem Abstand am schnellsten sind

Swift mit unsicheren Puffern dauert etwa 1,19X - 1,20X länger als C malloc-Arrays, wenn die schnellste und kleinste Codeoptimierung verwendet wird. Bei schneller, aggressiver Optimierung scheint der Unterschied etwas geringer zu sein (Swift dauert eher 1,18x bis 1,16x länger als C.

Wenn Sie normale Swift-Arrays verwenden, ist der Unterschied zu C etwas größer. (Swift dauert ~ 1,22 bis 1,23 länger.)

Normale Swift-Arrays sind DRAMATICALLYschneller als in Swift 1.2 / Xcode 6. Ihre Leistung kommt Swift-Arrays auf der Basis unsicherer Puffer so nahe, dass die Verwendung unsicherer Speicherpuffer die Mühe nicht mehr wert zu sein scheint, was sehr 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 bei SwiftPerformanceBenchmark überprüfen

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

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

Duncan C.
quelle
8

Das Hauptproblem, das von anderen erwähnt, aber nicht genug angesprochen wird, ist, dass -O3es in Swift überhaupt nichts tut (und es nie getan hat). Wenn es damit kompiliert wird, ist es effektiv nicht optimiert ( -Onone).

Optionsnamen haben sich im Laufe der Zeit geändert, sodass einige andere Antworten veraltete Flags für die Build-Optionen haben. Richtige aktuelle Optionen (Swift 2.2) sind:

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

Die gesamte Moduloptimierung hat eine langsamere Kompilierung, kann jedoch über Dateien innerhalb des Moduls hinweg optimiert werden, dh innerhalb jedes Frameworks und innerhalb des tatsächlichen Anwendungscodes, jedoch nicht zwischen diesen. Sie sollten dies für alles verwenden, was leistungskritisch ist.

Sie können Sicherheitsüberprüfungen auch für noch mehr Geschwindigkeit deaktivieren, wobei jedoch alle Aussagen und Voraussetzungen nicht nur deaktiviert, sondern auf der Grundlage ihrer Richtigkeit optimiert werden. Wenn Sie jemals eine Behauptung treffen, bedeutet dies, dass Sie sich in undefiniertem Verhalten befinden. 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.

Joseph Lord
quelle
Diese Antwort ist jetzt veraltet. Ab Swift 4.1 ist die gesamte Moduloptimierungsoption ein separater Boolescher Wert, der mit anderen Einstellungen kombiniert werden kann, und es gibt jetzt ein -Os, um die Größe zu optimieren. Ich kann aktualisieren, wenn ich Zeit habe, die genauen Optionsflags zu überprüfen.
Joseph Lord
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) 

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

Sie können sich den Partitionierungsalgorithmus von Lomuto unter Partitionieren der Liste ansehen. Geschrieben in Swift.

Abo3atef
quelle
4

Swift 4.1 führt den neuen -OsizeOptimierungsmodus ein.

In Swift 4.1 unterstützt der Compiler jetzt einen neuen Optimierungsmodus, der dedizierte Optimierungen ermöglicht, um die Codegröße zu reduzieren.

Der Swift-Compiler verfügt über 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, für eine minimale Codegröße und nicht für eine maximale Geschwindigkeit zu kompilieren.

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

Lesen Sie weiter: https://swift.org/blog/osize/

Casillas
quelle