未踏の説明会の続きですが、説明会の中に技術的なセッションもありまして、グーグル株式会社のソフトウェアエンジニア 鵜飼さんの講演が面白かったのですが、その中で、『1GBのintのソートにかかる時間は、封筒の裏計算で、30秒』というのがありました。
パフォーマンスには一家言ある私ですが、さすがに1GBのintのソート時間にはピンと来ませんでした。
という訳で、ホントかどうかやってみました。
#include <vector>
#include <algorithm>
#include <iostream>
#include <time.h>
using namespace std;
int main(void)
{
vector values;
srand(time(0));
// vectorに適当な値を入れる
for ( int i = 0; i < 1024*1024*1024 / sizeof(int); i++ ) {
values.push_back((int)(rand()*rand()-i));
}
// ソートする
clock_t t = clock();
sort( values.begin(), values.end());
cout << "Time(sort) is "
<< (double)(clock() - t) / CLOCKS_PER_SEC << "sec." << endl;
return 0;
}
実行時間(Core i7-920 Windows7 コンパイルVC++2008 リリースモード 64ビットモード)は以下になります。上記のプログラムですが、32ビットモードでは動作しません。32ビットプロセスはリニアに1GBのメモリは確保できないです。
Time(sort) is 43.895sec.
なるほど、確かに30秒からそう離れていません。
ちなみに、この手の封筒の裏計算ですが、桁が違わなければOKと考えてよいでしょう。なので、細かい値の違いが問題になる場合は、実アプリでキチンとベンチマークをとるのがよいでしょう。
この手の結果の受け止め方ですが、おそらく一般の業務アプリを作成する人にとっては『理論的限界値』程度に思っていた方がよいでしょう。つまり
1秒間に数百万個のint型のソートができる。数千万個になったら要注意。
と思っておけばよろしいかと思います。実際に私の経験でも行数が数百万件のソートをSQLで行うのはあまり問題になることはなかったです。(もちろんメモリが十分にあればの話ですが)。
実行時間の詳細ですが、説明では以下のとおりでした。
・要素を比較する回数(ソートのオーダnlogn)から、
2^28 * log(2^28) → 2^28 * 28 → 2^28 * 2^5 → 2^33(2の33乗)回
・比較に際してのL1キャッシュのアクセス時間 0.5ns / 回
・比較に際してのブランチペナルティ 2.5ns / 回(2回に1回ペナルティがあると仮定する)
実行時間 2^33 * (0.5 + 2.5)nsec = 25.76sec 約30秒
ただ、上記の計算ですが、ブランチペナルティが全体の速度を決定しているというのはいささか疑問があります。上記の場合、メモリのアクセス回数から計算した方が良いのでは?と思います。
つまり、
・要素を比較する回数(ソートのオーダnlogn)から、
2^28 * log(2^28) → 2^28 * 28 → 2^28 * 2^5 → 2^33(2の33乗)回
・ 比較に際してのメモリアクセス回数 2回(リード&ライト) 2*4バイト
・キャッシュライン 32バイト
・メインメモリへのアクセス回数 2^33 * 2 * 4 / 32 = 2^31 回
・メインメモリアクセス性能 1回のアクセス 10nsec(DDR3のレイテンシーから)
実行時間 2^31 * 10nsec = 21.47sec
うーん、数値的には似たり寄ったりであまり変わらないか・・・・
1年ぶりのC++/SLTネタです。
正規表現での置換ですが、ちょっとハマったのでメモ書きします。以下、srcstrで与えられたstringに対して,fstr(正規表現)の検索を行い、repstrで置換します。マッチする文字列全てを置換します。結果の文字列はdststrで返します。
void replace_regex_all(
string &srcstr, const char *fstr, const char *repstr, string &dststr)
{
string tmp;
boost::regex fnd(fstr);
ostringstream t(ios::out | ios::binary);
ostream_iterator oi(t);
boost::regex_replace(
oi,
srcstr.begin(),
srcstr.end(),
fnd,
repstr,
boost::match_default | boost::format_all);
dststr = t.str();
}
久しぶりのC++/STLネタです。今回は数値の3桁区切りをやってみました。
ネットを探すとCのサンプルはあるのですが、少々トリッキーなコーディングのものが多いので、C++でのベタな実装というとこで作ってみました。
/**********************************************************************
数値の3桁区切り
試したコンパイル環境
VC++ .NET 2003 / WINDOWS XP Professional 64 bit edition.
GCC C++ 3.3.6 / glibc 2.3.4 / Vine Linux 4.2
**********************************************************************/
#include <algorithm>
#include <iostream>
#include <iomanip>
#include <vector>
#include <string>
#include <sstream>
std::string formatNumber(int num)
{
std::vector<int> sepnum;
int number = abs(num);
int sgn = num >= 0 ? 1 : -1;
while ( number / 1000 ) {
sepnum.push_back(number % 1000);
number /= 1000;
}
std::stringstream ss;
ss << number * sgn;
for ( std::vector<int>::reverse_iterator i = sepnum.rbegin();
i < sepnum.rend(); i++ ) {
ss << "," << std::setfill('0') << std::setw(3) << *i;
}
return std::string(ss.str());
}
using namespace std;
int main(int argc, char* argv[])
{
cout << formatNumber(0) << endl;
cout << formatNumber(1) << endl;
cout << formatNumber(12) << endl;
cout << formatNumber(123) << endl;
cout << formatNumber(1234) << endl;
cout << formatNumber(12345) << endl;
cout << formatNumber(123456) << endl;
cout << formatNumber(1234567) << endl;
cout << formatNumber(12345678) << endl;
cout << formatNumber(123456789) << endl;
cout << formatNumber(1234567890) << endl;
cout << formatNumber(-1) << endl;
cout << formatNumber(-12) << endl;
cout << formatNumber(-123) << endl;
cout << formatNumber(-1234) << endl;
cout << formatNumber(-12345) << endl;
cout << formatNumber(-123456) << endl;
cout << formatNumber(-1234567) << endl;
cout << formatNumber(-12345678) << endl;
cout << formatNumber(-123456789) << endl;
cout << formatNumber(-1234567890) << endl;
}
久しぶりのC++/STLネタということで、CSVファイルの読み込みです。
もともと、Apacheが出力するアクセスログの読み込みの為に作ったのですが、せっかくなので、Excelが出力するCSVにも対応しました。
CSVファイルの読み込みは他の言語ならsplit一発でお茶を濁すのですが、カンマ(,)やダブルクオート(”)、改行があるデータにも対応してます。
/**********************************************************************
CSVファイルの読み込みサンプル
試したコンパイル環境
VC++ .NET 2003 / WINDOWS XP Professional 64 bit edition.
GCC C++ 3.3.6 / glibc 2.3.4 / Vine Linux 4.2
**********************************************************************/
#include <functional>
#include <algorithm>
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
/**********************************************************************
クオート文字の定義構造体
**********************************************************************/
struct quote{
char start; // 開始文字
char end; // 終了文字
char escape; // エスケープ文字
quote( char start_, char end_, char escape_) :
start(start_), end(end_), escape(escape_) {}
};
struct cmpStartQuote : public std::binary_function<quote,char,bool> {
bool operator()(const quote &l_s1, const char &l_s2) const {
return l_s1.start == l_s2;
}
};
/**********************************************************************
CSVの読取
**********************************************************************/
bool readCSV(
std::vector< std::string > &result, // 結果格納ベクター
std::istreambuf_iterator<char> &i, // 入力文字イテレータ
std::string &separator, // 区切り文字
std::vector<quote> "es) // クオート文字ベクター
{
bool retval = false;
result.clear();
std::string item;
char bc = '\0';
bool first = true;
std::vector<quote>::iterator ff = quotes.end();
while ( i != std::istreambuf_iterator<char>() &&
(ff != quotes.end() || *i != '\n') ) {
if ( ff == quotes.end() &&
strchr( separator.c_str(), *i) != 0 ) {
result.push_back(item);
item.clear();
first = true;
retval = true;
} else {
if ( first &&
(ff = find_if( quotes.begin(),
quotes.end(),
bind2nd( cmpStartQuote(), *i)))
!= quotes.end() ) {
first = false;
} else if ( ff != quotes.end() && ff->end == *i ) {
if ( ff->end == ff->escape ) {
bc = *i++;
if ( i == std::istreambuf_iterator<char>() )
break;
if ( *i == ff->end ) {
item.push_back(*i);
} else {
ff = quotes.end();
continue;
}
} else {
if ( bc == ff->escape ) {
item.push_back(*i);
} else {
ff = quotes.end();
}
}
} else {
item.push_back(*i);
first = false;
}
}
bc = *i++;
}
result.push_back(item);
if ( i != std::istreambuf_iterator<char>() ) {
retval = true;
i++;
}
return retval;
}
using namespace std;
int main(int argc, char* argv[])
{
basic_ifstream<char> inputFile("access_log");
istreambuf_iterator<char> sin(inputFile);
vector< string > rec;
// APACHEのアクセスログ
string separator(" ");
vector< quote > quotes;
// クオート文字(")の設定("を表示したいときは\"になる)
quotes.push_back( quote( '"', '"', '\\') );
// 時間部分のクオート文字([]で括る)
quotes.push_back( quote( '[', ']', 0 ) );
while ( readCSV( rec, sin, separator, quotes) ) {
vector< string >::iterator i;
for ( i = rec.begin(); i < rec.end(); i++ ) {
cout << i - rec.begin() << ":" << *i << endl;
}
cout << endl;
}
return 0;
}
上記コードは、Apacheのログを読み込むサンプルで、CSVファイルの読み込みの場合は、
separatorとquotesの部分が、
string separator(",");
vector< quote > quotes;
quotes.push_back( quote( '"', '"', '"') );
になります。
C++/STLで、ついついやってしまうミスに、コンテナの要素への参照を保存するがある。
最近、このミスをやってしまったので覚書にしておく。
要するに、コンテナに値を追加・削除するとイテレーターが無効になる、と同じことなのだが、参照だとついついやってしまいます。
ちなみに、このバグはコンテナの領域が再配置されない限り発生しないので、reserveメソッドを使用して適当な領域を確保するとデバッグが困難になるので、マジで注意が必要です。
以下、バグの再現コードです。
#include <iostream>
#include <vector>
using namespace std;
int main(void)
{
vector<int> array1;
array1.reserve(10);
for ( int i = 0; i < array1.capacity(); i++ ) {
array1.push_back(i);
}
int &last_value = array1.back(); // コンテナの要素の参照を得る
cout << "last_value: " << last_value << endl;
array1.push_back(10); // ここでlast_valueは無効になる
cout << "last_value: " << last_value << endl;
return 0;
}
実行結果
VC++ .NET 2003 / WINDOWS XP Professional 64 bit edition(32ビット環境で作成・実行)
last_value: 9
last_value: -572662307
C++の例外も、やはり避けて通っていたのだが、ちょっと調べて見ることにした。
以下、Visual C++ .NET 2003の調査結果の覚書。
例外をcatchしないとどうなるか?
例外をcatchしないとVC++.NET2003(おそらくそれ以降でも)
This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.
と出る。デバッグ版のライブラリをリンクしていると上記メッセージの前にダイアログが出てそのままデバッグできる。
C++の例外ってどういったものがあるか?
bad_alloc new演算子等でメモリを確保できないときに発生。vector等のコンテナでもメモリ確保に失敗すると発生する。
bad_cast dynamic_castキャストに失敗したときに発生、参照型で発生する。ポインタ型では発生しない。ポインタ型のキャストに失敗すると0(ヌルポインタ)が返る。
bad_typeid typeid演算子で無効なオブジェクトを渡すと発生する。
bad_exception 例外仕様に反した例外が発生した場合、プログラムは強制終了(terminateの呼び出し)されるが、bad_exceptionに変換する作法(?)がある。VC++ .NET 2003では正常に変換できなかった。
logic_error プログラムの論理エラー(回避可能なエラー)、以下のエラーの親(スーパークラス)になっている。
- domain_error 領域エラー、VC++.NET 2003のSTLでこのエラーがスローされることはない模様。
- invalid_argument 不正な引数、bitsetの初期化エラーでスローされる。
- length_error 長さエラー、コンテナ(vector,deque,list,string等)でmax_size以上の領域を確保(reserve,insert)しようとしたら発生。max_sizeメソッドは各コンテナで扱うことの出来る要素の最大数を指す、実際に確保できるサイズはこの値未満になる。
- out_of_range 範囲外、コンテナ(vector,deque等)のatメソッドの範囲チェックに引っかかった。
runtime_error 実行時エラーの総称、localeクラスのcombineテンプレートメソッドでスローされる。以下のエラーの親になっている。
- underflow_error アンダーフロー、VC++.NET 2003のSTLでこのエラーがスローされることはない模様。
- overflow_error オーバーフロー、bitsetのto_ulongメソッドでスローされる。
- range_error 範囲エラー、VC++.NET 2003のSTLでこのエラーがスローされることはない模様。
- ios_base::failure iostream関連の例外、basic_ios::exceptionsメソッドにより例外の発生をコントロールできる。
例外のサンプルコード
以下、各例外を検証するコードです。
#include <iostream>
#include <string>
#include <new>
#include <eh.h>
using namespace std;
/**********************************************************************
bad_alloc
**********************************************************************/
int test_bad_alloc()
{
int ret = 0;
try {
int *ptr = new int[511*1024*1024UL];
if ( ptr == 0 ) {
ret = 1;
}
} catch ( bad_alloc e) {
ret = 2;
} catch ( ... ) {
ret = 3;
}
return ret;
}
/**********************************************************************
bad_cast
VC 2003 ランタイム型情報を有効にしないとコンパイルできない
**********************************************************************/
class base_class {
public:
virtual void vfunc() const {}
};
class child_class: public base_class {
public:
virtual void virtualfunc() const {}
};
int test_bad_cast_ref()
{
int ret = 0;
base_class base;
base_class &ref_base = base;
try {
child_class& ref_child = dynamic_cast<child_class&>(ref_base);
ret = 1;
} catch (bad_cast) {
ret = 2;
} catch ( ... ) {
ret = 3;
}
return ret;
}
int test_bad_cast_ptr()
{
int ret = 0;
base_class base;
base_class *ref_base = &base;
try {
child_class *ref_child = dynamic_cast<child_class*>(ref_base);
ret = 1;
} catch (bad_cast) {
ret = 2;
} catch ( ... ) {
ret = 3;
}
return ret;
}
int test_bad_typeid()
{
int ret = 0;
base_class *b = 0;
try {
const type_info &t = typeid(*b);
cout << t.name();
ret = 1;
} catch ( bad_typeid ) {
ret = 2;
}
return ret;
}
/**********************************************************************
bad_exception
**********************************************************************/
void func() throw()
{
// warning C4297が出る(わざと例外仕様に反した例外を出す)。
throw "bad_exception!";
}
void convert_unexpected()
{
throw ;
}
int test_bad_exception()
{
int ret = 0;
// VC++ .NET 2003 SP1 で最適化を掛けるとconvert_unexpected関数が呼ばれない
// VC++ .NET 2008 Express Edition でも同様
unexpected_function back = set_unexpected(convert_unexpected);
try {
func();
ret = 1;
} catch ( char * ) {
ret = 2;
} catch ( bad_exception ) {
ret = 3;
} catch ( ... ) {
ret = 4;
}
set_unexpected(back);
return ret;
}
/**********************************************************************
null pointer assigment
**********************************************************************/
int test_null_pointer_assigment()
{
int ret = 0;
// VC++ .NET 2003 SP1 で最適化を掛けるとcatch(...)に行かない。
// _set_se_translatorを使って構造化例外処理からC++例外処理へ
// 例外をコンバートできる
try {
char *ptr = 0;
*ptr = '\a';
ret = 1;
} catch ( ... ) {
ret = 3;
}
return ret;
}
int main(int argc, char* argv[])
{
cout << "test_bad_alloc:" << test_bad_alloc() << endl;
cout << "test_bad_cast_ref:" << test_bad_cast_ref() << endl;
cout << "test_bad_cast_ptr:" << test_bad_cast_ptr() << endl;
cout << "test_bad_typeid:" << test_bad_typeid() << endl;
cout << "test_bad_exception:" << test_bad_exception() << endl;
cout << "test_null_pointer_assigment:" << test_null_pointer_assigment() << endl;
return 0;
}
コンパイル、実行結果
C:\>cl /GR /GX cpp_exception.cpp
Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 13.10.6030 for 80x86
Copyright (C) Microsoft Corporation 1984-2002. All rights reserved.
cpp_exception.cpp
cpp_exception.cpp(98) : warning C4297: 'func' : 例外をスローしないはずだがそれを
する関数。
__declspec(nothrow) または throw() が関数で指定されました。
Microsoft (R) Incremental Linker Version 7.10.6030
Copyright (C) Microsoft Corporation. All rights reserved.
/out:cpp_exception.exe
cpp_exception.obj
C:\>cpp_exception
test_bad_alloc:2
test_bad_cast_ref:2
test_bad_cast_ptr:1
test_bad_typeid:2
test_bad_exception:2
test_null_pointer_assigment:3
C++でDBへアクセスするにはMFCだの、ADOだのややこしいライブラリをリンクするか、黙ってCで記述(各DBのライブラリを直接呼び出すコードを記述)するか、マネージドコードの仲間入りになるかになる。
最近流行りの言語(PHPとか)はあっさりDBをサポートしているのにC++/STLでさくっとSQLを書きたい場合、結構骨が折れる。
と言う訳で、なんちゃってodbcクラスを作りました。
以下のようにSQLが発行できちゃいます。
db, "INSERT INTO test( c1, c2) VALUES(?,?)", "test", 100, endsql;
ぱっと見、何がなんだか分からないかもしれませんが、キモはSQL文に続けてパラメータが記述できる点で、結構楽にSQLが発行できます。
(見る人が見れば凶悪な演算子のオーバーロードに見えるかもしれないが・・・)
ちなみに、様々なDBに対応する為と、Linuxへの移植性を考えてODBCにしました。
開発環境
1.Windows
VC++ .NET 2003 / WINDOWS XP Pro 64(32ビット環境)
Access 2000 / SQL Server 2005(Developer Edition)
2.Linux
GCC 3.3.6 / unixODBC 2.2.11 / Vine Linux 4.2
MySQL 5.0.27 / SQLite3
ODBCドライバは、Vine Linuxにはない、別途インストールした。
※MySQL-Connector/ODBCは、MySQLのサイトからダウンロード(RPM、3.51)
http://dev.mysql.com/downloads/connector/odbc/3.51.html
RPMのインストール時にエラーが出たが単純なSELECTはできた
※SQLite3のODBCドライバは、以下のサイトからダウンロード(0.79)
http://www.ch-werner.de/sqliteodbc/
ソースRPMをダウンロードし、コンパイル、インストール
download ダウンロード(使い方・ヘッダファイル・サンプルおよびサンプルプロジェクト 2009/5/7アップデート)
今まで正規表現を使いたい場合、とっとと別の言語に切り替えていた私ですが、Boostというライブラリの正規表現パッケージを使ってみました。Boostのサイトにはサンプルはあるのですが、C++/STLでさくっと正規表現を使うにはイマイチ感があったので、覚書ということでサンプルのせます。ちなみに、Boostのインストール等の情報はLet’s Boostがお勧めです。
若干ですがコードの説明を、サンプルはHTTPのレスポンスヘッダ(WEBサーバーからの戻りヘッダ)からCookie情報を取得するものです。関数 boost::regex_search が正規表現を使って文字列の検索を行っています。
その他、正規表現の利用法として入力値のチェックもあるかと思います。その場合、boost::regex_matchという関数が使えます。
#include <boost/regex.hpp>
#include <iostream>
int main(int , char* [])
{
// 検索対象文字列
std::string str( "HTTP/1.1 200 OK\r\n"
"Server: Apache\r\n"
"Set-Cookie: NAME1=VAL1\r\n"
"Set-Cookie: NAME2=VAL2; expires=Mon, 18-02-2008 23:30:45 GMT\r\n"
"Set-Cookie: NAME3=VAL3; path=path\r\n"
"Set-Cookie: NAME4=VAL4; path=path; domain=example\r\n"
"Set-Cookie: NAME5=VAL5; path=path; domain=example; secure\r\n"
"Connection: close\r\n"
"Content-Type: text/html\r\n");
// 正規表現
boost::regex r(
"^Set-Cookie:s*([^;$s]+);?(?:s*expires=([^;$]+);?)?"
"(?:s*path=([^;$s]+);?)?(?:s*domain=([^;$s]+);?)?"
"s*(secure)?s*$"
);
boost::smatch what; // マッチ文字列参照オブジェクト
std::string::const_iterator start = str.begin();
std::string::const_iterator end = str.end();
// 検索対象の文字列から正規表現にマッチする文字列(複数有)を取り出す
while ( boost::regex_search(start, end, what, r) ) {
// 取得した文字列をstringに代入する(smatchにはイテレーターが入る)
std::string cookie(what[1].first, what[1].second);
std::string expires(what[2].first, what[2].second);
std::string path(what[3].first, what[3].second);
std::string domain(what[4].first, what[4].second);
std::string secure(what[5].first, what[5].second);
std::cout << cookie << ":" << expires << ":" << path << ":"
<< domain << ":" << secure << ":" << std::endl;
start = what[0].second; // 次のマッチ文字列を取得する
}
return 0;
}
今まで避けて通ってきた道でしたが、C++でsprintfを使うには少々違和感があった。
STLのstringクラス を使うようになり、『やっぱstrstreamか』と思った時期もあったが、最近、Rubyでプログラムを組む機会があり、あっさりとsprintfがサポートしているのを見て、stringクラスを返すsprinfのようなものがあってもよいなと思い直し作成した。
/**********************************************************************
CのsprintfをC++/STLで実現する
使い方
std::string str = cformat( "%d:%s", intvalue, charpointer);
試したコンパイル環境
VC++ .NET 2003 / WINDOWS XP Professional 64 bit edition.
GCC C++ 3.3.6 / glibc 2.3.4 / Vine Linux 4.2
**********************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <vector>
#include <string>
#include <stdexcept>
#ifdef _WIN32
#define vsnprintf _vsnprintf
#endif
std::string cformat( char *format, ...)
{
int bufsize = 1024; // 適当なサイズ
std::vector<char> buff(bufsize);
va_list args;
// 適当なバッファサイズで先ずは、vsnprintfを試す。
// 出力がバッファサイズ以上の場合、VC++ .NET 2003の場合は -1、
// glibc2.1以降は、書き込みに必要なサイズを返す。
va_start(args, format);
int vssize = vsnprintf( &buff[0], bufsize, format, args);
va_end(args);
// vsnprintfが成功した場合終了する。
if ( vssize >= 0 && vssize < bufsize ) {
buff.resize(vssize);
return std::string( buff.begin(), buff.end() );
}
#ifdef _WIN32
// VC++ .NET 2003 書き込みに必要なサイズを取得する。
va_start(args, format);
vssize = _vscprintf( format, args);
va_end(args);
#endif
if ( vssize < 0 ) throw std::runtime_error(format);
// サイズを再割り当てし、再度試す
buff.resize(vssize + 1);
va_start(args, format);
vssize = vsnprintf( &buff[0], vssize + 1, format, args);
va_end(args);
if ( vssize < 0 ) throw std::runtime_error(format);
buff.resize(vssize);
return std::string( buff.begin(), buff.end() );
}