Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

C++でCプリプロセッサを作ったり速くしたりしたお話

1. C プリプロセッサを使ってみよう
2. C プリプロセッサを作った話
3. C プリプロセッサを高速化した話

  • Be the first to comment

C++でCプリプロセッサを作ったり速くしたりしたお話

  1. 1. Photo by Markus Spiske · CC-License: CC BY www.temporausch.com C++でCプリプロセッサを 作ったり速くしたりしたお話 Ladies++meetup#1 @kinu/KinukoYasuda
  2. 2. やすだ きぬこ (@kinu) WhoamI 普段は C++ でブラウザ (Chrome) を作ってます ときどきルンバをハックしたり家庭運用の ための趣味アプリを作ったりしている C++11 勉強中
  3. 3. C++ の話…と見せかけて 概ね CPP (C Preprocessor・プリプロセッサ) のお話をしたいと思います 1. C Preprocessor を使ってみよう! 2. C Preprocessor を (C++で) 作った話 3. C Preprocessor を高速化した話 今日のお話!
  4. 4. Part1:CPreprocessor を 使ってみよう!
  5. 5. ❄ #include とか #define とかを解釈するアレ ❄ C や C++ とはまったく関係ない独立した文法/処理 ○ コメント処理 (削除)・’¥’ で終わる行の連結 ○ 外部ファイル読み込み (#include, #include_next) ○ 条件分岐 (#ifdef / #if / #else / #elif / #endif) ○ 単語置換・マクロ展開 (#define) ○ 文字列化 (#google → “google”) ○ 結合 (a##b → ab) ❄ 頑張れば割といろいろできる ○ でもマクロ展開の仕様が謎で使いこなすのはけっこう難しい ○ というか使いこなす必要は特にない… CPreprocessorのキホン
  6. 6. ❄ ファイルを include する。以上! ❄ その他の使い方は特に C++ ではあまり推奨されない ○ C で型共通のツールを書く (リンクリストとか) ○ template では面倒なちょっとしたマクロ ○ デバッグ用や個人用コードでの手抜き ○ 任意個引数 template の繰り返し記述を手抜きする (可変長 template で出番がなくなった気がする) CPreprocessor何に使う?
  7. 7. ❄ # による文字列化 (べんり) に展開 ❄ ## による結合 (べんり) ❄ マクロ名でもなんでも結合できる (なかなかキモい) 33 に展開される CPPのややマイナー機能を使う
  8. 8. これは “fooVER.h” を include してしまう! ❄ 以下のように書き換えれば動く… まず base と v を展開させる これは “foo1.h” を include! CPPの悩ましい展開例 (1) ・・・!?
  9. 9. ❄ 再帰呼び出しはできない になる; 再帰しない ❄ こんな風に擬似的に回避するのが普通 ❄ (もっとトリッキーにちゃんと再帰する方法もある) CPPの悩ましい展開例 (2) ・・・
  10. 10. ❄ 少し一般化するとやや汎用的に (boost 風味) ❄ 使い方例 CPPのマクロ展開で繰り返しを頑張る
  11. 11. なかなか便利。 とはいっても…
  12. 12. C++11, C++14 で C++ 本体が 大幅パワーアップ! range for や可変長テンプレートなどで オワコン化が進む 基本機能以外は FizzBuzz を書いたり 謎マクロ展開を楽しむための趣味言語になった… (私感)
  13. 13. …気にせず次のパートへ
  14. 14. Part2:CPreprocessor を C++で作ってみた
  15. 15. ❄ 背景:クラウドコンパイラ ○ Chromium のコンパイルが遅い! ○ Google インフラを使って大量のファイルを高速コンパイルするた めのシステムが ukai, shinh らにより開発される (参照) ■ このシステムのためにコンパイルに必要な include ファイルを 検知してクラウドに送りつける部分が必要に ● → #include 行を適当に処理すればいいじゃん? ● → 適当に作ったら問題たくさん発覚 ● → マジメな C Pre Processor を作ることに ❄ ということで 20% タイムで作ってみた (5年くらい前) なぜ CPP を作ったのか?
  16. 16. ❄ C++ で 2500 行くらい ❄ 基本構造 ○ シンプルな手書き再帰下降パーサ ■ # ではじまる行を探して ■ define ならマクロ定義部分を記録 ■ include や include_next ならファイルを読み込む ■ if, ifdef, else, elif, endif なら条件分岐 ○ include ファイルリストを求める機能に特化 ■ C や C++ のプログラム部分はパースしなくてよい ● でも include のためにマクロ展開と演算評価は必要 ❄ そこそこ高速 ○ gcc -E の 6倍くらい (単純比較はできないけど) お手製 CPP パーサの概要
  17. 17. ❄ CPP のマクロ展開は謎が多い ○ → なぜなら仕様が謎だった! ■ 仕様通り実装しても書かれてない部分があって完全に実装 できない・現在知られてる挙動にならない ○ ANSI C Standard Committee の Dave Prosser とい う人によるアルゴリズムが公開されている ■ http://www.spinellis.gr/blog/20060626/ CPP のマクロ展開を実装する これなかったら実装できなかった…
  18. 18. CPP のマクロ展開アルゴリズム (1)
  19. 19. CPP のマクロ展開アルゴリズム (2)
  20. 20. CPP のマクロ展開アルゴリズム (3) ❄ …とにかくアルゴリズム通りに書けば動く ❄ なんとなくの概要: ○ 各トークンに “hide set” (HS)というのが結びついている ○ マクロを展開するたびに展開結果のトークンの HS にそのマクロ が足される ○ 展開は再帰的に行われるが、”hide set” に入っているマクロはそ のスキャン中はもう展開されない (再帰の限定的禁止) ○ ## はマクロ展開より先に起こる (遅延させて回避可能) 。。。。。
  21. 21. CPP を作る・完成 ❄ その他 ○ __LINE__ や __DATE__ もちまちま作る ○ 条件分岐のために数式のパースと整数演算評価もする ❄ 少しずつ複雑そうなソースを使ってテストしながら修正・テ ストケースに足していく ○ Chromium, WebKit, elfutils, Linux Kernel, boost, ... ❄ だいたい正しく動くように (∩´∀`)∩
  22. 22. Part3:CPreprocessor を 高速化してみた
  23. 23. マジメに作ってみたのはいいが… ❄ 最初に作られた単純な実装 (string::find + substr + いろ んなハック) に比べると半分くらいの速度 ❄ コンパイル速度を速くするのがクラウドコンパイラの目標 なので、高速に動かないと意味がない… → 高速化しよう! なんか遅い。。
  24. 24. ❄ まずは細かい部分を端から修正 ○ switch などはできるだけテーブルに ○ locale 依存の ctype.h の関数 (isalnum など) は使わない ○ 再帰的なアルゴリズムを再帰を使わない形で再実装 ○ ホットな場所での深いポインタ参照は避ける ○ stringstream は激重なので (例えエラー出力でも) 避ける ○ トークンのサイズをなるべく小さく・頻度の高いトークンに最 適化 (ちなみに小さくし過ぎたら遅くなった) 1文字の記号など用 2文字の演算子用 (“==” など) CPP の高速化 (1)
  25. 25. ❄ 一旦処理したファイルはなるべくもう読まない ○ マクロを再定義して自分を再帰的に include するファイルもある ので単純にはできない ■ → ファイル毎に評価されたマクロと変更したマクロを覚えておき、 評価されたマクロが変更されない限り二度は読まない ■ → 全マクロIDの評価と変更を記録するとメモリを食いすぎて逆に遅 くなるので、bloomfilter っぽく false positive は許す → 20% 高速化! ❄ #endif やコメント終端までできるだけ速くジャンプ ○ ‘if/ifdef’ 毎に対応する endif の位置を憶えておく → ~15% 高速化! (でも後に削除) CPP の高速化 (2)
  26. 26. ❄ ‘#’ ディレクティブマッチに Double-Array Trie を使う ○ 大した種類はないけど頻繁に呼ばれる・テーブルは静的なので 更新の心配はなし → 5% 高速化! ❄ 読み飛ばし処理に SSE を使う ○ 次の ‘#’ まで読み飛ばす、次の ‘*/’ まで読み飛ばす、次の改行ま で読み飛ばすなど、最頻出処理 ○ SSE2 で16文字一度に compare し、popcount で改行も数える (__LINE__ のために行カウントが必要なため) → 25% 高速化! CPP の高速化 (3)
  27. 27. ❄ 最終的には最初の単純実装より2倍くらい速くなった ○ (現在は社内で引き継がれてさらに高速化されたようです) ❄ 雑な知見 ○ ctype.h, stringstream は速度が気になるなら使わない SSE は意外と普通に速くなる ○ 大幅高速化は大体「速くする」より「処理しない」 めでたしめでたし! CPP の高速化まとめ
  28. 28. おしまい HappyC++(andCPP)Life!

×