Hızlı Beta performansı: dizileri sıralama


929

Swift Beta'da bir algoritma uyguluyordum ve performansın çok kötü olduğunu fark ettim. Daha derine intikten sonra darboğazlardan birinin sıralama dizileri kadar basit bir şey olduğunu fark ettim. İlgili bölüm burada:

let n = 1000000
var x =  [Int](repeating: 0, count: n)
for i in 0..<n {
    x[i] = random()
}
// start clock here
let y = sort(x)
// stop clock here

C ++, benzer bir işlem bilgisayarımda 0.06s alır .

Python'da 0.6s alır (hile yok, sadece tamsayıların listesi için y = sıralanmış (x)).

Swift'te aşağıdaki komutla derlersem 6 saniye sürer :

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

Ve aşağıdaki komutla derlersem 88s kadar sürer :

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

Xcode'da "Release" ve "Debug" derlemeleri olan zamanlamalar benzerdir.

Burada yanlış olan ne? C ++ ile karşılaştırıldığında bazı performans kayıplarını anlayabildim, ancak saf Python ile karşılaştırıldığında 10 kat yavaşlama anlayamadım.


Düzenleme: hava durumu , bu kodun C ++ sürümü kadar hızlı çalışmasını -O3sağladığını fark etti -Ofast! Bununla birlikte, -Ofastdilin anlambilimini çok değiştirir - testimde, tamsayı taşmaları ve dizi indeksleme taşmaları kontrollerini devre dışı bıraktı . Örneğin -Ofast, aşağıdaki Swift kodu çökmeden sessizce çalışır (ve bazı çöpleri yazdırır):

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

Yani -Ofastbizim istediğimiz değil; Swift'in asıl amacı güvenlik ağlarının yerinde olmasıdır. Elbette, güvenlik ağlarının performans üzerinde bir miktar etkisi vardır, ancak programları 100 kat daha yavaş yapmamalıdır. Java'nın dizi sınırlarını zaten kontrol ettiğini ve tipik durumlarda yavaşlamanın 2'den daha az bir faktör olduğunu unutmayın. Ve Clang ve GCC'de -ftrapvtam sayı taşmalarını (imzalı) kontrol etmek için elimizde var ve o kadar da yavaş değil.

Dolayısıyla soru: Swift'te güvenlik ağlarını kaybetmeden nasıl makul bir performans elde edebiliriz?


Edit 2: Çizgiler boyunca çok basit döngülerle biraz daha kıyaslama yaptım

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

(Burada xor işlemi sadece montaj kodunda ilgili döngüyü daha kolay bulabilmem için var. Tespit edilmesi kolay ancak aynı zamanda "zararsız" bir işlem seçmeye çalıştım. tam sayı taşmalarına).

Yine, -O3ve arasındaki performansta büyük bir fark vardı -Ofast. Montaj koduna bir göz attım:

  • İle -Ofasthemen hemen ne beklenir olsun. İlgili bölüm, 5 makine dili talimatına sahip bir döngüdür.

  • En -O3çılgın hayal gücümün ötesinde bir şey alıyorum. İç döngü 88 satır montaj kodunu kapsar. Hepsini anlamaya çalışmadım, ama en şüpheli kısım 13 çağrı "callq _swift_retain" ve 13 çağrı "callq _swift_release" dir. Yani, iç döngüde 26 altyordam çağrısı !


Düzenleme 3: Yorumlarda Ferruccio, yerleşik işlevlere (örn. Sıralama) güvenmediği anlamında adil olan karşılaştırmalar istedi. Aşağıdaki programın oldukça iyi bir örnek olduğunu düşünüyorum:

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

Aritmetik yoktur, bu nedenle tamsayı taşmaları hakkında endişelenmemize gerek yoktur. Yaptığımız tek şey sadece dizi referansları. Sonuçlar burada — Swift -O3, -Ofast ile karşılaştırıldığında neredeyse 500 kat azalıyor:

  • C ++ -O3: 0.05 s
  • C ++ -O0: 0.4 s
  • Java: 0.2 s
  • PyPy ile Python: 0.5 s
  • Python: 12 s
  • Hızlı -Ofast: 0.05 s
  • Swift -O3: 23 s
  • Swift -O0: 443 s

