読者です 読者をやめる 読者になる 読者になる

文字列と相互変換できる列挙型(enum)

C++ Boost

stringとenumを双方向で変換できてかつPODなのが欲しくなったので書きました。内部の変換テーブルにはboost::bimapを使っています。

特徴としては

  • 双方向に変換できる
  • 実行時
  • POD型
  • boost::bimapなので速い(後述)

などです。

使用例

ENUM_UTILの実装は(長いので)記事の最後にあります。

#include <iostream>
#include "EnumUtil.h"
using namespace std;

//文字列変換できるenum
ENUM_UTIL(Number,
	ZERO,
	ONE,
	THREE = 3,
);

//クラス内にも定義可能
class MyClass {
	ENUM_UTIL(Fruit, APPLE, BANANA, ORANGE);
};

int main() {
	//文字列との相互変換
	Number zero = Number("ZERO");
	cout << to_string(zero) << endl;
	cout << to_string(Number::ONE) << " " << to_string(Number(Number::ONE)) << endl;

	//入出力
	Number n;
	cin >> n;
	cout << n << endl;

	//intとの変換
	int one = Number::ONE;
	Number three = static_cast<Number>(3);

	//比較
	assert(Number::ZERO == 0);
	assert(Number::ZERO < Number::ONE and Number::ONE < Number::THREE);

	//switch文
	switch (zero) {
	case Number::ZERO:
		break;
	default:
		assert(false);
	}
}

実行結果

ZERO
1 ONE
THREE	//input
THREE

速度

(主にbimapの)速度が不明だったので、要素が3つのenumを定義して列挙型→文字列→列挙型の変換を10億回繰り返してみました。

また、比較対象として

  • 列挙型→文字列はswitch、文字列→列挙型はifで分岐して変換
  • unordered_mapを2つ使って変換

も同時に計測しました。環境はgcc 5.1.0でオプションは-O3 -march=nativeです。

結果
方法 実行時間(ms)
ENUM_UTIL(今回・boost.bimap) 25937
switch & if 24045
unordered_map * 2 33817

結果自体は switch & if が一番速いですが、アルゴリズム的にはenumの要素数が増えればハッシュを使うbimapやunordered_mapの方が速くなってくることが予想されます。bimap速いんですねー。

実装

続きを読む

GCCのOpenMP実装(libgomp)のライセンス

C++ GCC OpenMP

最近gccOpenMPを使っていて、ふとライセンスが気になったので調べてみました。ただし私は法律の専門家ではないので、正確な情報はライセンスの原文を見て判断してください。

そもそも、gccC++のコードをコンパイルすると、OpenMPとは関係なくlibstdc++やlibgccが自動的にリンクされます。これらのライブラリは例外条項付きのGPLでライセンスされていて、コンパイル時にgccと非GPL互換ソフトウェアを混ぜて使わない限りはリンクしたプログラムを非GPLで配布できるというものです。

なのでOpenMPライブラリのlibgompも同じだろう、と思いGCC4.9.2のドキュメントを見たのですが、単にGPL(v3)としか書いていないように見えます。そんなはずは、と思ってもう少し調べてみるとGNUのライセンス解説のページに答えらしきものがありました。

どのライブラリをGCCランタイムライブラリ例外はカバーしますか?
 GCCランタイムライブラリ例外がカバーするのは、そのライセンスヘッダに例外が適用されると述べられた告知があるすべてのファイルです。これには、libgcc, libstdc++, libfortran, libgomp, libdecnumber, libgcov とGCCで配布されるそのほかのライブラリが含まれます。

GCCランタイムライブラリ例外とFAQ

どうやらlibgompも例外に含まれているようです。実際に手持ちのomp.hを見てみると、ちゃんと

Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.

と書いてありました。

なお、これは私の手元にあるライブラリの場合であって、皆さんの手元にあるものとは異なる可能性があります(特にGPLv2の場合など)。繰り返しになりますが正確な情報は皆さんの手元にあるライセンスの原文を参照してください。

初めてOpenMPを使ってみて気づいたこと

C++ OpenMP

最近、あるプログラムを並列化して速度を上げるためにOpenMPを使っています。ネット上で検索するとOpenMPの入門記事はあっても実際に使ってみたよ、というのは多くないようなのでそのときに気づいたことなどを書いてみました。

OpenMPと他の並列化技術との比較

並列化技術はOpenMPに限らず色々あります。C++なら最近は標準でstd::threadやstd::asyncがあります。また、並列化にはマルチスレッドによる並列化以外にもSIMDGPGPUや分散コンピューティングによる並列化もあります。OpenMPはもともとマルチスレッドによる並列化技術でしたが、最近の規格ではSIMDGPGPUによる並列化も定義していて、コンパイラは対応を進めている最中のようです。今回はOpenMPのマルチスレッドによる並列化を行いました。

速度面に関してはgccベンチマーク(-O3 -march=native)を取ってみたところ、OpenMPとstd::threadやstd::asyncではほぼ変わりないようです。

#pragmaによる記述は好みが分かれそうなところですが、既存のコードへの変更が少なくて済むのが利点です。特に並列for構文では各スレッドへのタスクの割り当てと結果のreductionを自動化してくれるので、プログラマが部分タスクへの切り分けをしなくて良くなります。また、上手く書けばOpenMPを無効化しても意味を変えずにコンパイルできるのも特徴です。

仕様書が読みやすい

OpenMP仕様書はすごく読みやすいので迷ったら当たってみることをお勧めします。最新の規格はOpenMP4.0ですが、一つ前のOpenMP3.0の仕様書は日本語版も公開されています。
http://openmp.org/wp/openmp-specifications/

初期値の設定

OpenMPでは多くのパラメータの初期値が実装定義となっているので、特定の値が欲しければ自分で設定する必要があります。並列化するスレッド数が実装定義とか言われても困るので

omp_set_num_threads(omp_get_num_procs());

などで設定しましょう。

スレッド数の動的管理の設定は重要そうですが私の環境では目に見える違いはありませんでした・・・

omp_set_dynamic(1);

scheduleの設定は重要

scheduleは並列for構文で並列化された処理をどのようにスレッドに割り当てるかを設定します。大まかに言えばループごとの処理量が一定ならschedule(static)が速く、そうでないならschedule(dynamic)などのほうが速いようです。場合によってはかなり差が出るので実際に試してみた方がいいところだと思います。ちなみに何も書かなかった場合の挙動は例によって実装定義です。

orderedは厄介

orderedは並列for構文内で一部だけ本来の順序で実行する機能です。そもそも並列化して実行速度を稼いでいるところに本来の順序を持ち込むので、当然速度は下がります。それだけではなくて、実行のブロックが入るせいかschedule次第で速くなったり遅くなったりフリーズしたりします。この辺はもしかしたらコンパイラごとに状況が違うのかもしれません。入力や出力を複数用意するなどの方法で避けられるなら避けたたほうが良さそうです。