Виконання бета-версії Swift: сортування масивів


929

Я впроваджував алгоритм у Swift Beta і помітив, що продуктивність дуже низька. Після глибокого копання я зрозумів, що одне із вузьких місць було чимось таким простим, як сортування масивів. Відповідна частина тут:

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 ++ подібна операція займає 0,06 секунди на моєму комп’ютері.

У Python він займає 0,6s (ніяких хитрощів, просто y = відсортовано (x) для списку цілих чисел).

У Swift він займає 6s, якщо я компілюю його з такою командою:

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

І це займає цілих 88, якщо я компілюю його з такою командою:

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

Часи в Xcode зі збірками "Release" та "Debug" схожі.

Що тут не так? Я міг зрозуміти деяку втрату продуктивності порівняно з C ++, але не 10-кратне уповільнення порівняно з чистим Python.


Edit: погода зауважив , що зміна -O3до -Ofastробить цей код запуску майже так само швидко , як C ++ версії! Однак -Ofastсильно змінює семантику мови - в моєму тестуванні вона відключила перевірки на цілі переповнення та переповнення індексації масиву . Наприклад, із -Ofastтаким кодом Swift працює безшумно, без збоїв (і виводить сміття):

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

Так -Ofastце не те, що ми хочемо; Вся суть Свіфта полягає в тому, що у нас є захисні мережі. Звичайно, захисні мережі мають певний вплив на продуктивність, але вони не повинні робити програми в 100 разів повільнішими. Пам’ятайте, що Java вже перевіряє межі масиву, і в типових випадках уповільнення є на набагато менший показник 2. І в Clang і GCC ми отримали -ftrapvдля перевірки (підписані) цілі переповнення, і це не так повільно.

Звідси виникає питання: як ми можемо досягти розумних показників роботи в Swift, не втрачаючи мереж безпеки?


Редагування 2: Я зробив ще кілька бенчмаркінгу, з дуже простими петлями по лініях

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

. до цілих переливів.)

Знову була велика різниця у виконанні між -O3та -Ofast. Тому я подивився код складання:

  • З -Ofastя отримую майже те, що я очікував. Відповідна частина - це цикл з 5 машинними мовними інструкціями.

  • З цим -O3я отримую щось, що було поза моєю найсміливішою уявою. Внутрішня петля охоплює 88 рядків коду складання. Я не намагався зрозуміти все це, але найбільш підозрілі частини - це 13 викликів "callq _swift_retain" та ще 13 викликів "callq _swift_release". Тобто 26 викликів підпрограми у внутрішньому циклі !


Редагувати 3: У коментарях Ферруччо попросив справедливих орієнтирів у тому сенсі, що вони не покладаються на вбудовані функції (наприклад, сортування). Я думаю, що наступна програма є досить хорошим прикладом:

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

Арифметики немає, тому нам не потрібно турбуватися про цілі переповнення. Єдине, що ми робимо - це просто багато посилань на масив. І результати тут - Swift -O3 втрачає майже в 500 разів порівняно з -Ofast:

  • C ++ -O3: 0,05 с
  • C ++ -O0: 0,4 с
  • Ява: 0,2 с
  • Пітон з PyPy: 0,5 с
  • Пітон: 12 с
  • Швидкий - швидкий: 0,05 с
  • Швидкий -O3: 23 с
  • Швидкий -O0: 443 с

(Якщо ви стурбовані тим, що компілятор може повністю оптимізувати безглузді цикли, ви можете змінити його, наприклад x[i] ^= x[j], і додати операцію друку, яка виводить x[0]. Це нічого не змінює; терміни будуть дуже схожими.)

І так, тут реалізація Python була дурною чистою реалізацією Python зі списком вкладених і вкладених циклів. Це повинно бути набагато повільніше, ніж неоптимізований Свіфт. Щось, здається, серйозно порушено із індексацією Swift та масиву.


Редагувати 4: Ці проблеми (як і деякі інші питання щодо продуктивності), схоже, були виправлені в Xcode 6 beta 5.