(Derleyicinin anlamsız döngüleri tamamen optimize edebileceğinden endişe ediyorsanız, bunu örneğin olarak değiştirebilir x[i] ^= x[j]ve çıktı veren bir baskı ifadesi ekleyebilirsiniz x[0]. Bu hiçbir şeyi değiştirmez; zamanlamalar çok benzer olacaktır.)

Ve evet, burada Python uygulaması, ints listesi olan ve döngüler için iç içe yerleştirilmiş aptal bir saf Python uygulamasıydı. Optimize edilmemiş Swift'ten çok daha yavaş olmalıdır . Swift ve dizi indeksleme ile ilgili bir şey ciddi bir şekilde kırılmış gibi görünüyor.


Edit 4: Bu sorunlar (ve diğer bazı performans sorunları) Xcode 6 beta 5'te düzeltilmiş gibi görünüyor.

Sıralama için şimdi aşağıdaki zamanlamalara sahibim:

  • clang ++ -O3: 0.06 s
  • swiftc -Ofast: 0.1 s
  • swiftc -O: 0.1 s
  • swiftc: 4 s

İç içe döngüler için:

  • clang ++ -O3: 0.06 s
  • swiftc -Ofast: 0,3 sn.
  • swiftc -O: 0.4 s
  • swiftc: 540 s

Görünüşe göre güvensiz -Ofast(aka -Ounchecked) kullanmak için hiçbir neden yok ; plain -Oeşit derecede iyi kod üretir.


20
İşte bir başka "C'den 100 kat daha yavaş" sorusu: stackoverflow.com/questions/24102609/…
Jukka Suomela

16
Ve Apple'ın Swift'in sıralamadaki iyi performansıyla ilgili pazarlama materyali hakkında tartışma: programmers.stackexchange.com/q/242816/913
Jukka Suomela

2
Sen derleyebirsiniz: xcrun --sdk macosx swift -O3. Daha kısa.
Southern Hospitality

3
Bu bağlantı, Objective-C'ye kıyasla diğer bazı temel işlemleri gösterir.
Wold

4
Beta 5 ile Swift'in hızında önemli bir gelişme oldu - daha fazla ayrıntı için Jesse Squires'ın bu yazısına bakın.
Nate Cook

Yanıtlar:


460

tl; dr Swift 1.0 artık varsayılan sürüm optimizasyon düzeyi [-O] kullanılarak bu kıyaslama ölçütü ile C kadar hızlı.


İşte Swift Beta'da yerinde bir çabuk sıralama:

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

Aynı şey 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);
}

Her ikisi de çalışır:

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]

Her ikisi de yazılı olarak aynı programda çağrılır.

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

Bu mutlak süreleri saniyeye çevirir:

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

Derleyicinin optimizasyon düzeylerinin bir özeti:

[-Onone] no optimizations, the default for debug.
[-O]     perform optimizations, the default for release.
[-Ofast] perform optimizations and disable runtime overflow checks and runtime type checks.

N = 10_000 için [-Onone] ile saniye cinsinden süre :

Swift:            0.895296452
C:                0.001223848

Swift'in n = 10_000 için yerleşik sort () yöntemi :

Swift_builtin:    0.77865783

İşte n = 10_000 için [-O] :

Swift:            0.045478346
C:                0.000784666
Swift_builtin:    0.032513488

Gördüğünüz gibi Swift'in performansı 20 kat arttı.

Gereğince mweathers' cevabı , ayar [-Ofast] için bu zamanlarda ortaya çıkan gerçek farkı yaratan n = 10_000 :

Swift:            0.000706745
C:                0.000742374
Swift_builtin:    0.000603576

Ve n = 1_000_000 için :

Swift:            0.107111846
C:                0.114957179
Swift_sort:       0.092688548

Karşılaştırma için bu n = 1_000_000 için [-Onone] ile :

Swift:            142.659763258
C:                0.162065333
Swift_sort:       114.095478272

Dolayısıyla, optimizasyonu olmayan Swift, bu ölçütte, geliştirilmesinde bu aşamada C'den neredeyse 1000 kat daha yavaştı. Öte yandan, her iki derleyici de [-Ofast] olarak ayarlandığında Swift aslında C'den biraz daha iyi olmasa da en azından iyi performans gösterdi.

