Kinerja Beta Swift: array penyortiran

929

Saya menerapkan algoritma di Swift Beta dan memperhatikan bahwa kinerjanya sangat buruk. Setelah menggali lebih dalam, saya menyadari bahwa salah satu hambatan adalah sesuatu yang sederhana seperti menyortir array. Bagian yang relevan ada di sini:

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

Di C ++, operasi yang sama mengambil 0,06s di komputer saya.

Dalam Python, dibutuhkan 0,6s (tidak ada trik, hanya y = diurutkan (x) untuk daftar bilangan bulat).

Di Swift dibutuhkan 6s jika saya mengompilasinya dengan perintah berikut:

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

Dan dibutuhkan sebanyak 88-an jika saya mengompilasinya dengan perintah berikut:

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

Timing dalam Xcode dengan build "Release" vs. "Debug" serupa.

Apa yang salah di sini? Saya bisa memahami beberapa kerugian kinerja dibandingkan dengan C ++, tetapi bukan penurunan 10 kali lipat dibandingkan dengan Python murni.


Edit: cuaca memperhatikan bahwa perubahan -O3untuk -Ofastmerek kode ini dijalankan hampir secepat C ++ versi! Namun, banyak -Ofastmengubah semantik bahasa - dalam pengujian saya, itu menonaktifkan pemeriksaan untuk bilangan bulat bilangan bulat dan limpahan pengindeksan array . Misalnya, dengan -Ofastkode Swift berikut ini berjalan secara diam-diam tanpa menabrak (dan mencetak beberapa sampah):

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

Jadi -Ofastbukan yang kita inginkan; Inti dari Swift adalah kita memiliki jaring pengaman di tempatnya. Tentu saja, jaring pengaman berdampak pada kinerja, tetapi seharusnya tidak membuat program 100 kali lebih lambat. Ingatlah bahwa Java sudah memeriksa batas array, dan dalam kasus-kasus tertentu, perlambatan adalah dengan faktor yang jauh lebih kecil dari 2. Dan di Dentang dan GCC kita punya -ftrapvuntuk memeriksa (menandatangani) bilangan bulat bilangan bulat, dan juga tidak terlalu lambat.

Karena itu pertanyaannya: bagaimana kita bisa mendapatkan kinerja yang wajar di Swift tanpa kehilangan jaring pengaman?


Sunting 2: Saya melakukan lebih banyak pembandingan, dengan loop yang sangat sederhana di sepanjang baris

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

(Di sini operasi xor ada di sana hanya supaya saya dapat lebih mudah menemukan loop yang relevan dalam kode assembly. Saya mencoba untuk memilih operasi yang mudah dikenali tetapi juga "tidak berbahaya" dalam arti bahwa seharusnya tidak memerlukan pemeriksaan terkait untuk integer overflow.)

Sekali lagi, ada perbedaan besar dalam kinerja antara -O3dan -Ofast. Jadi saya melihat kode perakitan:

  • Dengan -Ofastsaya mendapatkan cukup banyak apa yang saya harapkan. Bagian yang relevan adalah loop dengan 5 instruksi bahasa mesin.

  • Dengan -O3saya mendapatkan sesuatu yang berada di luar imajinasi saya yang paling liar. Loop dalam mencakup 88 baris kode rakitan. Saya tidak mencoba memahami semua itu, tetapi bagian yang paling mencurigakan adalah 13 doa "callq _swift_retain" dan 13 doa lainnya "callq _swift_release". Yaitu, 26 panggilan subrutin di loop dalam !


Sunting 3: Dalam komentar, Ferruccio meminta tolok ukur yang adil dalam arti bahwa mereka tidak bergantung pada fungsi bawaan (mis. Sort). Saya pikir program berikut adalah contoh yang cukup baik:

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

Tidak ada aritmatika, jadi kita tidak perlu khawatir tentang bilangan bulat bilangan bulat. Satu-satunya hal yang kami lakukan hanyalah banyak referensi array. Dan hasilnya ada di sini — Swift -O3 hilang dengan faktor hampir 500 dibandingkan dengan -Ofast:

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

