GPUを使って無線LANをクラックする話:Pyritの考古学/倫理学

拝啓。

自販機にたまにホット飲料を見かける季節となりましたが皆さまいかがおすごしでしょうか。

秋ですね。気まぐれと勢い 旅行で行った九州では彼岸花が咲いておりました。

今回は咲きかけギリギリのヒガンバナくんの写真です。

これぐらいのをお散歩中に見つけたら、だいたい翌日か翌々日ぐらいには咲きますので、翌日もぜひ見に行ってあげてくだせぇ。ヒガンバナくんは、写真で見るより実物のほうが絶対いいです。雨が降ってない事を祈ります。

あーでも。毒性があるので持ち帰って食べたりしちゃダメですよ。持ち帰るのは思い出だけ。山と一緒かな。

前回までのPyrit

じゃーまた、WiFiのパスワードでも、クラックしてきますか。

前回わかった事は、最初取ったベンチマークで「GPUはCPUより速くてすごーい!!」とか喜んでたけど、実はGPUは10%も使っていないし、CPUも全部は使いきってはいなかった…というなかなか衝撃的な事実でした。さらに言えば、そのベンチはGPUの分と称してCPUも使っている、公平かどうかは正直怪しいと言わざるを得ないものでありました。

言うなれば我々は…ただの幻を見て一喜一憂していたのです。

どうしてこうなった(AA略)

うーん。悲しい。

実際に何か手を動かす前に、どうしてこんなことになってしまったのか想像してみましょう。Pyritのクラスターを組んでいたみなさんは、実はほとんど寝てるだけのGPUを前に、ドヤっていたのか。まさか、そんな。

Pyritの考古学

Pyritが活発に開発されていたのは2009〜2010年ごろです。DeepLearningブームが始まったのが2012年とかなのでそれよりも前だし、その頃に生まれた子供は、今小学校で九九を覚えてる頃。それぐらい大昔です。そんな古代のソフトウェアを今動かそうというのですから、歴史を踏まえなければ、目の前の状況を理解することはできないでしょう。

まず、GPUをなんでこんなに遊ばせてしまっているのか。

ひょっとすると、前回使ったVisual Profilerが実はその頃なかったのかもしれません。だから使い切ってない事に誰も気づかなかったとか。が、しかし、NVIDIAの公式ページによると2008年から作ってるとの事なので(頑張ってるなNVIDIA)、この線は無い…と思いたい。つまり、「昔はGPUを全部使い切ってたけど、時間が経ったら状況が変わった」可能性が高い。

あの頃のGPU:GTX480が出た頃(買えたとは言ってない)

2010年といえば、GPUならGeForce GTX 295(2009年6月)とか、GTX 480(2010年3月)の頃です。…もう何も覚えてない…。まーとりあえず表でも書いてみますか。CUDAとは直接は関係ないですが、参考程度にPassMarkのベンチマークの数値も貼っときます。グラフィクスを描く時もCUDAコアが走るそうなので、無関係ではあるまい。

GPU 発売時期 CUDAコアクロック CUDAコア数 PassMarkベンチ
GTX 295 2009/06 1242MHz 240×2 1052
GTX 480 2010/03 1401MHz 480 4358
GTX 1080 Ti 2017/02 1480MHz 3584 14057

GTX480ではGPGPUへの最適化も進んだそうで、一気に性能が上がっていますが、曰く

最初のロットの歩留まりは、嘘か真かは不明だが2%ほどだったという。その後もステッピングを重ねてできる範囲での改良を施したものの、最終的に2010年3月のGeForce GTX 480発表時点で出荷可能な枚数は全世界を合わせて数千枚程でしかなかった。

ロードマップでわかる!当世プロセッサー事情 ― 第146回
GPU黒歴史 DX11への遅れが生んだ駄作 GeForce GTX 480

だそうで、2010年のゲーマーたちはだいたいGTX295を使っていたんじゃないかと思います。つまり、今の1080 Tiは当時に比べて10倍以上、逆に言えば、GTX295は今に比べたら1/10以下の文字通り「桁違い」の性能だったと思われます。…ところで、10%弱しか走ってなかったんですよね?

あの頃のCPU: Core i7が初めて出た頃

じゃあ一方CPUはどうなんだ。2010年にPCパーツショップに並んでいたのは、Core iシリーズの最初、Nehalem世代です。これももう覚えてないですねぇ。せいぜい登場時に「3と5と7って何?Core i7には7コア載ってるのか?」と思ったのを覚えている程度です(答え:載ってない)。

同じように表を書いてみましょう。