[-Ofast] ifadesinin dilin anlambilimini değiştirerek potansiyel olarak güvensiz hale getirdiği belirtilmiştir. Apple'ın Xcode 5.0 sürüm notlarında belirttiği şey:

LLVM'de bulunan yeni optimizasyon seviyesi -Ofast, agresif optimizasyonlara olanak tanır. -Ofast, çoğu kod için güvenli olan çoğunlukla kayan nokta işlemleri için bazı muhafazakar kısıtlamaları gevşetir. Derleyiciden yüksek performanslı önemli kazançlar sağlayabilir.

Hepsi onu savunuyor. Bu akıllıca olsun ya da olmasın, söyleyemediğim kadarıyla, yüksek hassasiyetli kayan nokta aritmetiği yapmıyorsanız ve tamsayı olmadığından emin değilseniz, bir sürümde [-Ofast] 'ı kullanmak için makul görünüyor. programınızda dizi taşmaları mümkündür. Yüksek performansa ve taşma kontrollerine / hassas aritmetiğe ihtiyacınız varsa , şimdilik başka bir dil seçin.

BETA 3 GÜNCELLEME:

[= O] ile n = 10_000 :

Swift:            0.019697268
C:                0.000718064
Swift_sort:       0.002094721

Swift genel olarak biraz daha hızlı ve Swift'in yerleşik sıralaması oldukça önemli ölçüde değişmiş gibi görünüyor.

NİHAİ GÜNCELLEME:

[-Onone] :

Swift:   0.678056695
C:       0.000973914

[-O] :

Swift:   0.001158492
C:       0.001192406

[-Kutulur] :

Swift:   0.000827764
C:       0.001078914

25
Ara SIL kodunun çıktısını almak için -emit-sil kullanılması neyin alıkonulduğunu gösterir (argh, yığın taşması bunu biçimlendirmeyi imkansız hale getirir). Array içindeki dahili bir tampon nesne. Bu kesinlikle bir optimizer hatası gibi görünüyor, ARC optimizer -Ofast olmadan muhafazaları kaldırabilmelidir.
Catfish_Man

Ofast optimizasyonlarını kullanmak istiyorsanız başka bir dil kullanmamız gerektiğini kabul etmeyeceğim. C gibi başka bir dil seçerseniz sınır kontrolleri ve diğer küçük problemler ile benzer şekilde ilgilenmek zorunda kalacaktır. Bu, programcının kodunuzun hatalarını ayıklamasını, her şeyin yolunda olduğundan emin olmak ve Ofast kullanarak derlemesini sağlar. Modern standartları kullanma ve yine de C gibi "güvensiz" bir dilin gücüne sahip olma olasılığı çok iyidir.
Wallacy

2
Bana bunun nasıl geçersiz olabileceğini söyleyebilirsen lütfen yap. ben her zaman daha fazla bilgi edinmek isterim
Joseph Mark

3
son bir güncelleme yaptı, Swift şimdi standart optimizasyonlar kullanarak bu kıyaslama tarafından C kadar hızlı.
Joseph Mark

4
İpucu: Swift ve çabuk C uygulamaları Hem eğer üzerinde recurse geliştirilebilir en küçük bölümü ilk! (Her zaman önce sol bölümde yineleme yapmak yerine.) En kötü durumda basit bir pivot seçimi ile uygulanan Quicksort O (n ^ 2) zaman alır, ancak bu en kötü durumda bile yineleyerek yalnızca O (log n) yığın alanına ihtiyacınız vardır. önce daha küçük bölmede.
Macneil Shonle

108

TL; DR : Evet, tek Swift dil uygulaması şu anda yavaş . Hızlı, sayısal (ve muhtemelen diğer tür kodlara) ihtiyacınız varsa, başka bir kodla gitmeniz yeterlidir. Gelecekte, seçiminizi yeniden değerlendirmelisiniz. Yine de, daha üst düzeyde yazılmış çoğu uygulama kodu için yeterince iyi olabilir.

