Быстрая бета-версия: сортировка массивов

928

Я реализовывал алгоритм в 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,6 с (без трюков, просто y = sorted (x) для списка целых чисел).

В Swift это займет 6 секунд, если я скомпилирую его с помощью следующей команды:

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 ++, но не десятикратное замедление по сравнению с чистым Python.


Редактировать: погода заметила, что переход -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не то, что мы хотим; Суть Swift в том, что у нас есть защитные сетки. Конечно, системы безопасности оказывают некоторое влияние на производительность, но они не должны делать программы в 100 раз медленнее. Помните, что Java уже проверяет границы массивов, и в типичных случаях замедление в два раза меньше. И в Clang и GCC мы получили -ftrapvпроверку целочисленных переполнений (со знаком), и это тоже не так медленно.

Отсюда вопрос: как мы можем получить разумную производительность в Swift, не теряя сетей безопасности?


Редактировать 2: я сделал еще несколько тестов, с очень простыми циклами по линии

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

(Здесь операция xor есть просто для того, чтобы мне было легче найти соответствующий цикл в коде сборки. Я попытался выбрать операцию, которую легко обнаружить, но при этом «безвредную» в том смысле, что она не должна требовать каких-либо проверок, связанных с к целочисленным переполнениям.)

Опять же, была огромная разница в производительности между -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 с
  • Python с PyPy: 0,5 с
  • Питон: 12 с
  • Быстрое-быстрое: 0,05 с
  • Swift -O3: 23 с
  • Swift -O0: 443 с

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

И да, здесь реализация Python была глупой чистой реализацией Python со списком целых и вложенных в циклы. Это должно быть намного медленнее, чем неоптимизированный Swift. Кажется, что-то серьезно сломано с помощью Swift и индексации массивов.


Редактировать 4: Эти проблемы (а также некоторые другие проблемы с производительностью), кажется, были исправлены в Xcode 6 бета 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 раз медленнее, чем С»: stackoverflow.com/questions/24102609/…
Юкка Суомела,
16
А вот обсуждение маркетингового материала Apple, связанного с хорошей производительностью Swift в сортировке: programmers.stackexchange.com/q/242816/913
Юкка Суомела,
2
Вы можете скомпилировать с: xcrun --sdk macosx swift -O3. Это короче.
Южное гостеприимство
3
Эта ссылка показывает некоторые другие основные операции по сравнению с Objective-C.
Wold
4
В бета-версии 5 скорость Swift значительно улучшилась - см. Этот пост Джесси Сквайрс для более подробной информации.
Нейт Кук,

Ответы:

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 () для 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

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

Было отмечено, что [-Ofast] изменяет семантику языка, делая его потенциально небезопасным. Вот что Apple заявляет в заметках о выпуске Xcode 5.0:

Новый уровень оптимизации - Fast, доступный в LLVM, обеспечивает агрессивную оптимизацию. -Ofast ослабляет некоторые консервативные ограничения, в основном для операций с плавающей запятой, которые безопасны для большей части кода. Это может дать значительные высокопроизводительные выигрыши от компилятора.

Они почти защищают это. Будь это мудро или нет, я не могу сказать, но из того, что я могу сказать, кажется достаточно разумным использовать [-Ofast] в релизе, если вы не выполняете высокоточную арифметику с плавающей запятой и уверены, что не целое число или в вашей программе возможно переполнение массива. Если вам нужна высокая производительность и проверки переполнения / точная арифметика, выберите другой язык.

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

n = 10_000 с [-O] :

Swift:            0.019697268
C:                0.000718064
Swift_sort:       0.002094721

Swift в целом немного быстрее, и похоже, что встроенная сортировка Swift значительно изменилась.

ЗАКЛЮЧИТЕЛЬНОЕ ОБНОВЛЕНИЕ:

[-Onone] :

Swift:   0.678056695
C:       0.000973914

[-O] :

Swift:   0.001158492
C:       0.001192406

[-Ounchecked] :