Для сортування у мене зараз такі терміни:

  • кланг ++ -O3: 0,06 с
  • swiftc -швидкий: 0,1 с
  • swiftc -O: 0,1 с
  • swiftc: 4 с

Для вкладених циклів:

  • кланг ++ -O3: 0,06 с
  • swiftc -швидкий: 0,3 с
  • swiftc -O: 0,4 с
  • swiftc: 540 с

Здається, більше немає причин використовувати небезпечну -Ofast(ака -Ounchecked); звичайний -Oвиробляє однаково хороший код.


20
Ось ще одне питання "Швидкий у 100 разів повільніше C": stackoverflow.com/questions/24102609/…
Jukka Suomela

16
І ось обговорення маркетингових матеріалів Apple, пов’язаних із хорошими показниками Свіфт у сортуванні: programmers.stackexchange.com/q/242816/913
Jukka Suomela

2
Ви можете скомпілювати з: xcrun --sdk macosx swift -O3. Це коротше.
Південна гостинність

3
Це посилання показує деякі інші основні операції порівняно з Objective-C.
Wold

4
З Beta 5 відбулося суттєве покращення швидкості Свіфта - детальніше див. Цей пост Джессі Сквайрса .
Нейт Кук

Відповіді:


460

tl; dr Swift 1.0 тепер настільки ж швидкий, як і C за цим еталоном, використовуючи рівень оптимізації випуску за замовчуванням [-O].


Ось місцевий швидкодіючий комбінат у 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)
}

І те саме в 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);
}

Обидва працюють:

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]

Обидва викликаються в тій же програмі, що і написана.

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

Це перетворює абсолютний час у секунди:

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

Ось коротка інформація про рівні оптимізації компілятора:

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

Час у секундах з [-Onone] за n = 10_000 :

Swift:            0.895296452
C:                0.001223848

Ось побудований Swift sort () для n = 10_000 :

Swift_builtin:    0.77865783

Ось [-O] для n = 10_000 :

Swift:            0.045478346
C:                0.000784666
Swift_builtin:    0.032513488

Як бачите, продуктивність Swift покращилася в 20 разів.

Відповідно до відповіді mweathers , встановлення [-Ofast] робить реальну різницю, в результаті чого для n = 10_000 в цей час :

Swift:            0.000706745
C:                0.000742374
Swift_builtin:    0.000603576

А для n = 1_000_000 :

Swift:            0.107111846
C:                0.114957179
Swift_sort:       0.092688548

Для порівняння, це з [-Onone] для n = 1_000_000 :

Swift:            142.659763258
C:                0.162065333
Swift_sort:       114.095478272

Таким чином, Свіфт без оптимізацій був майже в 1000 разів повільніше, ніж C у цьому етапі, на цьому етапі свого розвитку. З іншого боку, обидва компілятори, встановлені на [-Ofast], Swift насправді виконував принаймні так само добре, якщо не трохи краще, ніж C.

Було зазначено, що [-Ofast] змінює семантику мови, роблячи її потенційно небезпечною. Ось що зазначає Apple у примітках до випуску Xcode 5.0:

Новий рівень оптимізації -Ofast, доступний в LLVM, дає можливість агресивних оптимізацій. -Постійне послаблює деякі консервативні обмеження, в основному для операцій з плаваючою комою, безпечних для більшості кодів. Це може отримати значні високоефективні виграші від компілятора.

Вони всі, окрім того, відстоюють це. Це розумно чи ні, я не можу сказати, але з того, що я можу сказати, здається досить розумним використовувати [-Ofast] у випуску, якщо ви не виконуєте арифметику з плаваючою точкою високої точності і не впевнені, що немає цілого чи у вашій програмі можливі переповнення масивів. Якщо вам потрібні високі показники продуктивності та переповнення / точна арифметика, тоді виберіть іншу мову.

ОНОВЛЕННЯ БЕТА 3:

n = 10_000 з [-O] :

Swift:            0.019697268
C:                0.000718064
Swift_sort:       0.002094721