SIL ve LLVM IR'de gördüğüm kadarıyla, Clang'da (Objective-C için) uygulanabilecek tutma ve sürümleri kaldırmak için bir grup optimizasyona ihtiyaçları var gibi görünüyor , ancak henüz taşımadılar. Bu sorunun devam ettiği teori (şimdilik… Clang'ın bu konuda bir şey yaptığını hala doğrulamam gerekiyor), çünkü bu sorunun son test senaryosunda çalışan bir profilci bu “güzel” sonucu veriyor:

-O3'te zaman profili oluşturma -Ofast'ta zaman profili oluşturma

Diğerleri tarafından söylendiği gibi -Ofast, tamamen güvensizdir ve dil semantiğini değiştirir. Benim için, “Bunu kullanacaksan, sadece başka bir dil kullan” aşamasında. Değişirse bu seçimi daha sonra tekrar değerlendireceğim.

-O3bize bir grup verir swift_retainve swift_releasedürüst olmak gerekirse, bu örnek için orada olması gerektiği gibi görünmüyor. Optimize edici, diziyle ilgili bilgilerin çoğunu bildiği ve en azından güçlü bir referansı olduğunu bildiği için (çoğu) AFAICT'yi kullanmalıdır.

Nesneleri serbest bırakabilecek işlevler bile çağrılmadığında daha fazla koruma yaymamalıdır. Ben bir dizi yapıcı istedi ne daha küçük bir dizi dönebilir sanmıyorum, bu yayılan çekler bir sürü işe yaramaz anlamına gelir. Aynı zamanda taşma denetler böylece tamsayı, 10k üzerinde olması asla bilir olabilir (çünkü optimize edilmesi -Ofast(başka bir şey var anahtar sözcüğünün değişiyor ne de erişebilir ve 10k kadar ekleyerek garabeti, fakat dilin Anlambilimin tür için güvenlidir Int).

Derleyici sort(), dış bir işlev olan ve beklediği bağımsız değişkenleri almak zorunda oldukları için, geçtikleri için dizi veya dizi öğelerinin kutularını kaldıramayabilir . Bu, Intdeğerleri dolaylı olarak kullanmamızı sağlayacaktır , bu da onu biraz daha yavaş yapacaktır. sort()Genel işlev (çok yöntemli şekilde değil) derleyici tarafından kullanılabilirse ve satır içine alınırsa bu durum değişebilir .

Bu çok yeni bir (halka) dilidir ve görüş soran Swift dili ile ilgili (ağır) insanlar vardır çünkü, ben sandığım içinden birçok değişiklik vardır oluyor ve hepsi dil bitmedi demek ve irade değişiklik.

Kullanılan kod:

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")

Not: Objective-C konusunda uzman değilim, ne de Cocoa , Objective-C veya Swift çalışma zamanlarındaki tüm tesisler . Yazmadığım bazı şeyleri de varsayabilirim.


Derleyici, harici bir işlev olan ve beklediği bağımsız değişkenleri almak zorunda olan sort () öğesine geçtikleri için dizi veya dizi öğelerinin kutularını kaldıramayabilir. Bu nispeten iyi bir derleyici için önemli olmamalıdır. Gerçek veriler hakkında meta veriler (işaretçide - 64 bit çok fazla levee sunar) iletilir ve çağrılan işlevde dallanır.
bestsss

3
-Ofast"Tamamen güvensiz" olan şey tam olarak nedir ? Kodunuzu nasıl test edeceğinizi ve taşmaların nasıl dışlanacağını bildiğinizi varsayarsak.
Joseph Mark

@sjeohp: Bu aslında çok şey varsayıyor :-) Kodu kontrol etmek ve taşmaları yönetmek çok zor. Deneyimlerime göre (derleyici çalışması yapıyorum ve bazı büyük kod tabanlarını kontrol ettim) ve büyük şirketler üzerinde derleyici çalışması yapan insanlardan duyduğum, taşma ve diğer tanımlanmamış davranışları doğru elde etmek zor . Apple'ın UB'yi düzeltmeyle ilgili tavsiyeleri bile (sadece bir örnek) bazen yanlıştır ( randomascii.wordpress.com/2014/04/17/… ). -Ofastdil anlambilimini de değiştirir, ancak bunun için herhangi bir dokümanı finanse edemiyorum. Ne yaptığını bildiğinizden nasıl emin olabilirsiniz?
filcab

@bestsss: Mümkün ama faydalı olmayabilir. Int [] 'ye her erişim için kontroller ekler. Int dizilerinin ve diğer birkaç ilkel türün (en fazla 3 bitiniz var) çok kullanılıp kullanılmadığına bağlıdır (özellikle gerekirse C'ye düştüğünüzde). Ayrıca, ARC olmayan GC eklemek istedikleri takdirde kullanmak istedikleri bazı bitleri de kullanır. Birden fazla argümanla jeneriklere de ölçeklenmez. Tüm türlere sahip olduklarından, int [] 'e dokunan tüm kodları özelleştirmek (ancak Int? [] Değil) satır içi Int kullanmak çok daha kolay olacaktır. Ama sonra endişelenmek için Obj-C birlikte çalışabilirsin.
filcab

