Swift Beta performance: array di ordinamento


928

Stavo implementando un algoritmo in Swift Beta e ho notato che le prestazioni erano molto scarse. Dopo aver scavato più a fondo mi sono reso conto che uno dei colli di bottiglia era qualcosa di semplice come gli array di ordinamento. La parte pertinente è qui:

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 ++, un'operazione simile richiede 0.06s sul mio computer.

In Python sono necessari 0,6 s (nessun trucco, solo y = ordinato (x) per un elenco di numeri interi).

In Swift ci vogliono 6 secondi se lo compilo con il seguente comando:

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

E ci vogliono fino agli 88 se lo compilo con il seguente comando:

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

I tempi in Xcode con build "Release" vs. "Debug" sono simili.

Cosa c'è che non va qui? Potrei capire alcune perdite di prestazioni rispetto a C ++, ma non un rallentamento di 10 volte rispetto a Python puro.


Modifica: il meteo ha notato che il passaggio -O3a -Ofastquesto codice viene eseguito quasi alla stessa velocità della versione C ++! Tuttavia, -Ofastcambia molto la semantica della lingua: nei miei test ha disabilitato i controlli per overflow di interi e overflow di indicizzazione di array . Ad esempio, con -Ofastil seguente codice Swift viene eseguito silenziosamente senza arresti anomali (e stampa alcuni rifiuti):

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

Quindi -Ofastnon è quello che vogliamo; il punto centrale di Swift è che abbiamo le reti di sicurezza in atto. Naturalmente, le reti di sicurezza hanno un certo impatto sulle prestazioni, ma non dovrebbero rallentare i programmi 100 volte. Ricorda che Java controlla già i limiti dell'array e, in casi tipici, il rallentamento è di un fattore molto inferiore a 2. E in Clang e GCC abbiamo il -ftrapvcontrollo degli overflow di numeri interi (firmati) e non è neanche così lento.

Da qui la domanda: come possiamo ottenere prestazioni ragionevoli in Swift senza perdere le reti di sicurezza?


Modifica 2: ho fatto un po 'più di benchmarking, con loop molto semplici lungo la linea di

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

(Qui l'operazione xor è lì solo per poter trovare più facilmente il loop pertinente nel codice assembly. Ho cercato di scegliere un'operazione che sia facile da individuare ma anche "innocua", nel senso che non dovrebbe richiedere alcun controllo relativo a overflow di numeri interi.)

Ancora una volta, c'è stata un'enorme differenza nelle prestazioni tra -O3e -Ofast. Quindi ho dato un'occhiata al codice assembly:

  • Con -Ofastottengo praticamente quello che mi aspetterei. La parte pertinente è un loop con 5 istruzioni in linguaggio macchina.

  • Con -O3ottengo qualcosa che andava oltre la mia più sfrenata immaginazione. Il ciclo interno comprende 88 righe di codice assembly. Non ho provato a capirlo tutto, ma le parti più sospette sono 13 invocazioni di "callq _swift_retain" e altre 13 invocazioni di "callq _swift_release". Cioè, 26 chiamate di subroutine nel loop interno !


Modifica 3: Nei commenti, Ferruccio ha chiesto parametri di riferimento equi, nel senso che non si basano su funzioni integrate (ad es. Ordinamento). Penso che il seguente programma sia un buon esempio:

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

Non esiste aritmetica, quindi non dobbiamo preoccuparci di overflow di numeri interi. L'unica cosa che facciamo è solo un sacco di riferimenti di array. E i risultati sono qui: Swift -O3 perde di un fattore quasi 500 rispetto a -Ofast:

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