Свіфт взагалі трохи швидше, і схоже, що вбудований сорт Свіфта змінився досить суттєво.

ПІДСУМКОВА ОНОВЛЕННЯ:

[-Онон] :

Swift:   0.678056695
C:       0.000973914

[-O] :

Swift:   0.001158492
C:       0.001192406

[-Перевірено] :

Swift:   0.000827764
C:       0.001078914

25
Використання -emit-sil для виведення проміжного коду SIL показує, що зберігається (argh, переповнення стека робить неможливим форматування). Це внутрішній буферний об’єкт у масиві. Це безумовно звучить як помилка оптимізатора, оптимізатор ARC повинен бути в змозі видалити фіксатори без -Ofast.
Catfish_Man

Я просто не погоджуюся, що нам доведеться використовувати іншу мову, якщо ми хочемо використовувати оптимізацію Ofast. Аналогічно доведеться вирішувати питання про перевірку меж та інші незначні проблеми, якщо вибирати іншу мову, наприклад C. Швидкий крутий саме тому, що він має бути захищеним за замовчуванням, а також, можливо, швидким та незахищеним, якщо потрібно. Це дозволяє програмісту відладжувати ваш код також, щоб переконатися, що все в порядку, і компілювати за допомогою Ofast. Можливість використання сучасних стандартів і в той же час мати силу "небезпечної" мови на зразок C дуже класна.
Wallacy

2
якщо ви можете сказати мені, як це може бути недійсним, будь ласка, зробіть це. мені завжди подобається дізнатися більше
Джозеф Марк

3
зробив остаточне оновлення, Swift тепер настільки ж швидкий, як і Ц за цим еталоном, використовуючи стандартні оптимізації.
Джозеф Марк

4
Порада: І ваші реалізації Swift, і C з Quicksort можна вдосконалити, якщо спочатку будете повторювати найменший розділ! (Замість того, щоб спочатку повторювати лівий розділ завжди.) Quicksort, що реалізується простим підбором зсуву, у гіршому випадку займає час O (n ^ 2), але навіть у цьому гіршому випадку вам потрібен лише простір стека O (log n) шляхом повторного запису на меншій перегородці спочатку.
Macneil Shonle

108

TL; DR : Так, реалізація тільки Swift мову повільно, прямо зараз . Якщо вам потрібен швидкий, числовий (та інші типи коду, імовірно) код, просто перейдіть з іншим. Надалі слід переоцінити свій вибір. Це може бути досить добре для більшості кодів програми, написаних на більш високому рівні.

З того, що я бачу в SIL та LLVM IR, схоже, їм потрібна купа оптимізацій для видалення файлів retain та релізів, які можуть бути реалізовані в Clang (для Objective-C), але вони ще не перенесли їх. Це теорія, з якою я збираюся (поки що ... мені все одно потрібно підтвердити, що Кланг щось з цим робить), оскільки профайлер, що працює на останньому тестовому випадку цього питання, дає такий "гарний" результат:

Профілювання часу на -O3 Профілювання часу на - швидкий

Як сказали багато інших, -Ofastце абсолютно небезпечно і змінює мовну семантику. Для мене це знаходиться на етапі "Якщо ти будеш використовувати це, просто використовуй іншу мову". Я переоцінюю цей вибір пізніше, якщо він зміниться.

-O3отримує нам купу swift_retainі swift_releaseзакликає, якщо чесно, не схожий на те, що вони повинні бути там для цього прикладу. Оптимізатор повинен був ухилитись (більшість) з них AFAICT, оскільки він знає більшість інформації про масив і знає, що він має (принаймні) чітке посилання на нього.

Він не повинен випромінювати більше затримок, якщо навіть не викликає функції, які можуть звільнити об'єкти. Я не думаю, що конструктор масиву може повернути масив, менший від того, що було запропоновано, а це означає, що багато перевірених даних перевірок марно. Він також знає, що ціле число ніколи не буде вище 10 к, тому перевірки на переповнення можна оптимізувати (не через -Ofastдивацтво, а через семантику мови (нічого іншого не змінюється, що вар не може отримати до нього доступ, і додавши до 10 к безпечний для типу Int).

