ミュンヘン工科大学の研究チームのメンバーはRust言語で開発したネットワークデバイスドライバの処理速度をC言語のものと比較した。その結果、Rust版の速度低下は最大でも数%にとどまっていた。なぜ処理性能がわずかに遅くなるのか、その理由も説明した。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
「C」や「C++」に代わるシステムプログラミング言語として「Rust」が注目を集めている。メモリ安全性が高く、メモリ破壊バグといった脆弱(ぜいじゃく)性を作り込みにくいからだ(関連記事)。
ただし、システムプログラミング言語では、高い処理性能が必須条件であり、これがCやC++が使われ続けている理由となっている。Rustはどの程度「速い」のだろうか。
ドイツのミュンヘン工科大学で博士課程の学生であるポール・エメリク氏は2019年9月9日、Rustで作成したデバイスドライバの性能評価をGitHubで発表した。
同氏のグループはさまざまな言語で同じ機能を備えたデバイスドライバを記述し、性能を比較している。
性能評価用に作成したのは、Intelのイーサネットコントローラー向けのLinux用デバイスドライバだ(ixgbeタイプ)。
エメリク氏は解説の冒頭で研究に取り組んだ理由をはっきりと述べている。
「Rust版がC版より遅いのは、当然ながらRustの安全機能に起因する。だが、それを定量化できるのだろうか」
同氏によれば、Rustによる実装とCによる従来型の実装には大きな違いが2つしかないという。
つまり、境界チェックが、Rustのパフォーマンス上の主なデメリットとなっているという。
エメリク氏はRustが遅くなる理由を調べるため、デバイスドライバ動作時のCPUのパフォーマンスカウンタを調べた。次の表にある数字は言語ごと、パケットをまとめたバッチサイズごとのCPUの処理性能を示している。いずれもCPUのパフォーマンスカウンタを転送速度で割ったもの(イベント/パケット)。つまり、転送速度当たりのCPU性能である。なお、一般にバッチサイズが大きいほどネットワークコントローラーの処理性能は高くなるが、あまりにサイズが大きいとCPUキャッシュを使い切ってしまい、性能が低下する可能性がある。
全体の状況を見ると、Rustの高い処理性能が分かる。Rust版ではC版と比べて、転送パケット当たり65%多くの命令をCPUが実行できた。しかも、バッチサイズが32の場合、パケット当たりのサイクルの数は6%増にとどまっている(バッチサイズが8だと11%多い)。
エメリク氏はこのデータを評価して次のようにまとめている。
「スーパースカラーとアウトオブオーダーを備えたモダンなCPU(研究では『Intel XeonプロセッサーE5-2620 v3』を採用)であれば、Rustの安全チェックによるパフォーマンス上のハンデは隠れてしまう」
その一方で、CのコードはCPUの潜在力をフルに活用できていないという。研究チームが作成したデバイスドライバは、ノーマル実行時に境界チェックに違反しないので、CPUは正確に予測を行い(分岐予測ミス率は0.2〜0.3%)、正しいパスを投機的に実行できるという条件下の成績だからだ。キャッシュも、境界情報の追加的なロードが必要になったときに貢献している。このワークロードでは、L1キャッシュのヒット率は98%を超えている。
Rustに備わっている安全機能は境界チェックだけではない。整数オーバーフローチェックがある(コンパイル時にフラグで明示的に有効にする必要がある)。
そこで、整数オーバーフローチェックによる性能低下についても調査した。バッチサイズが8よりも大きいとき、チェックを有効にしても性能に統計上有意な差は生じなかった。バッチサイズが8であっても、スループットは0.8%しか低下しなかった。
プロファイリングによれば、整数オーバーフローチェックによって、CPUが実行する命令がパケット当たり9個増え、このうち8個は分岐であることが分かった。分岐予測ミスの総数には影響がなく、分岐チェックはCPUによって常に正確に予測されていた。
Copyright © ITmedia, Inc. All Rights Reserved.