(Jika Anda khawatir bahwa kompiler dapat mengoptimalkan loop sia-sia sepenuhnya, Anda dapat mengubahnya menjadi misalnya x[i] ^= x[j], dan menambahkan pernyataan cetak yang dihasilkan x[0]. Ini tidak mengubah apa pun; waktunya akan sangat mirip.)

Dan ya, di sini implementasi Python adalah implementasi Python murni yang bodoh dengan daftar int dan bersarang untuk loop. Itu harus jauh lebih lambat daripada Swift yang tidak dioptimalkan. Sesuatu tampaknya sangat rusak dengan pengindeksan Swift dan array.


Sunting 4: Masalah-masalah ini (serta beberapa masalah kinerja lainnya) tampaknya telah diperbaiki di Xcode 6 beta 5.

Untuk menyortir, saya sekarang memiliki timing berikut:

  • dentang ++ -O3: 0,06 dtk
  • swiftc -Ofast: 0,1 s
  • swiftc -O: 0,1 s
  • swiftc: 4 dtk

Untuk loop bersarang:

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

Tampaknya tidak ada alasan lagi untuk menggunakan yang tidak aman -Ofast(alias -Ounchecked); plain -Omenghasilkan kode yang sama baiknya.

Jukka Suomela
sumber
20
Inilah pertanyaan "Swift 100 kali lebih lambat dari C": stackoverflow.com/questions/24102609/…
Jukka Suomela
16
Dan di sini ada diskusi tentang materi pemasaran Apple terkait kinerja Swift yang baik dalam menyortir: programmers.stackexchange.com/q/242816/913
Jukka Suomela
2
Anda dapat mengkompilasi dengan: xcrun --sdk macosx swift -O3. Lebih pendek.
Southern Hospitality
3
Tautan ini menunjukkan beberapa operasi dasar lainnya dibandingkan dengan Objective-C.
Wold
4
Dengan Beta 5 telah ada peningkatan substansial dalam kecepatan Swift - lihat posting ini oleh Jesse Squires untuk lebih detail.
Nate Cook

Jawaban:

460

tl; dr Swift 1.0 sekarang lebih cepat dari C oleh tolok ukur ini menggunakan tingkat optimisasi rilis default [-O].


Berikut adalah quicksort di tempat di 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)
}

Dan hal yang sama dalam 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);
}

Keduanya bekerja:

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]

Keduanya dipanggil dalam program yang sama seperti yang tertulis.

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

Ini mengonversi waktu absolut ke detik:

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

Berikut ini adalah ringkasan tingkat optimisasi kompiler:

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

Waktu dalam detik dengan [-Onone] untuk n = 10_000 :

Swift:            0.895296452
C:                0.001223848

Ini adalah jenis bawaan Swift () untuk n = 10_000 :

Swift_builtin:    0.77865783

Ini [-O] untuk n = 10_000 :

Swift:            0.045478346
C:                0.000784666
Swift_builtin:    0.032513488

Seperti yang Anda lihat, kinerja Swift meningkat dengan faktor 20.

Seperti jawaban per mweathers , pengaturan [-Ofast] membuat perbedaan nyata, menghasilkan waktu ini untuk n = 10_000 :

Swift:            0.000706745
C:                0.000742374
Swift_builtin:    0.000603576

Dan untuk n = 1_000_000 :

Swift:            0.107111846
C:                0.114957179
Swift_sort:       0.092688548

Sebagai perbandingan, ini dengan [-Satu] untuk n = 1_000_000 :

Swift:            142.659763258
C:                0.162065333
Swift_sort:       114.095478272

Jadi Swift tanpa optimasi hampir 1000x lebih lambat daripada C dalam benchmark ini, pada tahap ini dalam pengembangannya. Di sisi lain dengan kedua kompiler diatur ke [-Ofast] Swift benar-benar tampil setidaknya juga jika tidak sedikit lebih baik daripada C.

Telah ditunjukkan bahwa [-Ofast] mengubah semantik bahasa, membuatnya berpotensi tidak aman. Inilah yang Apple nyatakan dalam catatan rilis Xcode 5.0:

Level optimisasi baru -Ofast, tersedia di LLVM, memungkinkan optimisasi yang agresif. -Ofast melonggarkan beberapa pembatasan konservatif, sebagian besar untuk operasi floating-point, yang aman untuk sebagian besar kode. Itu dapat menghasilkan kemenangan kinerja tinggi yang signifikan dari kompiler.

Mereka semua menganjurkannya. Entah itu bijaksana atau tidak, saya tidak bisa mengatakannya, tetapi dari apa yang saya bisa katakan, rasanya cukup masuk akal untuk menggunakan [-Ofast] dalam rilis jika Anda tidak melakukan aritmatika floating point presisi tinggi dan Anda yakin tidak memiliki bilangan bulat atau array overflow dimungkinkan dalam program Anda. Jika Anda memang membutuhkan pemeriksaan kinerja tinggi dan melimpah / aritmatika yang tepat, maka pilih bahasa lain untuk saat ini.

PEMBARUAN BETA 3:

n = 10_000 dengan [-O] :

Swift:            0.019697268
C:                0.000718064
Swift_sort:       0.002094721

Swift secara umum sedikit lebih cepat dan sepertinya jenis bawaan Swift telah berubah cukup signifikan.

PEMBARUAN AKHIR:

[-Onone] :

Swift:   0.678056695
C:       0.000973914

[-O] :

Swift:   0.001158492
C:       0.001192406

[-Dilihat] :

Swift:   0.000827764
C:       0.001078914
Joseph Mark
sumber
25
Menggunakan -emit-sil untuk menampilkan kode SIL perantara menunjukkan apa yang dipertahankan (argh, stack overflow membuat ini tidak mungkin untuk diformat). Ini adalah objek buffer internal di Array. Ini pasti kedengarannya seperti bug pengoptimal, pengoptimal ARC harus dapat menghapus penahan tanpa -Buka.
Catfish_Man
Hanya akan tidak setuju bahwa kita harus menggunakan bahasa lain jika ingin menggunakan optimasi Ofast. Ini harus berurusan dengan masalah yang sama dengan pemeriksaan batas dan masalah kecil lainnya jika memilih bahasa lain seperti C. Swift itu sebenarnya keren karena harus aman secara default dan cepat opsional dan tidak aman jika diperlukan. Ini memungkinkan programmer untuk men-debug kode Anda juga, untuk memastikan semuanya baik-baik saja dan kompilasi menggunakan Ofast. Kemungkinan menggunakan standar modern namun memiliki kekuatan bahasa "tidak aman" seperti C sangat keren.
Wallacy
2
jika Anda dapat memberi tahu saya bagaimana ini mungkin tidak valid, silakan lakukan. Saya selalu ingin belajar lebih banyak
Joseph Mark
3
membuat pembaruan akhir, Swift sekarang secepat C dengan tolok ukur ini menggunakan optimisasi standar.
Joseph Mark
4
Kiat: Implementasi Quicksort Swift dan C Anda dapat ditingkatkan jika rekursi Anda pada partisi terkecil terlebih dahulu! (Alih-alih berulang pada partisi kiri selalu terlebih dahulu.) Quicksort diimplementasikan dengan pemilihan pivot sederhana dalam kasus terburuk membutuhkan waktu O (n ^ 2), tetapi bahkan dalam kasus terburuk ini Anda hanya perlu O (log n) susun ruang dengan mengulangi pada partisi yang lebih kecil terlebih dahulu.
Macneil Shonle
108

TL; DR : Ya, satu-satunya implementasi bahasa Swift lambat, saat ini . Jika Anda memerlukan kode yang cepat, numerik (dan jenis kode lainnya, mungkin), cukup gunakan yang lain. Di masa depan, Anda harus mengevaluasi kembali pilihan Anda. Mungkin cukup baik untuk sebagian besar kode aplikasi yang ditulis pada level yang lebih tinggi.

