Swift Betaでアルゴリズムを実装していたところ、パフォーマンスが非常に低いことに気付きました。さらに深く掘り下げた後、ボトルネックの1つが配列の並べ替えと同じくらい単純なものであることに気付きました。関連する部分はここにあります:
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での「リリース」ビルドと「デバッグ」ビルドのタイミングは似ています。
ここで何が問題になっていますか?C ++と比較してパフォーマンスの低下を理解できましたが、純粋なPythonと比較して10倍の速度低下はありませんでした。
編集:天気が変わった-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は既に配列の境界をチェックしており、通常の場合、スローダウンは2倍未満であることに注意してください。ClangとGCC -ftrapv
では、(符号付きの)整数オーバーフローをチェックする必要があり、それほど遅くもありません。
したがって、質問:セーフティネットを失うことなく、Swiftで適切なパフォーマンスを得るにはどうすればよいでしょうか。
編集2:いくつかのベンチマークを行いました。
for i in 0..<n {
x[i] = x[i] ^ 12345678
}
(ここで、xor操作は、アセンブリコードで関連するループをより簡単に見つけることができるようにするためだけにあります。簡単に見つかるが、関連するチェックを必要としないという意味で「無害」な操作を選択しようとしました整数オーバーフローへ。)
ここでも、-O3
との間でパフォーマンスに大きな違いがありました-Ofast
。だから私はアセンブリコードを見ました:
と
-Ofast
私は私が期待するものをかなり得ます。関連する部分は、5つの機械語命令を持つループです。-O3
私は私の想像を超えていた何かを得ます。内部ループは、88行のアセンブリコードにまたがっています。私はそれをすべて理解しようとはしませんでしたが、最も疑わしい部分は、 "callq _swift_retain"の13回の呼び出しと "callq _swift_release"の13回の呼び出しです。つまり、内側のループで26個のサブルーチンが呼び出されます。
編集3:コメントで、Ferluccioは組み込み関数(たとえば、並べ替え)に依存しないという意味で公平なベンチマークを求めました。次のプログラムはかなり良い例だと思います:
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は-Ofastと比較してほぼ500倍の損失があります。
- C ++ -O3:0.05秒
- C ++ -O0:0.4秒
- Java:0.2秒
- PyPyを使用したPython:0.5秒
- Python:12秒
- Swift -Ofast:0.05秒
- Swift -O3:23秒
- Swift -O0:443秒
(コンパイラが無意味なループを完全に最適化するのではないかと心配している場合は、ループをeg x[i] ^= x[j]
に変更し、出力するprintステートメントを追加できますx[0]
。これにより何も変更されません。タイミングは非常に似ています。)
そして、はい、ここでのPythonの実装は、intのリストとforループの入れ子を持つ愚かな純粋なPythonの実装でした。最適化されていないSwiftよりもかなり遅くなるはずです。Swiftと配列のインデックス付けで何かが深刻に壊れているようです。
編集4:これらの問題(および他のいくつかのパフォーマンスの問題)は、Xcode 6ベータ5で修正されたようです。
並べ替えについては、次のタイミングがあります。
- clang ++ -O3:0.06秒
- swiftc -Ofast:0.1秒
- swiftc -O:0.1秒
- swiftc:4秒
ネストされたループの場合:
- clang ++ -O3:0.06秒
- swiftc -Ofast:0.3秒
- swiftc -O:0.4秒
- swiftc:540秒
unsafe -Ofast
(別名-Ounchecked
)を使用する理由はもうないようです。plain -O
も同様に良いコードを生成します。
xcrun --sdk macosx swift -O3
。短いです。