(Se sei preoccupato che il compilatore possa ottimizzare completamente i loop inutili, puoi cambiarlo in ad es x[i] ^= x[j]. E aggiungere un'istruzione di stampa che produca x[0]. Questo non cambia nulla; i tempi saranno molto simili.)

E sì, qui l'implementazione di Python era una stupida implementazione pura di Python con un elenco di ints e annidato per loop. Dovrebbe essere molto più lento di Swift non ottimizzato. Qualcosa sembra seriamente rotto con Swift e l'indicizzazione dell'array.


Modifica 4: questi problemi (così come altri problemi relativi alle prestazioni) sembrano essere stati risolti in Xcode 6 beta 5.

Per l'ordinamento, ora ho i seguenti tempi:

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

Per i loop nidificati:

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

Sembra che non ci sia più motivo di usare il non sicuro -Ofast(aka -Ounchecked); plain -Oproduce ugualmente un buon codice.


20
Qui è un altro "Swift 100 volte più lento di C" domanda: stackoverflow.com/questions/24102609/...
Jukka Suomela

16
Ed ecco la discussione sul materiale di marketing di Apple relativo alla buona performance di Swift nell'ordinamento: programmers.stackexchange.com/q/242816/913
Jukka Suomela

2
È possibile compilare con: xcrun --sdk macosx swift -O3. È più corto
Southern Hospitality,

3
Questo link mostra alcune altre operazioni di base rispetto a Objective-C.
Wold,

4
Con Beta 5 c'è stato un sostanziale miglioramento della velocità di Swift - vedi questo post di Jesse Squires per maggiori dettagli.
Nate Cook,

Risposte:


460

tl; dr Swift 1.0 è ora più veloce di C da questo benchmark usando il livello di ottimizzazione di rilascio predefinito [-O].


Ecco un quicksort sul posto 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)
}

E lo stesso 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);
}

Entrambi funzionano:

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]

Entrambi sono chiamati nello stesso programma come scritto.

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

Questo converte i tempi assoluti in secondi:

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

Ecco un riepilogo dei livelli di ottimizzazione del compilatore:

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

Tempo in secondi con [-Onone] per n = 10_000 :

Swift:            0.895296452
C:                0.001223848

Ecco l'ordinamento incorporato di Swift () per n = 10_000 :

Swift_builtin:    0.77865783

Ecco [-O] per n = 10_000 :

Swift:            0.045478346
C:                0.000784666
Swift_builtin:    0.032513488

Come puoi vedere, le prestazioni di Swift sono migliorate di un fattore 20.

Secondo la risposta di mweathers , l'impostazione di [-Ofast] fa la vera differenza, risultando in questi tempi per n = 10_000 :

Swift:            0.000706745
C:                0.000742374
Swift_builtin:    0.000603576

E per n = 1_000_000 :

Swift:            0.107111846
C:                0.114957179
Swift_sort:       0.092688548

Per confronto, questo è con [-Onone] per n = 1_000_000 :

Swift:            142.659763258
C:                0.162065333
Swift_sort:       114.095478272

Quindi Swift senza ottimizzazioni era quasi 1000 volte più lento di C in questo benchmark, in questa fase del suo sviluppo. D'altra parte con entrambi i compilatori impostati su [-Ofast] Swift ha effettivamente funzionato almeno se non leggermente migliore di C.

È stato sottolineato che [-Ofast] cambia la semantica della lingua, rendendola potenzialmente non sicura. Questo è ciò che Apple afferma nelle note di rilascio di Xcode 5.0:

Un nuovo livello di ottimizzazione -Ofast, disponibile in LLVM, consente ottimizzazioni aggressive. -Ofast rilassa alcune restrizioni conservative, principalmente per le operazioni in virgola mobile, che sono sicure per la maggior parte del codice. Può produrre significative vittorie ad alte prestazioni dal compilatore.

Lo sostengono tutti. Che sia saggio o no non potrei dirlo, ma da quello che posso dire sembra abbastanza ragionevole usare [-Ofast] in una versione se non stai facendo l'aritmetica in virgola mobile ad alta precisione e non sei sicuro che non ci siano numeri interi o overflow dell'array sono possibili nel tuo programma. Se hai bisogno di controlli ad alte prestazioni e overflow / aritmetica precisa, scegli un'altra lingua per ora.

AGGIORNAMENTO BETA 3:

n = 10_000 con [-O] :

Swift:            0.019697268
C:                0.000718064
Swift_sort:       0.002094721

Swift in generale è un po 'più veloce e sembra che l'ordinamento incorporato di Swift sia cambiato in modo abbastanza significativo.

AGGIORNAMENTO FINALE:

[-Onone] :

Swift:   0.678056695
C:       0.000973914

[-O] :

Swift:   0.001158492
C:       0.001192406

[-Ounchecked] :

Swift:   0.000827764
C:       0.001078914

