Я впроваджував алгоритм у 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
виробляє однаково хороший код.
xcrun --sdk macosx swift -O3
. Це коротше.