Компілятор, можливо, не зможе розблокувати масив або елементи масиву, однак, оскільки вони переходять до цього sort(), що є зовнішньою функцією і має отримати аргументи, які він очікує. Це змусить нас використовувати Intзначення опосередковано, що зробило б це трохи повільніше. Це могло б змінитися, якщо sort()загальна функція (не багато методом) була доступна компілятору і отримала вподобання.

Це дуже нова (загальнодоступна) мова, і вона переживає те, що я припускаю, що це багато змін, оскільки люди (сильно) залучені до мови Swift просять відгуку, і всі вони кажуть, що мова не закінчена і буде змінити.

Використовуваний код:

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: Я не фахівець з "Objective-C", ані всіх об'єктів з какао , "Objective-C" або "Swift". Я також можу припустити деякі речі, які я не написав.


Компілятор, можливо, не зможе розблокувати масив або елементи масиву, однак, оскільки вони передаються до сортування (), що є зовнішньою функцією і має отримати аргументи, які він очікує. Це не має значення для порівняно хорошого укладача. Передача метаданих (у покажчику - 64 біт пропонують багато рівня) про фактичні дані та розгалуження їх у викликаній функції.
bestsss

3
Що саме робить -Ofast"абсолютно небезпечним"? Припустимо, що ви знаєте, як перевірити свій код і виключити переливи.
Джозеф Марк

@sjeohp: Це насправді передбачає багато :-) Перевірка коду та виключення переповнення важко зробити. З мого досвіду (я працюю над компілятором і перевірив кілька великих кодових баз), і те, що я чув від людей, які займаються компілятором, працюють над величезними компаніями, отримуючи переповнення та інше невизначене правильне поведінку, важко . Навіть порада Apple (лише приклад) щодо виправлення UB помилкова, іноді ( randomascii.wordpress.com/2014/04/17/… ). -Ofastтакож змінює мовну семантику, але я не можу фінансувати жодних документів для цього. Як ви можете бути впевнені, що знаєте, що це робить?
filcab

@bestsss: Це можливо, але це може бути не корисно. Він додає перевірки кожного доступу до Int []. Це залежить, якщо масиви Int та кілька інших примітивних типів (у вас є, максимум, 3 біти) багато використовуються (особливо коли ви можете опуститись до C, якщо вам потрібно). Він також використовує деякі біти, які вони, можливо, захочуть використовувати, якщо зрештою вони хочуть додати не-ARC GC. Він не масштабується до дженериків, які містять більше одного аргументу. Оскільки вони мають усі типи, було б набагато простіше спеціалізувати весь код, який торкнувся Int [] (але не Int? []) Для використання вбудованого Int. Але тоді у вас є інтероп Obj-C.
filcab

@filcab, не-ARC (тобто реальна) GC була б дійсно корисною, але їм потрібно щось, що не сумісне з C, якщо вони хочуть по-справжньому одночасно не-STW GC. Я б не турбувався про "кожен доступ до Int[]", оскільки це залежить від рівня, який компілятор може вбудувати, і він повинен мати можливість вбудовувати тугі петлі за допомогою / після деяких вказівок.
bestsss

53

Я вирішив поглянути на це заради розваги, і ось які моменти часу я отримую:

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

Швидкий

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

Результати:

Швидкий 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

Швидкий 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

Швидкий 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

Здається, це те саме виконання, якщо я компілюю -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 до Swift 3.0, і я також бачу різницю між -Oі -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 знову покращує продуктивність, зберігаючи проміжок між -Oта -Ounchecked. -O -whole-module-optimizationне виявилося, щоб змінити значення.

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

Результати:

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

Вирок

Станом на час написання цього тексту, сорт Свіфта швидкий, але ще не такий швидкий, як сортування C ++ при компілюванні з -Oвищезазначеними компіляторами та бібліотеками. З -Ounchecked, схоже, він такий же швидкий, як C ++ у Swift 4.0.2 та Apple LLVM 9.0.0.


