|
本サイトは移転しました。旧アドレスからのリダイレクトは2025年03月31日(月)まで有効です。
|
🛈 | ✖ |
mingw-w64のプリコンパイル済みヘッダをCode::Blocks設定と共に説明し、コンパイル時間の短縮効果を測定する。
プリコンパイル済みヘッダとはコンパイル時間短縮を目的にインクルードファイルを前処理(プリコンパイル)してできる中間ファイルの事で、ソースコードファイルは元々のインクルードファイルの代わりにプリコンパイル済みヘッダを使う。mingw-w64を含め現代的なC++コンパイラのほとんどがこの機能を用意し、それぞれが規格の拡張として独自用法で実装する。mingw-w64などは翻訳単位毎にプリコンパイル済みヘッダを変える事ができる一方、プロジェクトでただ一つのプリコンパイル済みヘッダを持つ実装も存在する。本サイトはmingw-w64を使うもののプリコンパイル済みヘッダはプロジェクトで一つを原則として全ての翻訳単位がこれを共用するが、これはサイト作成者が以前に使用していたコンパイラの制限を無意識に受け継いでしまった結果である。
mingw-w64のプリコンパイル済みヘッダを簡単にまとめる。mingw-w64はプリコンパイル済みヘッダを以下に生成する(GCC 14.1.0 Manual 3.2 Options Controlling the Kind of Output)。
翻訳単位はコンパイル時に生成済みのプリコンパイル済みヘッダを利用できる(GCC 14.1.0 Manual 3.22 Using Precompiled Headers)。
Code::Blocksでmingw-w64のプリコンパイル済みヘッダを利用する方法をまとめる。あるプロジェクトのpch.hインクルードファイルをプリコンパイル対象とする。pch.hのプリコンパイルを設定し、プロジェクト設定でプリコンパイル済みヘッダ利用方法を選択する。
[Precompiled headers|Strategy]選択はプリコンパイル済みヘッダ出力を定義し、プリコンパイル/翻訳単位コンパイルに共通でインクルードパスオプションを追加する。一例としてDebug32ターゲットの場合をまとめる。
Generate PCH... | プリコンパイル済みヘッダ | インクルードパスオプションの追加 |
---|---|---|
in a directory alongside original header | pch.h.gch\Debug32_pch_h_gch | (無し) |
in the object output dir | obj\Debug32\pch.h.gch | -iquoteobj\Debug32 -Iobj\Debug32 -I. |
alongside original header (default) | pch.h.gch | (無し) |
in the object output dirのインクルードパスオプション追加は不具合を抱える。これはプリコンパイル済みヘッダをオブジェクトファイルディレクトリ(obj\Debug32)に出力して、翻訳単位コンパイルのインクルードパスでobj\Debug32をプロジェクトディレクトリ(.)に優先させる事を意図する。これで#include <pch.h>はpch.h.gchをインクルードする一方、#include "pch.h"はpch.hをインクルードする。なぜなら#include "pch.h"はソースコードファイルディレクトリが常にインクルードパス先頭にあり、そこでpch.h.gchを発見できない代わりにpch.hを発見する。コンパイラは正常にインクルードファイルを発見するので警告する謂れは無く、そのためプリコンパイル済みヘッダを利用していない事に気付かない。この不具合も考慮してプリコンパイル済みヘッダの利用方法を以下にまとめる。
Generate PCH... | 出力ディレクトリ | インクルード指令 |
---|---|---|
in a directory alongside original header | pch.hと同じディレクトリのサブディレクトリ | #include "pch.h" |
in the object output dir | オブジェクトファイル出力ディレクトリ | #include <pch.h> |
alongside original header (default) | pch.hと同じディレクトリ | #include "pch.h" |
pch.hと同じディレクトリのサブディレクトリに出力する場合はターゲット別にプリコンパイル済みヘッダが作成され、翻訳単位のコンパイル時にはディレクトリ内ファイルのインクルードを順次試みる。オブジェクトファイル出力ディレクトリに出力する場合もターゲット別にプリコンパイル済みヘッダが作成され、翻訳単位コンパイル時にはディレクトリ指定されて正しいファイルをインクルードする。pch.hと同じディレクトリに出力する場合はターゲット変更の度にプリコンパイル済みヘッダを作成しなおす。
boostやwxWidgetsで示すように同一ライブラリの複数バージョンを別ディレクトリにインストールする場合、プロジェクトターゲットでこれを切り替える事ができる。[Project|Build options]の[Project build options]ダイアログで左側ツリービューでターゲットを選択し、[Search directories]ページの[Compiler]、[Linker]、[Resource compiler]を適切に設定すれば良いが、mingw-w64に渡す-Iオプションが変更されるもののコンパイルオプションは変更されずプリコンパイル済みヘッダは同一と認識される。同一アーキテクチャの異なるターゲットでライブラリバージョンを変える場合、バージョン依存でライブラリインクルードが異なればエラーを発生する。
これを避けるにはプリコンパイル済みヘッダ出力ディレクトリをオブジェクトファイル出力ディレクトリ(in the object output dir)とすれば良いが、その場合はインクルードを#include <pch.h>とする必要がある。出力ディレクトリを他(in a directory alongside original headerあるいはalongside original header (default))として#include "pch.h"とするには、それぞれに異なるマクロ定義を加えれば良い。最も簡便には左側ツリービュールートノードのプロジェクトオプションを選択して[Compiler settings|#defines]ページにビルトイン変数$(TARGET_NAME)を追加しておけば、各ターゲットにターゲット名を持つマクロが自動的に定義される。
wxWidgetsライブラリを使用するプログラムがプリコンパイル済みヘッダを利用するにはWX_PRECOMPマクロを定義して次のインクルードファイル(wx_pch.h)をプリコンパイル対象とする。Code::Blocksに添付するwxWidgets projectウィザード、本サイトが提案するwxWidgetsプロジェクトウィザード(K1)/wxWidgetsプロジェクトウィザード(K2)はwx_pch.hを含むプロジェクトを生成する。コンパイラ時間をより短縮するには、それぞれの翻訳単位が用いるライブラリインクルード全てを// put here all your realy-changing header filesに移す。
wx/wxprec.hはwxWidgetsの供給するインクルードファイルで以下に示す。
WX_PRECOMPを定義するとwx_pch.hがwx/wx.hをインクルードしない代わりにwx/wxprec.hがwx/wx.hをインクルードする。この不自然な処置は複数のウィンドウズコンパイラに対応するための経験に基づくおまじないだそうだ。wx/wx.hはおよそ80個の主要ファイルをインクルードして、wx_pch.hはデフォルトで既に多くのインクルードファイルを含む。
プリコンパイル済みヘッダによるコンパイル時間短縮効果を測定する。測定対象はwxWidgets利用のサンプルコードで翻訳単位9、総行数およそ2500、ハードウェアはCore i5-3210M(2.50GHz)、メモリ8GiB、SSDストレージである。Code::Blocksプロジェクトは依存関係を制御できずかつ自動テスト構築が難しいため、コンパイルに必要な最小限のソースコードを新たなディレクトリ(...\KTxtEdit_pch_test)へコピーしてMSYS2のmakeとバッチファイルを活用する。ディレクトリ配置は以下とするが、丸括弧で表示したディレクトリ/ファイルはテスト水準が作成するもので次水準(objディレクトリのみ次試行)はこれらを最初に消去する。
ディレクトリ | ファイル | 内容 |
---|---|---|
...\KTxtEdit_pch_test | *.cpp | ソースコード |
| | *.h | インクルード |
| | resource.rc | ウィンドウズリソース |
| | wx_pch.h | プリコンパイル対象インクルード |
| | pch_test.bat | テスト用バッチ |
| | Makefile | メイクファイル |
| | out.dat | 測定結果出力 |
| | (wx_pch.h.gch) | プリコンパイル済みヘッダ |
├ iconimages | favicon.ico | ファビコン |
| | *.xpm | メニュー項目ビットマップ |
├ (obj) | (*.o) | オブジェクト |
└ (wx_pch.h.gch) | (X01_wx_pch_h_gch) | プリコンパイル済みヘッダ |
(X02_wx_pch_h_gch) | ... | |
(...) | ... |
テストはプリコンパイル済みヘッダを利用する場合は事前作成しておき、サンプルコードの全翻訳単位をコンパイルする時間を計測し、すなわちリンクはテストに含まない。以下の4水準×4水準とし、5回試行して平均値を採る。プリコンパイル済みヘッダ因子のPCHサブディレクトリ水準はサブディレクトリにマクロ定義を変更する10個のプリコンパイル済みヘッダを作成し、コンパイルはその内一つのマクロを定義して利用する。
因子 | 水準 | 説明 |
---|---|---|
コンパイラ | mingw32(-g) | 32ビット-gオプション、Debug32ターゲット相当 |
mingw32(-O2) | 32ビット-O2オプション、Release32ターゲット相当 | |
mingw64(-g) | 64ビット-gオプション、Debug64ターゲット相当 | |
mingw64(-O2) | 64ビット-O2オプション、Release64ターゲット相当 | |
プリコンパイル済みヘッダ | PCH無し | プリコンパイル済みヘッダを利用しない |
PCHデフォルト | wx_pch.hをライブラリインクルードを追加しないデフォルトのまま、wx_pch.h.gchをwx_pch.hと同じディレクトリに作成する | |
PCH | 全翻訳単位のライブラリインクルードを追加して、wx_pch.h.gchをwx_pch.hと同じディレクトリに作成する | |
PCHサブディレクトリ | 全翻訳単位のライブラリインクルードを追加して、wx_pch.h.gchサブディレクトリに10個プリコンパイル済みヘッダを作成する |
より多くのインクルードをプリコンパイルしたほうがより高速化を期待できるため、全翻訳単位のライブラリインクルードをwx_pch.hにコピーする。これらは#ifndef NO_RARELY_CHANGING_HEADER_FILESで囲みライブラリインクルードを追加しない場合もテストする。これを理由にコピーしたライブラリインクルードは各翻訳単位に残す。このwx_pch.hはpimplイディオムが使うMY_PIMPLマクロ定義も含むが、本記事では関係ない。
テストはCode::Blocksで行わずmakeを用い、Makefileファイルはそのタスクを定義する。用いるMakefileは次の環境変数定義を必要とする。
環境変数 | 内容 | 例 |
---|---|---|
MINGWPATH | MSYS2サブシステムパス | C:\msys64\mingw32 |
OPTIMIZE | 最適化オプション | -g, -O2 |
PREDEFINE | 事前マクロ定義 | -DWX_PRECOMP |
このMakefileは以下のフォニー(PHONY)ターゲットを持つ。テストはclean、precompiled、precompiled_2、compileを使用する。
ターゲット | 機能 |
---|---|
link | オブジェクトファイルから実行形式ファイルをリンクする |
compile | ソースコードファイルからオブジェクトファイルをコンパイルする |
precompiled | wx_pch.hからプリコンパイル済みヘッダwx_pch.h.gchを作成する |
windres | resoruce.rcリソーススクリプトからresoruce.resリソースを作成する |
clean | 実行形式、オブジェクト、プリコンパイル済みヘッダなどを削除する |
precompiled_2 | wx_pch.hからマクロIDENTITY=X01~X10とする10個のプリコンパイル済みヘッダをwx_pch.h.gchサブディレクトリに作成する |
4水準×4水準×5回のテストをmakeで自動実行する。コンパイル時間の計測にMSYS2のtimeコマンドラインツール、一部出力のファイル書き出しにteeコマンドラインツールを用いる。結果はout.datファイルに追記する。
%_FILEOUT%は標準出力をout.datに追記する。forループ内の%PATH%は丸括弧を含むとエラーとなるため遅延評価の!PATH!を用いる。timeはウィンドウズシェルコマンドと名前が衝突するため"time"としてファイル実行を明示する。timeは結果をエラー出力するため標準出力に切り替える一方で、make compileの標準出力はout.datに不要なのでcon:に切り替える。
コンパイラ | PCH無し | PCHデフォルト | PCH | PCHサブディレクトリ |
---|---|---|---|---|
mingw32(-g) | 48.45秒 | 15.74秒 | 14.47秒 | 14.65秒 |
mingw32(-O2) | 50.27秒 | 17.93秒 | 16.60秒 | 16.66秒 |
mingw64(-g) | 58.97秒 | 17.71秒 | 16.27秒 | 16.55秒 |
mingw64(-O2) | 61.07秒 | 18.68秒 | 17.18秒 | 17.44秒 |
wx_pch.hはデフォルトで多くのインクルードファイルを含むため、PCHデフォルトでもコンパイル時間は1/3程度に短縮する。短縮効果はコンパイラに依存しない。全翻訳単位のライブラリインクルードを追加したPCHはコンパイル時間を短縮するが効果は大きくない。追加したインクルードファイルが比較的少なく、追加したファイルのいくつかはデフォルトで既にインクルードされているためと考える。プリコンパイル済みヘッダをサブディレクトリ内で探索するPCHサブディレクトリはコンパイル時間を増加するが無視できるレベルにある。