自作の株式売買システムの改修で、反省事項があった。
このシステムは各種言語の混成で書いてある。株価データをネットからタダでいただいてくる「スクレイピング部」はPerlで、その株価データを使って様々な指標を片っ端から売買試行する「シミュレーション部」はCで、売買指示を閲覧する「ユーザインタフェイス部」はPHPで、それぞれ書いた。株価データを格納するDBにはPostgreSQLを使用している。
Cで書いた「シミュレーション部」をPostgreSQLにアクセスさせるのには、「libpq」を使っている。
先日、取り扱い銘柄を増やすため、2500銘柄ほどのシミュレーションを行った。ところがその際、1000銘柄の処理を過ぎるあたりで、メモリリークのために異常終了してしまう現象に悩まされた。それまでは最大でも300銘柄くらいのシミュレーションしかしていなかったので、不具合が伏在したまま、わからなかったのである。銘柄数を一挙に増やしたために不具合が顕在化したわけだ。
malloc()しているメモリは必ず開放しているし、ループ脱出などで未開放のままどこかへ飛ぶなんてこともしていない。
だが、すぐに、libpqで大きなメモリオブジェクトを返すselectの後、PQClear()をしていないこと、つまり
PGresult *res; sprintf(sqlstr, "select * from table where key='%s' order by col;", key); res = PQexec(con, sqlstr);
などというくだりの後、処理が終わっても
PQClear(res);
……をしていないのが原因っぽいことには気づいた。早速処理の後にPQClear(res)を書き加え、
PGresult *res;
sprintf(sqlstr, "select * from table where key='%s' order by col;", key);
res = PQexec(con, sqlstr);
・
・(処理)
・
PQClear(res);
というふうにした。
しかし、異常終了が1500銘柄のあたりまで伸びただけで、やはりメモリリークする。
1か月ばかりも悩んだ後、deleteやinsertのような、いわば「かけ捨て」のsqlも、メモリオブジェクトを返していることに気づいた。そういう部分は、例えば次のように書いて済ませていたのだ。
sprintf(sqlstr, "delete from table where key='%s';", key);
PQexec(con, sqlstr);
そこで、メモリオブジェクトを取っておいて、終わったらクリアするようにした。
sprintf(sqlstr, "delete from table where key='%s';", key);
res = PQexec(con, sqlstr);
PQClear(res);
同様に、insertしているところもupdateしているところも、全部そうした。そうしたら、たちどころにメモリリークはなくなり、異常終了直前に起こっていたスラッシングらしい現象による速度の低下もなくなった。
次に試したいのは、これを
sprintf(sqlstr, "delete from table where key='%s';", key);
PQClear(PQexec(con, sqlstr));
というふうに簡略に書いたらどうだろう、ということである。