2
Насправді ви ніколи не повинні викликати вектор :: резерв () перед тим, як вставити десять мільйонів елементів.
BJovke

Можливо! На даний момент приурочений лише сорт.
Дізнайтеся OpenGL ES

34

Від The Swift Programming Language:

Стандартна бібліотека функції сортування Swift надає функцію під назвою сортування, яка сортує масив значень відомого типу, виходячи з виводу закритого вами сортування. Після того, як він завершить процес сортування, функція сортування повертає новий масив того ж типу та розміру, що і старий, з його елементами у правильному відсортованому порядку.

sortФункція має два оголошення.

Декларація за замовчуванням, яка дозволяє вказати завершення порівняння:

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

І друге оголошення, яке приймає лише один параметр (масив) і "жорстко кодується для використання менш порівняного порівняння".

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

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

Я протестував модифіковану версію вашого коду на ігровому майданчику із доданим закриттям, щоб я міг уважніше стежити за функцією, і я виявив, що з n, встановленим у 1000, закриття викликали приблизно 11 000 разів.

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

Це не ефективна функція, я б рекомендував використовувати кращу функцію сортування.

Редагувати:

Я переглянув сторінку вікіпедії Quicksort і написав для неї реалізацію Swift. Ось повна програма, яку я використав (на дитячому майданчику)

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
}

Використовуючи це з n = 1000, я знайшов це

  1. quickSort () телефонували приблизно 650 разів,
  2. було здійснено близько 6000 свопів,
  3. і є близько 10 000 порівнянь

Здається, що вбудований метод сортування (або близький до) швидкого сортування, і це дуже повільно ...


17
Можливо, я абсолютно помиляюся, але згідно з en.wikipedia.org/wiki/Quicksort , середня кількість порівнянь у Quicksort є 2*n*log(n). Це 13815 порівнянь для сортування n = 1000 елементів, тож якщо функція порівняння викликається приблизно 11000 разів, це не здається поганим.
Мартін Р

6
Також Apple заявляла, що "складний тип об'єктів" (що б там не було) у Swift в 3,9 рази швидший, ніж у Python. Тому не слід шукати "кращу функцію сортування". - Але Свіфт все ще розвивається ...
Мартін Р

6
Це дійсно відноситься до натурального логарифму.
Мартін Р

24
log(n)для алгоритмічної складності умовно відноситься до журналу бази-2. Причина невказання бази полягає в тому, що закон про зміну бази для логарифмів вводить лише постійний множник, який відкидається для цілей O-позначення.
minuteman3

3
Щодо дискусії про природний логарифм проти логарифму бази 2: Точне твердження зі сторінки Вікіпедії полягає в тому, що середня кількість порівнянь, необхідних для n елементів, становить C(n) = 2n ln n ≈ 1.39n log₂ n. Для n = 1000 це дає C (n) = 13815, і це не "нотація big-O".
Мартін Р

18

Станом на Xcode 7 ви можете увімкнути Fast, Whole Module Optimization. Це повинно негайно підвищити вашу ефективність.

введіть тут опис зображення


12

Переглянуто продуктивність Swift Array:

Я написав власний орієнтир, порівнюючи Swift з C / Objective-C. Мій орієнтир обчислює прості числа. Він використовує масив попередніх простих чисел для пошуку простих факторів у кожного нового кандидата, тому це досить швидко. Однак він робить TONS зчитування масиву і менше запису в масиви.

Я спочатку робив цей показник проти Swift 1.2. Я вирішив оновити проект і запустити його проти Swift 2.0.

Проект дозволяє вибирати між звичайними масивами швидкого доступу та використанням небезпечних буферів пам'яті Swift за допомогою семантики масиву.

Для C / Objective-C ви можете або використовувати NSArrays, або C malloc'ed масиви.

Результати тестів здаються досить схожими на швидку, найменшу оптимізацію коду ([-0s]) або швидку, агресивну ([-0fast]) оптимізацію.