Dari apa yang saya lihat di SIL dan LLVM IR, sepertinya mereka membutuhkan banyak optimasi untuk menghapus retain dan rilis, yang mungkin diimplementasikan di Dentang (untuk Objective-C), tetapi mereka belum porting mereka. Itulah teori yang saya ajukan (untuk saat ini ... saya masih perlu memastikan bahwa Clang melakukan sesuatu tentang hal itu), karena seorang profiler yang menjalankan uji kasus terakhir dari pertanyaan ini menghasilkan hasil yang “cantik” ini:

Waktu profiling pada -O3 Waktu membuat profil di -Ofast

Seperti yang dikatakan oleh banyak orang lain, -Ofastsama sekali tidak aman dan mengubah semantik bahasa. Bagi saya, itu pada tahap "Jika Anda akan menggunakannya, gunakan saja bahasa lain". Saya akan mengevaluasi kembali pilihan itu nanti, jika itu berubah.

-O3memberi kita banyak swift_retaindan swift_releasepanggilan itu, jujur, tidak terlihat seperti mereka harus ada untuk contoh ini. Pengoptimal seharusnya menghilangkan (sebagian besar) dari mereka AFAICT, karena ia mengetahui sebagian besar informasi tentang array, dan tahu bahwa ia memiliki (setidaknya) referensi kuat untuk itu.

Seharusnya tidak memancarkan lebih banyak mempertahankan ketika itu bahkan tidak memanggil fungsi yang mungkin melepaskan objek. Saya tidak berpikir konstruktor array dapat mengembalikan array yang lebih kecil dari yang diminta, yang berarti bahwa banyak pemeriksaan yang dipancarkan tidak berguna. Ia juga tahu bahwa integer tidak akan pernah di atas 10k, jadi pemeriksaan overflow dapat dioptimalkan (bukan karena -Ofastkeanehan, tetapi karena semantik bahasa (tidak ada yang mengubah var itu dan tidak dapat mengaksesnya, dan menambahkan hingga 10k aman untuk tipenya Int).

Kompiler mungkin tidak dapat membuka kotak array atau elemen array, karena mereka diteruskan ke sort(), yang merupakan fungsi eksternal dan harus mendapatkan argumen yang diharapkan. Ini akan membuat kita harus menggunakan Intnilai secara tidak langsung, yang akan membuatnya sedikit lebih lambat. Ini bisa berubah jika sort()fungsi generik (tidak dengan cara multi-metode) tersedia untuk kompiler dan mendapat inline.

Ini adalah bahasa yang sangat baru (secara publik), dan sedang melalui apa yang saya asumsikan banyak perubahan, karena ada orang (banyak) yang terlibat dengan bahasa Swift meminta umpan balik dan mereka semua mengatakan bahasa itu belum selesai dan akan perubahan.

Kode yang digunakan:

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: Saya bukan ahli Objective-C atau semua fasilitas dari Cocoa , Objective-C, atau runtime Swift. Saya mungkin juga mengasumsikan beberapa hal yang tidak saya tulis.

filcab
sumber
Kompiler mungkin tidak dapat membuka kotak array atau elemen array, karena mereka diteruskan untuk mengurutkan (), yang merupakan fungsi eksternal dan harus mendapatkan argumen yang diharapkan. Itu seharusnya tidak masalah bagi kompiler yang relatif baik. Melewati metadata (dalam pointer - 64bits menawarkan banyak tanggul) tentang data aktual dan bercabang dalam fungsi yang disebut.
bestsss
3
Apa sebenarnya yang membuat -Ofast"sama sekali tidak aman"? Dengan asumsi Anda tahu cara menguji kode Anda dan mengesampingkan luapan.
Joseph Mark
@sjeohp: Itu sebenarnya banyak asumsi :-) Memeriksa kode dan mengesampingkan luapan sulit dilakukan. Dari pengalaman saya (saya melakukan pekerjaan kompiler dan telah memeriksa beberapa basis kode besar), dan apa yang saya dengar dari orang-orang yang melakukan pekerjaan kompiler pada perusahaan besar, mendapatkan overflow dan perilaku tidak terdefinisi lainnya benar sulit . Bahkan saran Apple (hanya sebuah contoh) tentang memperbaiki UB adalah salah, kadang-kadang ( randomascii.wordpress.com/2014/04/17/… ). -Ofastjuga mengubah semantik bahasa, tetapi saya tidak dapat mendanai dokumen apa pun untuk itu. Bagaimana Anda bisa yakin bahwa Anda tahu apa yang dilakukannya?
filcab
@bestsss: Itu mungkin, tetapi mungkin tidak berguna. Itu menambah pemeriksaan pada setiap akses ke Int []. Itu tergantung jika array Int dan beberapa tipe primitif lainnya (Anda memiliki, paling banyak, 3 bit) banyak digunakan (terutama ketika Anda dapat menurunkan ke C jika Anda perlu). Ini juga menggunakan beberapa bit yang mungkin ingin mereka gunakan jika, pada akhirnya, mereka ingin menambahkan non-ARC GC. Itu tidak skala ke generik dengan lebih dari satu argumen, baik. Karena mereka memiliki semua tipe, akan jauh lebih mudah untuk mengkhususkan semua kode yang menyentuh Int [] (tetapi tidak Int? []) Untuk menggunakan Int sebaris. Tapi kemudian Anda perlu khawatir dengan Obj-C interop.
filcab
@filcab, non-ARC (yaitu nyata) GC akan benar-benar berguna tetapi mereka membutuhkan sesuatu yang tidak kompatibel dengan C jika mereka menginginkan GC non-STW yang benar-benar konkuren. Saya tidak perlu khawatir tentang 'setiap akses ke Int[]' karena itu tergantung pada level yang bisa di-compiler oleh kompiler dan seharusnya bisa inline loop ketat dengan / setelah beberapa panduan.
bestsss
53