Swift:   0.000827764
C:       0.001078914
Джозеф Марк
источник
25
Использование -emit-sil для вывода промежуточного кода SIL показывает, что сохраняется (ага, переполнение стека делает невозможным форматирование). Это внутренний буферный объект в массиве. Это определенно звучит как ошибка оптимизатора, оптимизатор ARC должен быть в состоянии удалить остатки без -Ofast.
Catfish_Man
Я просто не согласен с тем, что мы должны использовать другой язык, если хотите использовать оптимизацию Ofast. При выборе другого языка, такого как C., ему придется аналогичным образом решить вопрос о проверке границ и других мелких проблем. Swift - это круто именно потому, что он должен быть безопасным по умолчанию и, при необходимости, быстрым и небезопасным, если это необходимо. Это позволяет программисту также отлаживать ваш код, чтобы убедиться, что все в порядке, и скомпилировать с помощью Ofast. Возможность использования современных стандартов и все же обладать мощью «небезопасного» языка, такого как C, очень крута.
Уолласи
2
если вы можете сказать мне, как это может быть недействительным, пожалуйста, сделайте. Я всегда хотел бы узнать больше
Джозеф Марк
3
В последнем обновлении Swift теперь работает так же быстро, как и C, с помощью стандартных оптимизаций.
Джозеф Марк
4
Подсказка: обе ваши реализации Swift и C быстрой сортировки могут быть улучшены, если вы сначала рекурсивно выбираете самый маленький раздел! (Вместо рекурсии в левом разделе всегда сначала.) Быстрая сортировка, реализованная с помощью простой сводной выборки, в худшем случае занимает O (n ^ 2) времени, но даже в этом худшем случае вам нужно только O (log n) стекового пространства путем рекурсии сначала на меньшем разделе.
Макнейл Шонл
108

TL; DR : Да, единственная реализация языка Swift медленная, прямо сейчас . Если вам нужен быстрый, числовой (и, возможно, другие типы кода) код, просто перейдите к другому. В будущем вам следует пересмотреть свой выбор. Это может быть достаточно для большинства приложений, написанных на более высоком уровне.

Из того, что я вижу в SIL и LLVM IR, кажется, что им нужна куча оптимизаций для удаления сохранений и выпусков, которые могут быть реализованы в Clang (для Objective-C), но они еще не перенесли их. Это теория, которой я придерживаюсь (на данный момент… мне все еще нужно подтвердить, что Clang что-то с этим делает), поскольку профилировщик, запущенный в последнем тестовом примере этого вопроса, дает этот «симпатичный» результат:

Время профилирования на -O3 Время профилирования на -Ofast

Как говорили многие другие, -Ofastэто совершенно небезопасно и меняет семантику языка. Для меня это на стадии «Если вы собираетесь использовать это, просто используйте другой язык». Я пересмотрю этот выбор позже, если он изменится.

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