25
L'uso di -emit-sil per generare il codice SIL intermedio mostra ciò che viene mantenuto (argh, lo stack overflow sta rendendo impossibile la formattazione). È un oggetto buffer interno nell'array. Questo suona sicuramente come un bug dell'ottimizzatore, l'ottimizzatore ARC dovrebbe essere in grado di rimuovere i blocchi senza -Fast.
Catfish_Man,

Non sarò d'accordo sul fatto che dobbiamo usare un'altra lingua se vogliamo usare le ottimizzazioni di Ofast. Dovrà affrontare in modo simile la questione dei controlli dei limiti e di altri problemi minori se si sceglie un'altra lingua come C. Il rapido è interessante proprio perché deve essere sicuro di default e facoltativamente veloce e insicuro se necessario. Ciò consente al programmatore di eseguire il debug anche del codice, per assicurarsi che tutto sia a posto e compilare utilizzando Ofast. La possibilità di utilizzare standard moderni e tuttavia avere il potere di un linguaggio "non sicuro" come C è molto interessante.
Wallacy,

2
se puoi dirmi come potrebbe non essere valido, ti preghiamo di farlo. mi piace sempre saperne di più
Joseph Mark,

3
fatto un aggiornamento finale, Swift ora è veloce come C da questo benchmark usando ottimizzazioni standard.
Joseph Mark,

4
Suggerimento: Entrambe le implementazioni Swift e C di quicksort possono essere migliorate se si esegue prima la recurse sulla partizione più piccola ! (Invece di ricorrere sulla partizione sinistra sempre per primo.) Quicksort implementato con una semplice selezione pivot nel caso peggiore richiede tempo O (n ^ 2), ma anche nel caso peggiore è necessario solo lo spazio dello stack O (log n) ricorrendo nuovamente prima sulla partizione più piccola.
Macneil Shonle,

108

TL; DR : Sì, l'unica implementazione del linguaggio Swift è lenta, in questo momento . Se hai bisogno di un codice veloce, numerico (e altri tipi di codice, presumibilmente), basta andare con un altro. In futuro, dovresti rivalutare la tua scelta. Tuttavia, potrebbe essere abbastanza buono per la maggior parte del codice dell'applicazione scritto a un livello superiore.

Da quello che sto vedendo in SIL e LLVM IR, sembra che abbiano bisogno di un sacco di ottimizzazioni per rimuovere i fermi e le versioni, che potrebbero essere implementati in Clang (per Objective-C), ma non li hanno ancora portati. Questa è la teoria con cui sto andando (per ora ... devo ancora confermare che Clang fa qualcosa al riguardo), dal momento che un profiler eseguito sull'ultimo test-case di questa domanda produce questo risultato "carino":

Profilazione temporale su -O3 Profilazione temporale su -Ofast

Come è stato detto da molti altri, -Ofastè totalmente insicuro e cambia la semantica della lingua. Per me, è allo stadio "Se hai intenzione di usarlo, usa solo un'altra lingua". Riconsidererò quella scelta in seguito, se cambia.

-O3ci dà un sacco di swift_retaine swift_releasechiama che, onestamente, non sembra che dovrebbero essere lì per questo esempio. L'ottimizzatore avrebbe dovuto eludere (la maggior parte) di AFAICT, poiché conosce la maggior parte delle informazioni sull'array e sa che ha (almeno) un forte riferimento ad esso.

Non dovrebbe emettere più risorse quando non è nemmeno chiamata funzioni che potrebbero rilasciare gli oggetti. Non credo che un costruttore di array possa restituire un array più piccolo di quanto richiesto, il che significa che molti controlli che sono stati emessi sono inutili. Sa anche che l'intero non sarà mai superiore a 10k, quindi i controlli di overflow possono essere ottimizzati (non a causa della -Ofaststranezza, ma a causa della semantica della lingua (nient'altro sta cambiando quel var né può accedervi, e aggiungendo fino a 10k è sicuro per il tipo Int).

Il compilatore potrebbe non essere in grado di decomprimere l'array o gli elementi dell'array, tuttavia, poiché vengono passati a sort(), che è una funzione esterna e deve ottenere gli argomenti che si aspetta. Questo ci costringerà a usare i Intvalori indirettamente, il che renderebbe un po 'più lento. Ciò potrebbe cambiare se la sort()funzione generica (non in modalità multi-metodo) fosse disponibile per il compilatore e venisse incorporata.