Saya memutuskan untuk melihatnya untuk bersenang-senang, dan inilah waktu yang saya dapatkan:

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

Cepat

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

Hasil:

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

Cepat 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

Tampaknya kinerja yang sama jika saya kompilasi dengan -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

Tampaknya ada regresi kinerja dari Swift 2.0 ke Swift 3.0, dan saya juga melihat perbedaan antara -Odan -Ouncheckeduntuk pertama kalinya.

Cepat 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 meningkatkan kinerja lagi, sambil menjaga jarak antara -Odan -Ounchecked. -O -whole-module-optimizationtampaknya tidak membuat perbedaan.

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

Hasil:

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

Putusan

Pada saat penulisan ini, jenis Swift cepat, tetapi belum secepat jenis C ++ ketika dikompilasi -O, dengan kompiler & pustaka di atas. Dengan -Ounchecked, tampaknya secepat C ++ di Swift 4.0.2 dan Apple LLVM 9.0.0.

Pelajari OpenGL ES
sumber
2
Pada kenyataannya, Anda seharusnya tidak pernah memanggil vektor :: cadangan () sebelum memasukkan sepuluh juta elemen.
BJovke
Mungkin! Hanya jenis yang dihitung waktunya saat ini.
Pelajari OpenGL ES
34

Dari The Swift Programming Language:

Sortir Fungsi Pustaka standar Swift menyediakan fungsi yang disebut sort, yang mengurutkan array nilai dari tipe yang dikenal, berdasarkan pada output dari penutupan sortir yang Anda berikan. Setelah menyelesaikan proses pengurutan, fungsi pengurutan mengembalikan array baru dengan jenis dan ukuran yang sama dengan yang lama, dengan elemen-elemennya dalam urutan yang benar.

The sortfungsi memiliki dua deklarasi.

Deklarasi default yang memungkinkan Anda untuk menentukan penutupan perbandingan:

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

Dan deklarasi kedua yang hanya mengambil parameter tunggal (array) dan "hardcoded untuk menggunakan komparator kurang dari."

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

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

Saya menguji versi modifikasi kode Anda di taman bermain dengan penutup ditambahkan sehingga saya bisa memantau fungsinya sedikit lebih dekat, dan saya menemukan bahwa dengan n set ke 1000, penutupan dipanggil sekitar 11.000 kali.

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

Ini bukan fungsi yang efisien, dan saya akan merekomendasikan menggunakan implementasi fungsi penyortiran yang lebih baik.

EDIT:

Saya melihat halaman wikipedia Quicksort dan menulis implementasi Swift untuk itu. Ini adalah program lengkap yang saya gunakan (di taman bermain)

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
}

Menggunakan ini dengan n = 1000, saya menemukan itu

  1. quickSort () dipanggil sekitar 650 kali,
  2. sekitar 6000 swap dibuat,
  3. dan ada sekitar 10.000 perbandingan

Tampaknya metode pengurutan bawaan (atau hampir) pengurutan cepat, dan sangat lambat ...

David Skrundz
sumber
17
Mungkin saya benar-benar salah, tetapi menurut en.wikipedia.org/wiki/Quicksort , jumlah rata-rata perbandingan di Quicksort adalah 2*n*log(n). Itu adalah 13815 perbandingan untuk menyortir n = 1000 elemen, jadi jika fungsi perbandingan dipanggil sekitar 11000 kali itu sepertinya tidak terlalu buruk.
Martin R
6
Apple juga mengklaim bahwa "objek kompleks" (apa pun itu) 3,9 kali lebih cepat di Swift daripada di Python. Oleh karena itu tidak perlu menemukan "fungsi penyortiran yang lebih baik". - Tapi Swift masih dalam pengembangan ...
Martin R
6
Itu memang merujuk pada logaritma natural.
Martin R
24
log(n)untuk kompleksitas algoritmik secara konvensional mengacu pada log base-2. Alasan untuk tidak menyatakan dasar adalah bahwa perubahan hukum dasar untuk logaritma hanya memperkenalkan pengganda konstan, yang dibuang untuk keperluan notasi-O.
minuteman3
3
Mengenai diskusi tentang logaritma natural vs logaritma base 2: Pernyataan yang tepat dari halaman Wikipedia adalah bahwa jumlah rata-rata perbandingan yang diperlukan untuk n elemen adalah C(n) = 2n ln n ≈ 1.39n log₂ n. Untuk n = 1000 ini menghasilkan C (n) = 13815, dan ini bukan "notasi O besar".
Martin R
18

Pada Xcode 7 Anda dapat menghidupkan Fast, Whole Module Optimization. Ini akan segera meningkatkan kinerja Anda.

masukkan deskripsi gambar di sini

Antoine
sumber
12

Kinerja Swift Array ditinjau kembali:

Saya menulis tolok ukur saya sendiri membandingkan Swift dengan C / Objective-C. Tolok ukur saya menghitung bilangan prima. Ia menggunakan array bilangan prima sebelumnya untuk mencari faktor prima di setiap kandidat baru, sehingga cukup cepat. Namun, ia melakukan BANYAK membaca array, dan kurang menulis ke array.

Saya awalnya melakukan patokan ini terhadap Swift 1.2. Saya memutuskan untuk memperbarui proyek dan menjalankannya melawan Swift 2.0.

Proyek ini memungkinkan Anda memilih antara menggunakan array swift normal dan menggunakan buffer memori Swift yang tidak aman menggunakan semantik array.

Untuk C / Objective-C, Anda bisa memilih untuk menggunakan NSArrays, atau array C malloc'ed.

Hasil pengujian tampaknya sangat mirip dengan optimasi kode tercepat, terkecil ([-0s]) atau optimasi tercepat, agresif ([-0fast]).

Kinerja Swift 2.0 masih mengerikan dengan optimasi kode dimatikan, sedangkan kinerja C / Objective-C hanya lebih lambat.

Intinya adalah bahwa perhitungan berbasis array C malloc'd adalah yang tercepat, dengan margin sederhana

Swift dengan buffer yang tidak aman membutuhkan waktu sekitar 1,19X - 1,20X lebih lama dari array C malloc'd saat menggunakan optimasi kode tercepat dan terkecil. perbedaannya tampak sedikit kurang dengan optimasi yang cepat dan agresif (Swift membutuhkan waktu lebih lama dari 1,18x hingga 1,16x lebih lama dari C.

Jika Anda menggunakan array Swift biasa, perbedaan dengan C sedikit lebih besar. (Swift membutuhkan waktu ~ 1,22 hingga 1,23 lebih lama.)