GPU 発売時期 クロック(ターボ) コア数(スレッド数) PassMarkベンチ(マルチ/シングル)
Core i7 960 2009/10 3.2GHz(3.46GHz) 4(8) 5828/1389
Core i7 8700K 2017/10 3.7GHz(4.7GHz) 6(12) 15969/2704
Xeon Silver
4116
2017/02 2.1GHz(3.0GHz) 12(24) 15131/1599

ターボクロックはカッコの中に一応書いてはいますが、ターボブーストは「一時的に加速できる」という機能ですので、ずっと負荷を掛け続けるベンチマークではベースクロックの方が重要です。

ベンチに使ってるサーバに乗ってるのが最後のXeon Silver 4116です。PassMarkのベンチマーク的にはなんとシングルコア性能自体は2009年のCPUである960と大して変わらないという結果になってます。もちろん周波数は960よりかなり低いので、「周波数あたりの性能は上がってる」と言えるのは間違いなく事実なんですけどね。

最近のサーバ用CPUトレンドはこのようにそこまでの性能ではない低い周波数のコアをたくさん束ねて全体としての性能を確保する戦略らしく、結果としてCPU全体では10年前のCPUの三倍ぐらいの性能は出るようになっています。という所は観察できました。

時を経ても変わらないもの―Pythonはシングルスレッド

GPUもCPUも速くなってはいるけれど、一つだけ変わらない事があります。

それは、Pythonはシングルスレッドでしか動かないという事実です。PythonにはGIL(Global Interpreter Lock)という仕組みがあるので、いくらthreading.Thread()してスレッドを作っても、同時に動くPythonのスレッドはたった1つだけです。100コア積んでも5000兆コア積んでも1つだけ。

ちょっと待て。じゃあなんでPythonで書かれてるはずのPyritはマルチコア対応してるんだ?もっというとDeepLearningはみんなPythonでやってるんじゃ無いのか?シングルスレッドなのか?

もちろんそんな事あるわけなくて、PythonからC言語の拡張を呼んだ時は、C言語で処理しつつPythonの他のスレッドを起こす事ができます。その時はPy_BEGIN_ALLOW_THREADS;Py_END_ALLOW_THREADS;で囲めばマクロがよしなにスレッドの面倒を見てくれます。

実際にPyritでCPUでクラックする時もこんな感じになってます:

    Py_DECREF(passwd_seq);

    if (arraysize > 0)
    {
        Py_BEGIN_ALLOW_THREADS;
        i = 0;
        do
            i += finalize_pmk(&pmk_buffer[i]);
        while (i < arraysize);
        Py_END_ALLOW_THREADS;

        result = PyTuple_New(arraysize);
        for (i = 0; i < arraysize; i++)
            PyTuple_SetItem(result, i, PyString_FromStringAndSize((char*)pmk_buffer[i].e1, 32));
    } else {
        result = PyTuple_New(0);
    }

    PyMem_Free(pmk_buffer);

    return result;
}

これを使う事で1つ以上のCPUコアを使う事が可能になっていますが、しかし、他の部分は全部シングルスレッドでしか動かないことは記しておく価値があると思います。C言語のコードでも、PythonのオブジェクトからCの値に変換したり、その逆をしたりするところはシングルスレッドでやらないといけません。

CPUを全コア使いきれていない理由は、前回書いた通り、ここだと思います。パスワードを生成する部分がPythonで書かれているので、そこがボトルネックになっているんだと思います。

Pyritの倫理学:「公平なベンチマーク」ってなんじゃらほい

まぁ、だいたい状況は掴めました。次にルールを考えましょう。

「ベンチマーク」という「勝負」のルールを、です。

現在のPyritのbenchmarkコマンドのルールでは、「CPU」と「GPU」の勝負のはずが、「GPUのために準備するCPUのコード」も同時に別々のCPUのスレッドで走っています。

CPUのスレッドはランダムなタイミングで割り込まれる可能性がありますから、CPUがクラックしているコード上の「行間」でGPUの為に準備するスレッドのコードが実行されて、CPUの性能が見かけ上悪くなってる可能性はかなり高いです。わたしはこんなんじゃあ、公平なベンチマークとは到底言えない思います。

「公平」とはなんじゃらほい??

ほう、「公平」ねぇ。ところで、「公平」ってなんでしょう?試しに「公平な社会」とか書いて見ると、これがいかにも政治家さんや官僚さんの口から出てきそうな単語な事からも分かるように、「公平」とは人によって意見が別れうる、一筋縄ではいかない手強い単語なわけです。

まぁ、「公平な社会」が何なのかは置いておいて(わたしにはわからん)、今回は「公平なPyritのベンチマーク」とは何なのかのわたしなりの意見を書いておきます。