Questa è una lingua (pubblicamente) molto nuova, e sta attraversando ciò che presumo siano molti cambiamenti, dal momento che ci sono persone (fortemente) coinvolte nella lingua Swift che chiedono feedback e tutti dicono che la lingua non è finita e lo farà modificare.

Codice utilizzato:

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: Non sono un esperto di Objective-C né di tutte le strutture di Cocoa , Objective-C o dei runtime Swift. Potrei anche supporre alcune cose che non ho scritto.


Il compilatore potrebbe non essere in grado di decomprimere l'array o gli elementi dell'array, tuttavia, poiché vengono passati a sort (), che è una funzione esterna e deve ottenere gli argomenti che si aspetta. Ciò non dovrebbe importare a un compilatore relativamente buono. Passare i metadati (nel puntatore - 64 bit offre molti livelli) sui dati effettivi e ramificarli nella funzione chiamata.
bestsss

3
Cosa rende esattamente -Ofast"totalmente insicuro"? Supponendo che tu sappia come testare il tuo codice ed escludere gli overflow.
Joseph Mark,

@sjeohp: In realtà sta assumendo molto :-) Controllare il codice ed escludere gli overflow è difficile da fare. In base alla mia esperienza (lavoro sul compilatore e ho verificato alcune grandi basi di codice), e quello che ho sentito dalle persone che lavorano al compilatore su grandi aziende, ottenendo overflow e altri comportamenti indefiniti, è difficile . Anche il consiglio di Apple (solo un esempio) sulla correzione di UB è errato, a volte ( randomascii.wordpress.com/2014/04/17/… ). -Ofastcambia anche la semantica della lingua, ma non posso finanziare alcun documento per questo. Come puoi essere sicuro di sapere cosa sta facendo?
Filcab,

@bestsss: è possibile, ma potrebbe non essere utile. Aggiunge controlli su ogni accesso a un Int []. Dipende se le matrici di Int e alcuni altri tipi primitivi (hai, al massimo, 3 bit) sono usati molto (specialmente quando puoi abbassare a C se necessario). Utilizza anche alcuni bit che potrebbero voler usare se, eventualmente, volessero aggiungere GC non ARC. Non si adatta ai generici con più di un argomento, neanche. Dal momento che hanno tutti i tipi, sarebbe molto più facile specializzare tutto il codice che toccava Int [] (ma non Int? []) Per usare Int. Ma poi devi preoccuparti dell'interoperabilità di Obj-C.
Filcab,

@filcab, GC non ARC (cioè reale) sarebbe effettivamente utile, ma hanno bisogno di qualcosa che non sia compatibile con C se vogliono un GC realmente non simultaneo, non STW. Non mi preoccuperei di "ogni accesso a Int[]" poiché dipende dal livello in cui il compilatore può essere in linea e dovrebbe essere in grado di incorporare i circuiti stretti con / dopo alcune indicazioni.
bestsss

53

Ho deciso di dare un'occhiata a questo per divertimento, e qui ci sono i tempi che ottengo:

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

veloce

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

risultati:

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

Sembra essere la stessa prestazione se compilo -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

Sembra che ci sia stata una regressione delle prestazioni da Swift 2.0 a Swift 3.0, e vedo anche una differenza tra -Oe -Ouncheckedper la prima volta.

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 migliora di nuovo le prestazioni, mantenendo un divario tra -Oe -Ounchecked. -O -whole-module-optimizationnon sembrava fare la differenza.

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

risultati:

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

Verdetto

Al momento della stesura di questo articolo, l'ordinamento di Swift è veloce, ma non ancora veloce come quello di C ++ quando compilato -O, con i compilatori e le librerie di cui sopra. Con -Ounchecked, sembra essere veloce come C ++ in Swift 4.0.2 e Apple LLVM 9.0.0.


2
In realtà non dovresti mai chiamare vector :: reserve () prima di inserire dieci milioni di elementi.
Giovanni il

Forse! Al momento è in corso solo l'ordinamento.
Impara OpenGL ES il

34

Da The Swift Programming Language:

La libreria standard della funzione di ordinamento di Swift fornisce una funzione chiamata ordinamento, che ordina una matrice di valori di tipo noto, in base all'output di una chiusura di ordinamento fornita. Una volta completato il processo di ordinamento, la funzione di ordinamento restituisce un nuovo array dello stesso tipo e dimensione di quello precedente, con i suoi elementi nell'ordinamento corretto.

La sortfunzione ha due dichiarazioni.

La dichiarazione predefinita che consente di specificare una chiusura di confronto:

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

E una seconda dichiarazione che accetta solo un singolo parametro (l'array) ed è "hardcoded per usare il comparatore meno di".

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

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

Ho testato una versione modificata del tuo codice in un parco giochi con la chiusura aggiunta in modo da poter monitorare un po 'più da vicino la funzione, e ho scoperto che con n impostato su 1000, la chiusura veniva chiamata circa 11.000 volte.

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

Non è una funzione efficiente, raccomanderei di utilizzare un'implementazione della funzione di ordinamento migliore.

MODIFICARE:

Ho dato un'occhiata alla pagina di Wikipedia Quicksort e ho scritto un'implementazione Swift per questo. Ecco il programma completo che ho usato (in un parco giochi)

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
}

Usando questo con n = 1000, l'ho trovato

  1. quickSort () è stato chiamato circa 650 volte,
  2. sono stati effettuati circa 6000 swap,
  3. e ci sono circa 10.000 confronti

Sembra che il metodo di ordinamento incorporato sia (o sia vicino) un ordinamento rapido ed è molto lento ...


17
Forse mi sbaglio completamente, ma secondo en.wikipedia.org/wiki/Quicksort , il numero medio di confronti in Quicksort è 2*n*log(n). Questo è 13815 confronti per l'ordinamento di n = 1000 elementi, quindi se la funzione di confronto viene chiamata circa 11000 volte non sembra così male.
Martin R,

6
Inoltre, Apple ha affermato che un "ordinamento di oggetti complessi" (qualunque cosa sia) è 3,9 volte più veloce in Swift che in Python. Pertanto, non dovrebbe essere necessario trovare una "migliore funzione di ordinamento". - Ma Swift è ancora in fase di sviluppo ...
Martin R

6
Si fa riferimento al logaritmo naturale.
Martin R,

24
log(n)per complessità algoritmica si riferisce convenzionalmente a log base-2. La ragione per non dichiarare la base è che la legge del cambio di base per i logaritmi introduce solo un moltiplicatore costante, che viene scartato ai fini della notazione O.
minuteman3,

3
Per quanto riguarda la discussione sul logaritmo naturale contro il logaritmo di base 2: l'affermazione precisa della pagina di Wikipedia è che il numero medio di confronti necessari per n elementi è C(n) = 2n ln n ≈ 1.39n log₂ n. Per n = 1000 questo dà C (n) = 13815, ed è non è una "notazione O-grande".
Martin R

18

A partire da Xcode 7 puoi accenderlo Fast, Whole Module Optimization. Questo dovrebbe aumentare immediatamente le tue prestazioni.

inserisci qui la descrizione dell'immagine


12

Prestazioni di Swift Array rivisitate:

Ho scritto il mio benchmark confrontando Swift con C / Objective-C. Il mio benchmark calcola i numeri primi. Utilizza la matrice dei numeri primi precedenti per cercare i fattori primi in ogni nuovo candidato, quindi è abbastanza veloce. Tuttavia, produce tonnellate di lettura di array e meno scrittura su array.

Inizialmente ho fatto questo benchmark contro Swift 1.2. Ho deciso di aggiornare il progetto ed eseguirlo su Swift 2.0.

Il progetto consente di selezionare tra l'utilizzo di normali array swift e l'utilizzo di buffer di memoria non sicuri Swift utilizzando la semantica dell'array.

Per C / Objective-C, puoi scegliere di usare NSArrays o C malloc'ed array.

I risultati del test sembrano essere abbastanza simili con l'ottimizzazione del codice più veloce, più piccola ([-0s]) o più veloce, aggressiva ([-0fast]).

Le prestazioni di Swift 2.0 sono ancora orribili con l'ottimizzazione del codice disattivata, mentre le prestazioni C / Objective-C sono solo moderatamente più lente.