Продуктивність Swift 2.0 все ще жахлива, коли оптимізація коду вимкнена, тоді як продуктивність C / Objective-C лише помірно повільніше.

Суть полягає в тому, що обчислення на основі масиву C malloc'd є найшвидшими, з невеликим запасом

Швидке використання небезпечних буферів займає 1,19X - 1,20X довше масивів C malloc'd при використанні найшвидшої та найменшої оптимізації коду. різниця здається дещо меншою при швидкій, агресивній оптимізації (Swift займає більше, ніж 1,18x до 1,16x довше, ніж C.

Якщо ви використовуєте регулярні масиви Swift, різниця з C трохи більша. (Свіфт займає від 1,22 до 1,23 більше.)

Регулярні масиви Swift є DRAMATICALLY швидші, ніж вони були у Swift 1.2 / Xcode 6. Їх продуктивність настільки близька до масивів на основі небезпечних буферів Swift, що використання небезпечних буферів пам’яті справді не стоїть проблеми, що велике.

До речі, смерть продуктивності NSArray від Objective-C. Якщо ви збираєтесь використовувати власні об'єкти контейнерів на обох мовах, Swift швидше DRAMATICALLY .

Ви можете ознайомитись з моїм проектом на github на SwiftPerformanceBenchmark

Він має простий інтерфейс, який робить збір статистики досить простим.

Цікаво, що сортування здається, що у Swift трохи швидше, ніж у C, але цей алгоритм простого числення все ж швидший у Swift.


8

Основне питання, яке згадують інші, але недостатньо називається, це -O3 у Swift (і ніколи не взагалі нічого не робиться, тому при компіляції це ефективно не оптимізоване ( -Onone).

Імена варіантів з часом змінювалися, тому деякі інші відповіді мають застарілі прапори для параметрів збірки. Правильними поточними параметрами (Swift 2.2) є:

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

Оптимізація цілого модуля має більш повільну компіляцію, але може оптимізувати файли всередині модуля, тобто в кожному рамках та в реальному коді програми, але не між ними. Ви повинні використовувати це для будь-якої критичної продуктивності)

Ви також можете відключити перевірку безпеки на ще більшу швидкість, але з усіма твердженнями та передумовами не просто вимкнено, але оптимізовано на основі правильності. Якщо ви коли-небудь стикаєтесь із твердженням, це означає, що ви перебуваєте у невизначеній поведінці. Використовуйте з особливою обережністю і лише в тому випадку, якщо визначите, що підвищення швидкості для вас варто (тестуючи). Якщо ви вважаєте його цінним для якогось коду, я рекомендую відокремити цей код в окремий фреймворк і виключити лише перевірки безпеки для цього модуля.


Ця відповідь застаріла. Станом на Swift 4.1, весь варіант оптимізації модуля є окремим булевим, який можна комбінувати з іншими налаштуваннями, і тепер є -O для оптимізації за розміром. Я можу оновитись, коли встигну перевірити точні прапорці опцій.
Йосип Лорд

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) 

Це мій блог про Швидке Сортування - зразок Github Швидке Сортування

Ви можете подивитися алгоритм розподілу Lomuto в розділі списку. Написано у Swift.


4

Swift 4.1 вводить новий -Osizeрежим оптимізації.

У Swift 4.1 тепер компілятор підтримує новий режим оптимізації, який дозволяє спеціалізованим оптимізаціям зменшити розмір коду.

Компілятор Swift оснащений потужними оптимізаціями. При компіляції з -O компілятор намагається перетворити код так, щоб він виконувався з максимальною продуктивністю. Однак таке поліпшення продуктивності роботи іноді може спричинити компроміс збільшеного розміру коду. У новому режимі оптимізації -Osize користувач може вибрати компіляцію для мінімального розміру коду, а не для максимальної швидкості.

Щоб увімкнути режим оптимізації розміру в командному рядку, використовуйте -Osize замість -O.

Подальше читання: https://swift.org/blog/osize/

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.