meryngii.neta

今日も新たな"ネタ"を求めて。

Boost.Spiritのコンパイル時間を短縮

Boost.Spiritを使った中〜大規模のプログラムのコンパイル時間は明らかに長すぎで、ひどい時は何時間もかかることがあります。これをある程度改善するために、前に使っていた手法を公開しておきます。
ただ、本格的にパーサを組むなら大人しくCaperなどを使った方が健全な気がします。
今回使うのは、C++マニアならご存知のテンプレートの明示的なインスタンス化です。
http://www.fides.dti.ne.jp/~oka-t/cpplab-template-3.html
Spiritのコンパイル時間の多くは、parser::definitionのコンストラクタに費やされているようです。そしてこの定義がヘッダにあるので、パーサの中身が少しでも変わると、インクルードしているファイルを全てコンパイルし直す必要があります。
要はコンストラクタの部分だけを別にコンパイルすればよいのです。そうすれば、個々のパーサに少し変化があっても、リンクの時間だけで済みます。しかし、テンプレートの中なので定義を抜き取っただけではうまくいきません。そこで明示的なインスタンス化が登場します。
例として簡単な電卓を作りました。
my_calc.hpp

#include <string>
#include <stack>
#include <boost/spirit.hpp>

namespace my_action
{

extern std::stack<int> values;

inline int get() { int val = values.top(); values.pop(); return val; }
inline void push(int val) { values.push(val); }

struct add {
	template <class IteratorT>
	inline void operator() (IteratorT first, IteratorT last) const {
		int y = get(), x = get();
		push(x + y);
	}
};
struct sub {
	template <class IteratorT>
	inline void operator() (IteratorT first, IteratorT last) const {
		int y = get(), x = get();
		push(x - y);
	}
};
struct mul {
	template <class IteratorT>
	inline void operator() (IteratorT first, IteratorT last) const {
		int y = get(), x = get();
		push(x * y);
	}
};
struct div {
	template <class IteratorT>
	inline void operator() (IteratorT first, IteratorT last) const {
		int y = get(), x = get();
		push(x / y);
	}
};

}

struct my_calc
	: public boost::spirit::grammar<my_calc>
{
	template<typename ScannerT>
	struct definition
	{
		boost::spirit::rule<ScannerT> expression, value;
		
		definition(const my_calc&);
		
		const boost::spirit::rule<ScannerT>& start() const { return expression; }
	};
};

my_calc.cpp

#include "my_calc.hpp"

std::stack<int> my_action::values;

template <typename ScannerT>
my_calc::definition<ScannerT>::definition(const my_calc&)
{
	expression
		= value
		| ('+' >> expression >> expression)[my_action::add()]
		| ('-' >> expression >> expression)[my_action::sub()]
		| ('*' >> expression >> expression)[my_action::mul()]
		| ('/' >> expression >> expression)[my_action::div()];
	
	value
		= boost::spirit::int_p[&my_action::push];
}

template struct my_calc::definition<
	boost::spirit::scanner<
		std::string::const_iterator,
		boost::spirit::scanner_policies< boost::spirit::skipper_iteration_policy<> >
	>
>;

test.txt

#include "my_calc.hpp"

int main()
{
	for (std::string line; std::getline(std::cin, line), !line.empty(); )
	{
		my_calc c;
		std::string::const_iterator first = line.begin(), last = line.end();
		boost::spirit::parse_info<std::string::const_iterator> info =
			boost::spirit::parse(first, last, c, boost::spirit::space_p);
		
		if (info.full)
			std::cout << "result: " << my_action::get() << std::endl;
		else
			std::cout << "error" << std::endl;
	}
	
	return 0;
}

結果

+ 1 + 2 3
result: 6
/ + 10 8 * 2 3
result: 3

wikipedia:ポーランド記法ってやつです。Lispっぽい感じ。
重要なのはここです。

template struct my_calc::definition<
	boost::spirit::scanner<
		std::string::const_iterator,
		boost::spirit::scanner_policies< boost::spirit::skipper_iteration_policy<> >
	>
>;

コンストラクタの定義があるファイルで、definitionのインスタンス化をします。
ちなみに、この長いテンプレート引数をどこから拾ってきたのかというと、リンカのエラーからです。

error LNK2019: 未解決の外部シンボル "public: __thiscall my_calc::definition,class std::allocator >,struct boost::spirit::scanner_policies,struct boost::spirit::match_policy,struct boost::spirit::action_policy> > >::definition,class std::allocator >,struct boost::spirit::scanner_policies,struct boost::spirit::match_policy,struct boost::spirit::action_policy> > >(struct my_calc const &)" (以下略)

ここから必要な部分だけ取り出すとあんな感じになります。
実際のプログラムではこんな感じのパーサをいくつも組み合わせて、依存性を減らしていきます。