Он не должен выдавать больше значений, даже если он не вызывает функции, которые могут освобождать объекты. Я не думаю, что конструктор массива может вернуть массив, размер которого меньше запрашиваемого, а это означает, что многие выполненные проверки бесполезны. Он также знает, что целое число никогда не будет больше 10k, поэтому проверки переполнения могут быть оптимизированы (не из-за -Ofastстранности, а из-за семантики языка (ничто иное не меняет этот var и не может получить к нему доступ, и добавляет до 10k). безопасно для типа 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. Я мог бы также предположить некоторые вещи, которые я не писал.

filcab
источник
Однако компилятор не сможет распаковать массив или элементы массива, поскольку они передаются в sort (), которая является внешней функцией и должна получить ожидаемые аргументы. Это не должно иметь значения для относительно хорошего компилятора. Передача метаданных (в указателе - 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[]», поскольку это зависит от уровня, который может встроить компилятор, и он должен иметь возможность встроить узкие циклы с / после некоторого руководства.
14:00
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()

Результаты:

Swift 1.1

xcrun swiftc --version
Swift version 1.1 (swift-600.0.54.20)
Target: x86_64-apple-darwin14.0.0

xcrun swiftc -O SwiftSort.swift
./SwiftSort     
Elapsed time: 1.02204304933548

Swift 1.2

xcrun swiftc --version
Apple Swift version 1.2 (swiftlang-602.0.49.6 clang-602.0.49)
Target: x86_64-apple-darwin14.3.0

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

Swift 2.0

xcrun swiftc --version
Apple Swift version 2.0 (swiftlang-700.0.59 clang-700.0.72)
Target: x86_64-apple-darwin15.0.0

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

Кажется, это будет та же производительность, если я скомпилирую -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

решение суда

На момент написания этой статьи сортировка Swift была быстрой, но еще не такой быстрой, как сортировка C ++ при компиляции -Oс вышеуказанными компиляторами и библиотеками. При этом -Ouncheckedон выглядит так же быстро, как C ++ в Swift 4.0.2 и Apple LLVM 9.0.0.

Изучите OpenGL ES
источник
2
На самом деле вы никогда не должны вызывать vector :: reserve (), прежде чем вставлять десять миллионов элементов.
BJovke
Может быть! Только сортировка рассчитывается в данный момент.
Изучите OpenGL ES
34

От The Swift Programming Language:

Стандартная библиотека Sort Function Swift предоставляет функцию под названием sort, которая сортирует массив значений известного типа на основе вывода предоставленного вами закрытия сортировки. После завершения процесса сортировки функция сортировки возвращает новый массив того же типа и размера, что и старый, с его элементами в правильном порядке сортировки.

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. и есть около 10000 сравнений

Кажется, что встроенный метод сортировки (или близок к) быстрой сортировки, и это действительно медленно ...

Дэвид Скрундз
источник
17
Возможно, я совершенно не прав, но, согласно en.wikipedia.org/wiki/Quicksort , среднее число сравнений в Quicksort равно 2*n*log(n). Это 13815 сравнений для сортировки n = 1000 элементов, поэтому, если функция сравнения вызывается примерно 11000 раз, это не так уж плохо.
Мартин Р
6
Также Apple утверждала, что «сортировка сложных объектов» (что бы это ни было) в 3,9 раза быстрее в Swift, чем в Python. Следовательно, нет необходимости искать «лучшую функцию сортировки». - Но Свифт все еще находится в разработке ...
Мартин Р
6
Это действительно относится к натуральному логарифму.
Мартин Р
24
log(n)Алгоритмическая сложность условно относится к log base-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. Это должно немедленно увеличить вашу производительность.

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

Antoine
источник
12

Пересмотр производительности Swift Array:

Я написал свой собственный тест, сравнивающий Swift с C / Objective-C. Мой бенчмарк вычисляет простые числа. Он использует массив предыдущих простых чисел для поиска простых факторов в каждом новом кандидате, так что это довольно быстро. Тем не менее, он делает тонны чтения массивов и меньше записи в массивы.

Первоначально я сделал этот тест против Swift 1.2. Я решил обновить проект и запустить его против Swift 2.0.

Проект позволяет выбирать между использованием обычных массивов swift и использованием небезопасных буферов памяти Swift с использованием семантики массива.

Для C / Objective-C вы можете выбрать использование массивов NSArrays или C malloc.

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

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

Суть в том, что C malloc'd вычисления на основе массива являются самыми быстрыми, с небольшим запасом

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

Если вы используете обычные массивы Swift, разница с C немного больше. (Swift занимает ~ 1,22 до 1,23 дольше.)

Обычные массивы Swift работают DRAMATICALLYбыстрее, чем в Swift 1.2 / Xcode 6. Их производительность настолько близка к массивам, основанным на небезопасных буферах Swift, что использование небезопасных буферов памяти на самом деле больше не стоит проблем, а это очень много.

Кстати, Objective-C NSArray производительность воняет. Если вы собираетесь использовать собственные объекты-контейнеры на обоих языках, Swift будет ДРАМАТИЧЕСКИ быстрее.

Вы можете проверить мой проект на 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, опция оптимизации всего модуля представляет собой отдельное логическое значение, которое можно комбинировать с другими настройками, и теперь есть опция -Os для оптимизации по размеру. Я могу обновить, когда у меня будет время, чтобы проверить точные флажки опций.
Джозеф Лорд
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.

Abo3atef
источник
4

Swift 4.1 представляет новый -Osizeрежим оптимизации.

В Swift 4.1 компилятор теперь поддерживает новый режим оптимизации, который позволяет использовать специальные оптимизации для уменьшения размера кода.

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

Чтобы включить режим оптимизации размера в командной строке, используйте -Osize вместо -O.

Дальнейшее чтение: https://swift.org/blog/osize/

Касильяс
источник