CPUとGPUは明らかに対等な立場ではありません。というのも、CPUだけでパスワードのクラックはできるけれど、GPUだけではできないからです。CPUがGPUの処理しやすい問題の形式に整えてから、GPUが一気に処理をする。そういう、協力関係で計算を進めるのがGPGPUです。こういうのを一般的かつかっちょいい言葉では「ヘテロジニアス・コンピューティング」といいます。

そういう意味では、同じ勝負でも「サッカー」とはだいぶ雰囲気が違います。サッカーみたいに、お互いが対称なルール、例えば「GPUめ、CPUに頼らずクラックしてみろ。CPUもお前には頼らないから平等だよな?」みたいなルールでは、単にGPUが何もできなくて降参して終わりです。

そもそも勝負のルールってどうして必要なんだ?

サッカーという勝負のルールは、選手や観客が楽しく、かつ安全に遊ぶためのルールですが(たぶん)、じゃあ、Pyritのベンチマークという勝負のルールは何の為のルールでしょう?

まぁ結局は同じ、「面白いかどうか」が基準でいいと思うんですが、それをもうちょっと具体的に言えば、「GPUとCPU同士の本気の勝負が見たい」に尽きると思います。もうちょっと大人じみた言葉で言えば、「GPUの得意な処理をCPUからGPUにオフロードした時に、そこがCPUと比べてどれぐらい速くなるか?」の限界が見たい。

もちろん、一部だけ高速化できても全体で速くなるとは限りません。全体の10%がGPUで10倍速くなっても、1.1倍ぐらいしか速くなりません(アムダールの法則)。例えば、GPUがCPUに比べてそこまで得意ではない処理もGPUに持って言って「並列化」する事で、その部分はそこまで高速化しなくとも、システム全体としては高速化する可能性はあります。…が、個人的にそこはあんまり面白くないなぁ…GPUの最高性能、つまり「ガチ」が見たいなぁ…と思ったので独断により今後はこの上に書いた方針で行きたいと思います。Pyrit全体よりも局所的な情報の方が、他のシステムについて見積もる時に便利そうだし。まぁこのいかにもな理由の方は後付けなんですが。

流石にこれだけではGPUに甘々な感じがするので、今邪魔されているCPUにも救いの手を差し伸べましょう。CPUが邪魔されている原因は、結局CPUとGPUを同時に走らせてクラックしていることです。これは単にベンチマークをとる時に、別々に走らせれば邪魔されずにすみます。

ベンチマークのルールは与えられるものじゃない、考えるものだ

というわけで考えた「面白い勝負」のためのルールをまとめると:

  • GPUの一番得意な処理をCPUからGPUにオフロードする
    • そこが全く同じ処理をするCPUと比べ、どれぐらい速くなるかを測定
  • CPUとGPUは別々に走らせて、お互いに邪魔しあう事を防ぐ

そろそろ長くなってきたので、もっと具体的な話は次回で。

本当に今のルールは「公平」ではないのか?

一応擁護しておくと、今のPyritのベンチマークのルールも「公平」だと言えなくもありません。

というのも、今のPyritのベンチマークは会社でよくある「成果主義」そのものだ、と捉えることも出来るからです。会社というのも協力関係がお互いにあるわけですが、「成果主義」の会社ではお給料を決めたりする時はその上で「そいつが、会社の利益に、どれくらい貢献したか?」みたいなのを(定量的にじゃないにしても)測ります。Pyritの今のベンチマークは、それと似たような事を測っている、と捉えることは可能だと思います。

で、この「成果主義」をどう思うか、具体的にどうするか、色々意見があることと、上でわたしがベンチマークのルールをどうするか悩んでいるのと、対応がつくわけです。もちろん、人とコンピュータの世界では前提が色々違うので全く同じというわけではありませんが、「一筋縄ではいかないねぇ〜」「意見が別れるだろうね〜」という点では全く同じです。

まとめ

Pyritが想像以上に大昔のソフトウェアだったので、歴史を調べました。

Pyritが盛んに開発されていた頃のGPUはCUDAへの最適化が進む一歩手前で今とは桁違いの性能差がありそうなこと。

シングルコア性能は今のサーバのXeonは昔のハイエンドCore i7と同じぐらいの性能だけれどマルチコアにすることで性能を稼いでいること、Pythonは相変わらずシングルスレッドなこと。そんなことがわかりました。

そして、わたしの興味に応じた「ぼくのかんがえたベンチマークのルール」も考えました。意外と人の仔の世界とも通じるものが有るんじゃのぅ。

さ〜て次回のPyritは?

このルールに応じてPyritを書き換えて、測定しなおしましょう!

山の上の石に日が暮れるまで座って色々考えてると今回みたいな記事になりまして。
まぁ、たまにはこういうのをじっくり考えるのも大事なんじゃないかなぁ、って最近思うんですよ。

投稿者: 平藤 燎

実在する架空のプログラマー