Я реализовывал алгоритм в 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
выдает одинаково хороший код.
источник
xcrun --sdk macosx swift -O3
. Это короче.Ответы:
tl; dr Swift 1.0 теперь работает так же быстро, как C в этом тесте, используя уровень оптимизации релиза по умолчанию [-O].
Вот быстрая сортировка на месте в Swift Beta:
И то же самое в C:
Обе работы:
Оба вызываются в той же программе, что и написаны.
Это преобразует абсолютное время в секунды:
Вот сводка уровней оптимизации компилятора:
Время в секундах с [-Onone] для n = 10_000 :
Вот встроенная сортировка Swift () для n = 10_000 :
Вот [-O] для n = 10_000 :
Как видите, производительность Swift улучшилась в 20 раз.
Согласно ответу mweathers , установка [-Ofast] имеет реальное значение, приводя к этим временам для n = 10_000 :
И для n = 1_000_000 :
Для сравнения это с [-Onone] для n = 1_000_000 :
Таким образом, на этом этапе разработки Swift без оптимизации был почти в 1000 раз медленнее, чем C. С другой стороны, с обоими компиляторами, установленными в [-Ofast], Swift фактически работал так же хорошо, если не чуть лучше, чем C.
Было отмечено, что [-Ofast] изменяет семантику языка, делая его потенциально небезопасным. Вот что Apple заявляет в заметках о выпуске Xcode 5.0:
Они почти защищают это. Будь это мудро или нет, я не могу сказать, но из того, что я могу сказать, кажется достаточно разумным использовать [-Ofast] в релизе, если вы не выполняете высокоточную арифметику с плавающей запятой и уверены, что не целое число или в вашей программе возможно переполнение массива. Если вам нужна высокая производительность и проверки переполнения / точная арифметика, выберите другой язык.
БЕТА 3 ОБНОВЛЕНИЕ:
n = 10_000 с [-O] :
Swift в целом немного быстрее, и похоже, что встроенная сортировка Swift значительно изменилась.
ЗАКЛЮЧИТЕЛЬНОЕ ОБНОВЛЕНИЕ:
[-Onone] :
[-O] :
[-Ounchecked] :
источник
TL; DR : Да, единственная реализация языка Swift медленная, прямо сейчас . Если вам нужен быстрый, числовой (и, возможно, другие типы кода) код, просто перейдите к другому. В будущем вам следует пересмотреть свой выбор. Это может быть достаточно для большинства приложений, написанных на более высоком уровне.
Из того, что я вижу в SIL и LLVM IR, кажется, что им нужна куча оптимизаций для удаления сохранений и выпусков, которые могут быть реализованы в Clang (для Objective-C), но они еще не перенесли их. Это теория, которой я придерживаюсь (на данный момент… мне все еще нужно подтвердить, что Clang что-то с этим делает), поскольку профилировщик, запущенный в последнем тестовом примере этого вопроса, дает этот «симпатичный» результат:
Как говорили многие другие,
-Ofast
это совершенно небезопасно и меняет семантику языка. Для меня это на стадии «Если вы собираетесь использовать это, просто используйте другой язык». Я пересмотрю этот выбор позже, если он изменится.-O3
получает насswift_retain
иswift_release
называет это, честно говоря, не похоже, что они должны быть там для этого примера. Оптимизатор должен был исключить (большинство из них) AFAICT, поскольку он знает большую часть информации о массиве и знает, что он имеет (по крайней мере) сильную ссылку на него.Он не должен выдавать больше значений, даже если он не вызывает функции, которые могут освобождать объекты. Я не думаю, что конструктор массива может вернуть массив, размер которого меньше запрашиваемого, а это означает, что многие выполненные проверки бесполезны. Он также знает, что целое число никогда не будет больше 10k, поэтому проверки переполнения могут быть оптимизированы (не из-за
-Ofast
странности, а из-за семантики языка (ничто иное не меняет этот var и не может получить к нему доступ, и добавляет до 10k). безопасно для типаInt
).Однако компилятор не сможет распаковать массив или элементы массива, поскольку они передаются
sort()
, которая является внешней функцией и должна получить ожидаемые аргументы. Это заставит нас использоватьInt
значения косвенно, что сделает его немного медленнее. Это может измениться, еслиsort()
обобщенная функция (не в мульти-методе) была доступна компилятору и стала встроенной.Это очень новый (публичный) язык, и он, как я полагаю, претерпит множество изменений, поскольку есть люди (в значительной степени) вовлеченные в язык Swift, просящие обратной связи, и все они говорят, что язык еще не закончен и будет изменение.
Используемый код:
PS: я не эксперт ни по Objective-C, ни по всем возможностям из Какао , Objective-C или Swift. Я мог бы также предположить некоторые вещи, которые я не писал.
источник
-Ofast
«совершенно небезопасным»? Предполагая, что вы знаете, как проверить свой код и исключить переполнение.-Ofast
также изменяет семантику языка, но я не могу финансировать какие-либо документы для этого. Как ты можешь быть уверен, что знаешь, что он делает?Int[]
», поскольку это зависит от уровня, который может встроить компилятор, и он должен иметь возможность встроить узкие циклы с / после некоторого руководства.Я решил взглянуть на это ради забавы, и вот время, которое я получаю:
стриж
Результаты:
Swift 1.1
Swift 1.2
Swift 2.0
Кажется, это будет та же производительность, если я скомпилирую
-Ounchecked
.Swift 3.0
Похоже, что с Swift 2.0 до Swift 3.0 наблюдается снижение производительности, и я также вижу разницу между
-O
и-Ounchecked
впервые.Swift 4.0
Swift 4 снова повышает производительность, сохраняя при этом разрыв между
-O
и-Ounchecked
.-O -whole-module-optimization
не похоже, чтобы изменить ситуацию.C ++
Результаты:
Apple Clang 6.0
Apple Clang 6.1.0
Apple Clang 7.0.0
Apple Clang 8.0.0
Apple Clang 9.0.0
решение суда
На момент написания этой статьи сортировка Swift была быстрой, но еще не такой быстрой, как сортировка C ++ при компиляции
-O
с вышеуказанными компиляторами и библиотеками. При этом-Ounchecked
он выглядит так же быстро, как C ++ в Swift 4.0.2 и Apple LLVM 9.0.0.источник
От
The Swift Programming Language
:sort
Функция имеет два объявления.Декларация по умолчанию, которая позволяет указать закрытие сравнения:
И второе объявление, которое принимает только один параметр (массив) и «жестко запрограммировано для использования компаратора меньше чем».
Я протестировал модифицированную версию вашего кода на игровой площадке с добавлением замыкания, чтобы я мог более внимательно следить за функцией, и обнаружил, что при значении n, равном 1000, замыкание вызывалось около 11 000 раз.
Это не эффективная функция, я бы порекомендовал использовать лучшую реализацию функции сортировки.
РЕДАКТИРОВАТЬ:
Я взглянул на страницу Википедии Quicksort и написал для нее реализацию Swift. Вот полная программа, которую я использовал (на детской площадке)
Используя это с n = 1000, я обнаружил, что
Кажется, что встроенный метод сортировки (или близок к) быстрой сортировки, и это действительно медленно ...
источник
2*n*log(n)
. Это 13815 сравнений для сортировки n = 1000 элементов, поэтому, если функция сравнения вызывается примерно 11000 раз, это не так уж плохо.log(n)
Алгоритмическая сложность условно относится к log base-2. Причина, по которой не указывается основание, заключается в том, что закон изменения базиса для логарифмов вводит только постоянный множитель, который отбрасывается для целей O-записи.C(n) = 2n ln n ≈ 1.39n log₂ n
. Для n = 1000 это дает C (n) = 13815, и это не «нотация big-O».Начиная с Xcode 7 вы можете включить
Fast, Whole Module Optimization
. Это должно немедленно увеличить вашу производительность.источник
Пересмотр производительности 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.
источник
Основная проблема, о которой упоминают другие, но недостаточно, это то, что
-O3
она ничего не делает в Swift (и никогда не делает), поэтому при компиляции она фактически не оптимизируется (-Onone
).Имена опций со временем менялись, поэтому некоторые другие ответы имеют устаревшие флаги для опций сборки. Правильные текущие параметры (Swift 2.2):
Оптимизация всего модуля имеет более медленную компиляцию, но может оптимизировать файлы в модуле, то есть в каждой структуре и в реальном коде приложения, но не между ними. Вы должны использовать это для всего, что критично к производительности)
Вы также можете отключить проверки безопасности для еще большей скорости, но при этом все утверждения и предварительные условия не просто отключаются, а оптимизируются на основе их правильности. Если вы когда-либо ударили по утверждению, это означает, что вы находитесь в неопределенном поведении. Используйте с особой осторожностью и только в том случае, если вы решите, что повышение скорости имеет смысл для вас (путем тестирования). Если вы находите это полезным для некоторого кода, я рекомендую разделить этот код на отдельную структуру и отключить только проверки безопасности для этого модуля.
источник
Это мой блог о быстрой сортировке - Github образец быстрой сортировки
Вы можете взглянуть на алгоритм разбиения Lomuto в разделе Разделение списка. Написано в Swift.
источник
Swift 4.1 представляет новый
-Osize
режим оптимизации.Дальнейшее чтение: https://swift.org/blog/osize/
источник