Array Swift biasa DRAMATICALLYlebih cepat daripada yang ada di Swift 1.2 / Xcode 6. Performa mereka sangat dekat dengan array berbasis buffer Swift yang tidak aman sehingga menggunakan buffer memori yang tidak aman tidak terlalu lagi berarti masalah, yang besar.

BTW, kinerja Objective-C NSArray bau. Jika Anda akan menggunakan objek penampung asli dalam kedua bahasa, Swift secara DRAMATICALLY lebih cepat.

Anda dapat memeriksa proyek saya di github di SwiftPerformanceBenchmark

Ini memiliki UI sederhana yang membuat pengumpulan statistik sangat mudah.

Sangat menarik bahwa menyortir tampaknya sedikit lebih cepat di Swift daripada di C sekarang, tetapi algoritma bilangan prima ini masih lebih cepat di Swift.

Duncan C
sumber
8

Masalah utama yang disebutkan oleh orang lain tetapi tidak cukup disebut adalah bahwa -O3tidak melakukan apa-apa di Swift (dan tidak pernah memiliki) sehingga ketika dikompilasi dengan itu secara efektif tidak dioptimalkan ( -Onone).

Nama opsi telah berubah dari waktu ke waktu sehingga beberapa jawaban lain memiliki bendera yang sudah usang untuk opsi pembuatan. Pilihan saat ini yang benar (Swift 2.2) adalah:

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

Optimalisasi modul secara keseluruhan memiliki kompilasi yang lebih lambat tetapi dapat mengoptimalkan seluruh file dalam modul yaitu dalam setiap kerangka kerja dan dalam kode aplikasi yang sebenarnya tetapi tidak di antara mereka. Anda harus menggunakan ini untuk kinerja yang kritis)

Anda juga dapat menonaktifkan pemeriksaan keamanan untuk kecepatan yang lebih tinggi tetapi dengan semua pernyataan dan prasyarat tidak hanya dinonaktifkan tetapi dioptimalkan dengan dasar bahwa mereka benar. Jika Anda pernah menekan suatu pernyataan, ini berarti Anda melakukan perilaku yang tidak terdefinisi. Gunakan dengan sangat hati-hati dan hanya jika Anda menentukan bahwa peningkatan kecepatan bermanfaat untuk Anda (dengan menguji). Jika Anda merasa berharga untuk beberapa kode saya sarankan memisahkan kode itu ke dalam kerangka kerja yang terpisah dan hanya menonaktifkan pemeriksaan keamanan untuk modul itu.

Joseph Lord
sumber
Jawaban ini sekarang kedaluwarsa. Pada Swift 4.1, opsi optimalisasi seluruh modul adalah boolean terpisah yang dapat dikombinasikan dengan pengaturan lain dan sekarang ada -O untuk mengoptimalkan ukuran. Saya dapat memperbarui ketika saya punya waktu untuk memeriksa flag opsi yang tepat.
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) 

Ini adalah Blog saya tentang Quick Sort- Contoh Github Quick-Sort

Anda dapat melihat algoritma pemartisian Lomuto di Mempartisi daftar. Ditulis dalam Swift.

Abo3atef
sumber
4

Swift 4.1 memperkenalkan -Osizemode optimisasi baru .

Dalam Swift 4.1, kompiler sekarang mendukung mode optimisasi baru yang memungkinkan optimisasi khusus untuk mengurangi ukuran kode.

Kompiler Swift hadir dengan optimasi yang kuat. Ketika mengkompilasi dengan -O kompiler mencoba mengubah kode sehingga dieksekusi dengan kinerja maksimum. Namun, peningkatan kinerja runtime ini kadang-kadang bisa disertai dengan peningkatan ukuran kode. Dengan mode optimasi -Osize baru pengguna memiliki pilihan untuk mengkompilasi untuk ukuran kode minimal daripada untuk kecepatan maksimum.

Untuk mengaktifkan mode optimisasi ukuran pada baris perintah, gunakan -Osize bukan -O.

Bacaan lebih lanjut: https://swift.org/blog/osize/

casillas
sumber