@filcab, ARC olmayan (yani gerçek) GC gerçekten yararlı olacaktır, ancak gerçekten eşzamanlı, STW olmayan bir GC istiyorlarsa C uyumlu olmayan bir şeye ihtiyaçları vardır. 'Her erişim' için endişelenmeyeceğim, Int[]çünkü bu derleyicinin satır içi seviyesine bağlıdır ve sıkı döngülerin bazı rehberliklerle / sonrasında yönlendirilmesi gerekir.
bestsss

53

Eğlence için buna bir göz atmaya karar verdim ve işte aldığım zamanlamalar:

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

hızlı

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

Sonuçlar:

Hızlı 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

Hızlı 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

Derlediğimde aynı performans gibi görünüyor -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

Swift 2.0'dan Swift 3.0'a bir performans gerilemesi görülüyor ve ben de ilk kez -Ove arasında bir fark görüyorum -Ounchecked.

Swift 4.0

xcrun swiftc --version
Apple Swift version 4.0.2 (swiftlang-900.0.69.2 clang-900.0.38)
Target: x86_64-apple-macosx10.9

xcrun -sdk macosx swiftc -O SwiftSort.swift
./SwiftSort     
Elapsed time: 0.834299981594086

xcrun -sdk macosx swiftc -Ounchecked SwiftSort.swift
./SwiftSort     
Elapsed time: 0.742045998573303

Swift 4, performansı tekrar geliştirirken, -O ve -Ounchecked. -O -whole-module-optimizationfark yaratmadı.

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

Sonuçlar:

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

Elma 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

Elma 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

Elma 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

Karar

Bu yazının yazıldığı zaman itibariyle, Swift'in sıralaması hızlıdır, ancak -Oyukarıdaki derleyiciler ve kitaplıklar ile derlendiğinde C ++ 'ın sıralaması kadar hızlı değildir . İle -Ounchecked, Swift 4.0.2 ve Apple LLVM 9.0.0'da C ++ kadar hızlı görünüyor.


2
Gerçekte on milyon eleman eklemeden önce asla vector :: reserve () öğesini çağırmamalısınız .
BJovke

Belki! Şu an sadece sıralama zamanlanıyor.
OpenGL

34

Gönderen The Swift Programming Language:

Sıralama İşlevi Swift'in standart kitaplığı, sağladığınız bir sıralama kapağının çıktısına bağlı olarak, bilinen türde bir dizi değeri sıralayan sıralama adlı bir işlev sağlar. Sıralama işlemini tamamladıktan sonra, sıralama işlevi, öğeleri doğru sıralama düzeninde olacak şekilde, eskisiyle aynı tür ve boyutta yeni bir dizi döndürür.

sortFonksiyon iki bildirimleri vardır.

Bir karşılaştırma kapatma belirtmenizi sağlayan varsayılan bildirim:

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

Ve yalnızca tek bir parametre (dizi) alan ve "küçüktür karşılaştırıcıyı kullanmak için sabit kodlanmış" ikinci bir bildirim.

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

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

Bir oyun alanında kodunuzun değiştirilmiş bir versiyonunu, kapağın eklendiği şekilde test ettim, böylece işlevi biraz daha yakından izleyebildim ve n'nin 1000'e ayarlandığında, kapamanın yaklaşık 11.000 kez çağrıldığını buldum.

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