La linea di fondo è che i calcoli basati su array di C malloc sono i più veloci, con un margine modesto

Swift con buffer non sicuri richiede circa 1,19X - 1,20X in più rispetto agli array C malloc quando si utilizza l'ottimizzazione del codice più veloce e più piccola. la differenza sembra leggermente inferiore con l'ottimizzazione rapida e aggressiva (Swift impiega più da 1,18x a 1,16x più a lungo di C.

Se si utilizzano le normali matrici Swift, la differenza con C è leggermente maggiore. (Swift impiega da ~ 1,22 a 1,23 in più.)

Gli array Swift regolari sono DRAMATICALLYpiù veloci di quanto non fossero in Swift 1.2 / Xcode 6. Le loro prestazioni sono così vicine agli array basati su buffer non sicuri Swift che l'utilizzo di buffer di memoria non sicuri non sembra più valere il problema, il che è grande.

A proposito, le prestazioni NSArray Objective-C puzzano. Se utilizzerai gli oggetti contenitore nativi in ​​entrambe le lingue, Swift è DRAMATICAMENTE PIÙ veloce.

Puoi dare un'occhiata al mio progetto su github su SwiftPerformanceBenchmark

Ha una semplice interfaccia utente che rende piuttosto semplice la raccolta di statistiche.

È interessante notare che l'ordinamento sembra essere leggermente più veloce in Swift che in C ora, ma che questo algoritmo di numeri primi è ancora più veloce in Swift.


8

Il problema principale che viene menzionato da altri ma che non viene richiamato abbastanza è che -O3non fa nulla in Swift (e non l'ha mai fatto), quindi una volta compilato è effettivamente non ottimizzato ( -Onone).

I nomi delle opzioni sono cambiati nel tempo, quindi alcune altre risposte hanno flag obsoleti per le opzioni di generazione. Le opzioni correnti corrette (Swift 2.2) sono:

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

L'ottimizzazione dell'intero modulo ha una compilazione più lenta ma può essere ottimizzata tra i file all'interno del modulo, ovvero all'interno di ciascun framework e all'interno del codice dell'applicazione reale, ma non tra di essi. Dovresti usarlo per qualsiasi cosa critica per le prestazioni)

Puoi anche disabilitare i controlli di sicurezza per una velocità ancora maggiore ma con tutte le asserzioni e condizioni preliminari non solo disabilitate ma ottimizzate sulla base della loro correttezza. Se hai mai asserito un'asserzione, ciò significa che hai un comportamento indefinito. Utilizzare con estrema cautela e solo se si determina che l'aumento di velocità è utile per te (mediante test). Se lo trovi utile per alcuni codici, ti consiglio di separare quel codice in un framework separato e di disabilitare solo i controlli di sicurezza per quel modulo.


Questa risposta non è più aggiornata. A partire da Swift 4.1, l'intera opzione di ottimizzazione del modulo è un valore booleano separato che può essere combinato con altre impostazioni e ora esiste un -O per ottimizzare le dimensioni. Posso aggiornare quando ho tempo di controllare i flag delle opzioni esatte.
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) 

Questo è il mio blog su Quick Sort-Github esempio Quick-Sort

Puoi dare un'occhiata all'algoritmo di partizionamento di Lomuto in Partizionare l'elenco. Scritto in Swift.


4

Swift 4.1 introduce una nuova -Osizemodalità di ottimizzazione.

In Swift 4.1 il compilatore ora supporta una nuova modalità di ottimizzazione che consente ottimizzazioni dedicate per ridurre le dimensioni del codice.

Il compilatore Swift viene fornito con potenti ottimizzazioni. Durante la compilazione con -O il compilatore tenta di trasformare il codice in modo che venga eseguito con le massime prestazioni. Tuttavia, questo miglioramento delle prestazioni di runtime può talvolta comportare un compromesso di dimensioni del codice maggiori. Con la nuova modalità di ottimizzazione -Osize, l'utente ha la possibilità di compilare per la dimensione minima del codice piuttosto che per la massima velocità.

Per abilitare la modalità di ottimizzazione delle dimensioni sulla riga di comando, utilizzare -Osize anziché -O.

Ulteriori letture: https://swift.org/blog/osize/

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.