地震でここしばらくブログの更新を自粛しておりましたが、自分でできることをやるのが一番ということで、ブログの更新を再開します。
ブログビューワーのリリースを行おうかとおもっとりましたが、その間にADPもアップデートがあったのと、その中で、久しぶりにC++/STLネタができたので、今回はC++/STLネタを披露いたします。
ちなみに、このブログで一番アクセス数が多い記事は、
C++/STLでCSVファイルの読み込みで、一時期、
自作機向けWindows7のKernel-Power 41病対策のアクセス数が首位に躍り出たのですが、再びCSVが首位に返り咲きました。『おまえらどれだけCSVやねん』と突っ込みたくなるのですが、次の刺客ということで、日本語メール送信を送り込みます。
コードは長くなるので呼び出し部分のみ以下に示します。全ソースのダウンロードは、プラットホーム別に以下のとおりです(ソースの内容自体は同じでプラットフォームに合わせて日本語のエンコードを変えています)。
Windows版(Shift_JIS)
Linux版(utf-8)
int main(void)
{
#ifdef _WIN32
WSADATA wsaData;
WSAStartup( 0x0101, &wsaData);
string charset = "Shift_JIS ";
#else
string charset = "utf-8";
#endif
string smtpserver("mail");
string to("to_address@example.com");
string cc("");
string bcc("");
string from("from_address@example.com");
string subject("サブジェクト");
string text("メール本文");
cout << subject << ":" << text << endl;
if ( !sendmail( smtpserver, to, cc, bcc, from, subject, text, charset) ) {
cout << "Error Send Mail.";
return 1;
}
#ifdef _WIN32
WSACleanup();
#endif
return 0;
}
各パラメータに値をセットして、sendmail関数を呼び出します。あて先(to,cc等)が複数ある場合はコンマ , で区切ります。サブジェクト(件名)とメールの本文のみ日本語OKです。使用している文字コードをcharsetで指定します。
例では、WindowsがShift_JISで、それ以外がutf-8になっていますが、Windowsでutf-8を使うことも可能です。
(もちろんメールの件名や本文で使用する文字コードをutf-8にする必要があります)。
日本語の取り扱いについてですが、昔、日本語メールと言えば、JISコードに変換して送るという風にやっておったのですが、最近のメーラは適切に処理をすれば(文字コードを指定し、base64等で適切にエンコードする)文字コードを変換する必要がなくそのまま送信できるようです。このプログラムもそのまま送信しています。
ちなみに、このプログラムにはbase64のエンコード・デコードも入っています。詳しくはソースをご覧ください。
コンパイルに際して注意があります。char をunsigned charとしてコンパイルする必要があります。
gccでは、
g++ -funsigned-char sendmail.cpp
のように -funsigned-char オプションを使用してコンパイルします。
Visual Studio 2008の場合は、プロジェクトのプロパティ→構成プロパティ→C/C++→言語→char型を規定でunsigned を はい にします(または/Jオプションを指定します)。
このプログラムは、以下の環境で動作確認しました。
Linux:Centos 5.5
Windows: Visual Studio 2008 professional / Windows 7 ulitimate 64bit (32ビットモードでコンパイル)
不覚にも風邪をひいてしまい先週から更新が滞ってましたです。
以前は何カ月も更新せずにほっておいたのですが、最近更新しないとアクセス数が減るのである種の脅迫観念にとらわれたりしますです。
風邪をひいていたのでネタもあまりなく先週に引き続き他の記事の引用で。以下の記事によりますと話題のプログラミング言語第一位はJavaらしい。
プログラミング人気ランキング、Smalltalkトップ50から消える
はやくADPもランクインしたいのだが・・・というお約束は置いておいて、JavaとCで話題が二分しているらしいです。JavaとCであれば作るものに応じて住み分けができているかと思われます。つまり、JavaだとWEBアプリで、Cだとシステムよりのツール等という感じになるでしょうか。なのでこの2つの言語はそれぞれ順位を保っていくのではないでしょうか。またC++が3位というのが興味深かったりします。ちなみに私ですが、ここ10年程C++の仕事を受けたことがありません。もちろん使えはしますが、C++を使った最後の請負仕事は12年程前でMFCでGUIのプログラムの作成だったりします。もっともCの方はもっと前になりますが・・・。と思いきや最近、久しぶりにC++の仕事をしました。
もちろん自社で使うツールなどをC++やCを使ってというのはあるのですが・・・。Javaに関しては5年程前にある人に教えて以来ということになります。一時はJavaPressに記事を書いていたこともあったのですが・・・。
とまぁ私の仕事事情(体感)とちょっと違うのですが、2010年現在のITエンジニアがマスターすべき言語は、Java、C、C++ということのようです。ってホンマかどうかは置いておいてこのうちの1つはマスターしておいた方が良さそうです。
このブログですがC++ネタを上げているのですが、確かにC++を使う方の人口が多いみたいで、ちょいちょいアクセスがあます。ちなみにダントツの人気記事は、
C++/STLでCSVファイルの読み込みだったりします。『21世紀も10年が過ぎ去ろうというところで、C++でCSVっているの?』と思わなくもないのですが、まぁ人気があります。ちなみに、
『【求人募集】GIGAZINEのために働いてくれる記者・編集を募集します』を読んで思うこと。はさすがにGIGAZINE人気に乗っかって先週はアクセスを伸ばしたのですが、今週はぱったりとやんでしまいました。
未踏の説明会の続きですが、説明会の中に技術的なセッションもありまして、グーグル株式会社のソフトウェアエンジニア 鵜飼さんの講演が面白かったのですが、その中で、『1GBのintのソートにかかる時間は、封筒の裏計算で、30秒』というのがありました。
パフォーマンスには一家言ある私ですが、さすがに1GBのintのソート時間にはピンと来ませんでした。
という訳で、ホントかどうかやってみました。
#include <vector>
#include <algorithm>
#include <iostream>
#include <time.h>
using namespace std;
int main(void)
{
vector<int> 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
うーん、数値的には似たり寄ったりであまり変わらないか・・・・
少し間があきましたが、技術ネタで。
new/deleteは、C++はもとより、最近のプログラミング言語なら当たり前のようにやる(おっとdeleteはしないか)かと思いますが、そのコストについてはついつい忘れがちになります。
ADPは、C++で作成しているのですが、オブジェクトをリサイクルするように変更したところ、実行速度が倍ぐらいに速くなった。もともとは速くするために行った訳ではないのだが意外な副産物となった。
Visutal C++ではいつのころからか(遅くともVC++ 2003以降)、newすると最終的にはWindowsのAPIが呼び出される。パフォーマンスにシビアなシステムでは、ローカル変数の定義のようにお気楽に出来るものではないかもしれない。
といっても理屈だけではなんなので、具体的にどのくらいのコストがかかるかベンチマークしてみました。
#include <iostream>
#include <vector>
#include <time.h>
using namespace std;
class myobject {
int myvalue;
public:
myobject() : myvalue(0){};
};
myobject *myobjects[10*1000*1000];
int test(int v)
{
return v * 1000;
}
int main(void)
{
clock_t t = clock();
// new(1千万回)
t = clock();
for ( int i = 0; i < 10 * 1000 * 1000; i++ ) {
myobjects[i] = new myobject();
}
cout << "Time(new) is "
<< (double)(clock() - t) / CLOCKS_PER_SEC << "sec." << endl;
// delete(1千万回)
t = clock();
for ( int i = 0; i < 10 * 1000 * 1000; i++ ) {
delete myobjects[i];
}
cout << "Time(delete) is "
<< (double)(clock() - t) / CLOCKS_PER_SEC << "sec." << endl;
// 関数呼出し(1千万回)
t = clock();
for ( int i = 0; i < 10 * 1000 * 1000; i++ ) {
test(i);
}
cout << "Time(function call) is "
<< (double)(clock() - t) / CLOCKS_PER_SEC << "sec." << endl;
return 0;
}
以下、実行結果(Core i7-920 Windows7 コンパイルVC++2008 デバッグモード)
Time(new) is 2.065sec.
Time(delete) is 2.35sec.
Time(function call) is 0.26sec.
Windows環境でC++だと、おおむね1秒間に約数百万個のオブジェクトが作れるようです。また、関数呼び出しは数千万回できるようです。
上記の実行結果はデバッグ環境で行っていますので、リリースモードで実行するとこれから数倍速くなります。
この手の数字にピンとこない人の為に補足しますと、最近のコンピュータは1秒間に数十億個の命令が実行できます。単純に計算しますと、メモリの確保は約千個の命令を使っており、関数呼び出しは約百個の命令を使うということになります。
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<char, char> oi(t);
boost::regex_replace(
oi,
srcstr.begin(),
srcstr.end(),
fnd,
repstr,
boost::match_default | boost::format_all);
dststr = t.str();
}