Verimli bir işlev değildir, daha iyi bir sıralama işlevi uygulamasını kullanmanızı öneririm.

DÜZENLE:

Quicksort wikipedia sayfasına bir göz attım ve bunun için bir Swift uygulaması yazdım. İşte kullandığım tam program (bir oyun alanında)

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
}

Bunu n = 1000 ile kullanarak,

  1. quickSort () yaklaşık 650 kez çağrıldı,
  2. yaklaşık 6000 takas yapıldı,
  3. ve yaklaşık 10.000 karşılaştırma var

Yerleşik sıralama yönteminin hızlı sıralama olduğu (veya yakın olduğu) ve gerçekten yavaş olduğu görülüyor ...


17
Belki de tamamen yanılıyorum, ancak en.wikipedia.org/wiki/Quicksort'a göre Quicksort'taki ortalama karşılaştırma sayısı 2*n*log(n). Bu, n = 1000 öğeyi sıralamak için 13815 karşılaştırmasıdır, bu nedenle karşılaştırma işlevi yaklaşık 11000 kez çağrılırsa, bu çok kötü görünmez.
Martin R

6
Ayrıca Apple, Swift'te "karmaşık nesne sıralamasının" (her ne olursa olsun) Python'a göre 3,9 kat daha hızlı olduğunu iddia etti. Bu nedenle "daha iyi bir sıralama fonksiyonu" bulmak gerekli olmamalıdır. - Ama Swift hala geliştiriliyor ...
Martin R

6
Bu vermez doğal logaritma bakın.
Martin R

24
log(n)algoritmik karmaşıklık için geleneksel olarak log tabanı-2'yi belirtir. Tabanı belirtmemenin nedeni, logaritma için taban değişikliği yasasının yalnızca O gösterimi amacıyla atılan sabit bir çarpan getirmesidir.
minuteman3

3
Doğal logaritma ve taban 2 logaritması hakkında tartışma ile ilgili: Wikipedia sayfasından yapılan kesin açıklama, n element için gerekli ortalama karşılaştırma sayısının olmasıdır C(n) = 2n ln n ≈ 1.39n log₂ n. N = 1000 için, bu Cı (n) 13815 = verir ve bunun değil , bir "büyük-O gösterimi".
Martin R

18

Xcode 7'den itibaren açabilirsiniz Fast, Whole Module Optimization. Bu, performansınızı hemen artırmalıdır.

resim açıklamasını buraya girin


12

Swift Array performansı yeniden ziyaret edildi:

Swift'i C / Objective-C ile karşılaştırarak kendi ölçütümü yazdım. Karşılaştırmam asal sayıları hesaplar. Her yeni adayda asal faktörleri aramak için önceki asal sayı dizisini kullanır, bu yüzden oldukça hızlıdır. Ancak, dizi okuma TONS ve dizilere daha az yazma yapar.

Başlangıçta bu ölçütü Swift 1.2'ye karşı yaptım. Projeyi güncellemeye ve Swift 2.0 ile çalıştırmaya karar verdim.

Proje, normal hızlı dizileri kullanma ve dizi semantiği kullanarak Swift güvensiz bellek arabelleklerini kullanma arasında seçim yapmanızı sağlar.

C / Objective-C için, NSArrays veya C hatalı diziler kullanmayı tercih edebilirsiniz.

Test sonuçları en hızlı, en küçük kod optimizasyonu ([-0s]) veya en hızlı, agresif ([-0fast]) optimizasyonu ile oldukça benzer görünmektedir.

Swift 2.0 performansı hala kod optimizasyonu kapalıyken korkunç, C / Objective-C performansı ise sadece orta derecede yavaş.

Sonuç olarak, C malloc'd dizi tabanlı hesaplamalar mütevazı bir farkla en hızlı

Güvenli olmayan arabellekli Swift, en hızlı, en küçük kod optimizasyonunu kullanırken C malloc'd dizilerinden yaklaşık 1.19X - 1.20X daha uzun sürer. hızlı, agresif optimizasyonla fark biraz daha az görünüyor (Swift, C'den 1.18x ila 1.16x daha uzun sürüyor.

Düzenli Swift dizileri kullanırsanız, C ile olan fark biraz daha fazladır. (Swift ~ 1.22 ila 1.23 daha uzun sürer.)

Düzenli Swift dizileri DRAMATICALLY Swift 1.2 / Xcode 6'da daha hızlıdır. Performansları, güvensiz bellek arabelleklerini kullanmak artık sorun değmeyecek gibi büyük, Swift güvensiz tampon tabanlı dizilere çok yakın.

BTW, Objective-C NSArray performans kokuyor. Yerel kap nesnelerini her iki dilde kullanacaksanız , Swift DRAMATICALLY daha hızlıdır.

SwiftPerformanceBenchmark adresinden github'daki projemi kontrol edebilirsiniz.

Toplama istatistiklerini oldukça kolaylaştıran basit bir kullanıcı arayüzüne sahiptir.

Swift'te sıralamanın şimdi C'den biraz daha hızlı olduğu ilginç, ancak bu asal sayı algoritmasının Swift'te hala daha hızlı olduğu ilginç.


8

Başkaları tarafından bahsedilen ancak yeterince çağrılmayan ana sorun, -O3Swift'te hiçbir şey yapmaması (ve asla sahip olmadığı), bu nedenle derlendiğinde etkili bir şekilde optimize edilmemiş ( -Onone) olmasıdır.

Seçenek adları zaman içinde değişti, bu nedenle diğer bazı cevapların oluşturma seçenekleri için eski bayraklar var. Doğru akım seçenekleri (Swift 2.2):

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

Tüm modül optimizasyonu daha yavaş bir derlemeye sahiptir, ancak modül içindeki dosyalar arasında, yani her bir çerçeve içinde ve gerçek uygulama kodunda ancak aralarında değil, optimizasyon yapabilir. Performans açısından kritik olan herhangi bir şey için bunu kullanmalısınız)

Ayrıca, daha fazla hız için güvenlik kontrollerini devre dışı bırakabilirsiniz, ancak tüm iddialar ve ön koşullar yalnızca devre dışı değil aynı zamanda doğru olduklarına göre optimize edilmiştir. Bir iddiayı vurursanız, bu, tanımlanmamış davranışa girdiğiniz anlamına gelir. Çok dikkatli kullanın ve sadece hız artışının sizin için değerli olduğunu belirlerseniz (test ederek). Bazı kodlar için değerli bulursanız, bu kodu ayrı bir çerçeveye ayırmanızı ve yalnızca o modülün güvenlik kontrollerini devre dışı bırakmanızı öneririm.


Bu cevap şimdi güncel değil. Swift 4.1'den itibaren tüm modül optimizasyon seçeneği, diğer ayarlarla birleştirilebilen ayrı bir boole'dir ve şimdi boyut için optimize etmek için bir -O'lar vardır. Tam seçenek bayraklarını kontrol etmek için zamanım olduğunda güncelleyebilirim.
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) 

Bu, Hızlı Sıralama- Github örneği Hızlı Sıralama hakkındaki Blogum

Listeyi Bölümlere Ayırma bölümündeki Lomuto'nun bölümleme algoritmasına göz atabilirsiniz. Swift dilinde yazıldı.


4

Swift 4.1 yeni -Osizeoptimizasyon modu sunuyor.

Swift 4.1'de derleyici artık kod boyutunu azaltmak için özel optimizasyonlara olanak tanıyan yeni bir optimizasyon modunu destekliyor.

Swift derleyicisi güçlü optimizasyonlarla birlikte gelir. -O ile derlerken, derleyici kodu maksimum performansla çalışacak şekilde dönüştürmeye çalışır. Bununla birlikte, çalışma zamanı performansındaki bu iyileştirme bazen artan kod boyutunda bir takas ile gelebilir. Yeni -Osize optimizasyon moduyla, kullanıcı maksimum hız yerine minimum kod boyutu için derleme seçeneğine sahiptir.

Komut satırında boyut optimizasyon modunu etkinleştirmek için -O yerine -Osize kullanın.

Daha fazla okuma: https://swift.org/blog/osize/

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.