動作環境
C++ Builder XE4
状況
- ファイル処理
- ATOM E3845 Quad Core
- CPUがネック
- 処理の高速化ができないか
- 文字列の抽出が遅いようだ (7msec)
- カンマ区切り文字列から、任意の場所の文字列を抽出
「カンマ区切り文字列から、任意の場所の文字列を抽出」について性能を調べた
実装案
- A. TStringList使用
- DelimitedTextで分割して抽出
- B. Pos()にてカンマの位置を調べながら抽出
- C. char[]に変換した後、ポインタ操作しながら抽出
実装
code v0.1
Unit1.h
//---------------------------------------------------------------------------
# ifndef Unit1H
# define Unit1H
//---------------------------------------------------------------------------
# include <System.Classes.hpp>
# include <Vcl.Controls.hpp>
# include <Vcl.StdCtrls.hpp>
# include <Vcl.Forms.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE で管理されるコンポーネント
TMemo *Memo1;
TButton *Button1;
void __fastcall Button1Click(TObject *Sender);
private: // ユーザー宣言
void __fastcall debug_outputCurrentTimeWithMillisec(String amsg);
String __fastcall getNthString_TStringList(String srcStr, int targetIndex);
String __fastcall getNthString_Pos(String srcStr, int targetIndex);
String __fastcall getNthString_char(String srcStr, int targetIndex);
void __fastcall checkIntegrity();
void __fastcall checkPerformance(int targetIndex);
public: // ユーザー宣言
__fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
# endif
Unit1.cpp
//---------------------------------------------------------------------------
# include <vcl.h>
# pragma hdrstop
# include <memory>
# include "Unit1.h"
//---------------------------------------------------------------------------
# pragma package(smart_init)
# pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
static const String kString_target = // 処理対象テスト文字列
L"AAA1,BBB,CCC,DDD,EEE" // 0..4
",FFF,G2GG,HHH,III,JJJ" // ..9
",FFF,GGG,H3HH,III,JJJ" // ..14
",FFF,GGG,HHH,I4II,JJJ" // ..19
",FFF,GGG,HHH,III,J5JJ" // ..24
",FFF,GGG,HHH,II6I,JJJ" // ..29
",FFF,GGG,H7HH,III,JJJ" // ..34
",FFF,GG8G,HHH,III,JJJ" // ..39
",FF9F,GGG,HHH,III,JJJ" // ..44
",FFF,G10GG,HHH,III,JJJ" // ..49
",FFF,GGG,HH11H,III,ZZZ" // ..54
;
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
// デバッグ用
void __fastcall TForm1::debug_outputCurrentTimeWithMillisec(String amsg)
{
String infmsg = Now().FormatString(L"yyyy/mm/dd hh:nn:ss.zzz") + L" " + amsg;
Memo1->Lines->Add(infmsg);
}
//---------------------------------------------------------------------------
// イベント処理
void __fastcall TForm1::Button1Click(TObject *Sender)
{
Memo1->Lines->Clear();
checkIntegrity();
checkPerformance(/*targetIndex=*/2);
checkPerformance(/*targetIndex=*/30);
checkPerformance(/*targetIndex=*/54);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::checkIntegrity()
{
String res = L"";
Memo1->Lines->Add(L"=====Integrity check:");
for(int idx=0; idx < 4; idx++) {
res = getNthString_TStringList(kString_target, idx);
res += L",";
res += getNthString_Pos(kString_target, idx);
res += L",";
res += getNthString_char(kString_target, idx);
Memo1->Lines->Add(res);
}
for(int idx=52; idx < 55; idx++) {
res = getNthString_TStringList(kString_target, idx);
res += L",";
res += getNthString_Pos(kString_target, idx);
res += L",";
res += getNthString_char(kString_target, idx);
Memo1->Lines->Add(res);
}
}
void __fastcall TForm1::checkPerformance(int targetIndex)
{
Memo1->Lines->Add(L"=====Performance check (target:" + IntToStr(targetIndex) + L")");
String res;
debug_outputCurrentTimeWithMillisec("---- _TStringList");
for(int loop=0; loop<1000; loop++) {
res = getNthString_TStringList(kString_target, targetIndex);
// resを使った処理は未実装
int nop=1; // for breakpoint
}
debug_outputCurrentTimeWithMillisec(L"---- _Pos()");
for(int loop=0; loop<1000; loop++) {
res = getNthString_Pos(kString_target, targetIndex);
// resを使った処理は未実装
int nop=1; // for breakpoint
}
debug_outputCurrentTimeWithMillisec(L"----- _char + pointer");
for(int loop=0; loop<1000; loop++) {
res = getNthString_char(kString_target, targetIndex);
// resを使った処理は未実装
int nop=1; // for breakpoint
}
debug_outputCurrentTimeWithMillisec(L"End<<<");
}
//---------------------------------------------------------------------------
// { 文字列の抽出
String __fastcall TForm1::getNthString_TStringList(String srcStr, int targetIndex)
{
std::unique_ptr<TStringList> lineSL(new TStringList);
lineSL->StrictDelimiter = true;
lineSL->Delimiter = ',';
lineSL->DelimitedText = srcStr;
if (lineSL->Count < targetIndex) {
return L"";
}
return lineSL->Strings[targetIndex];
}
String __fastcall TForm1::getNthString_Pos(String srcStr, int targetIndex)
{
int wrk_pos = 0; // 今のカンマ位置
int pre_pos = 0; // 前のカンマ位置
String wrk = srcStr;
for(int idx=0; idx <= targetIndex; idx++) {
pre_pos += wrk_pos;
wrk_pos = wrk.Pos(",");
if (wrk_pos == 0) { // 見つからない
return srcStr.SubString(pre_pos + 1, MAXINT); // 1: カンマの分
}
wrk = wrk.SubString(wrk_pos + 1, MAXINT); // MAXINT: 最後まで
}
String res = srcStr.SubString(pre_pos + 1, wrk_pos - 1); // 1: カンマの分
return res;
}
String __fastcall TForm1::getNthString_char(String srcStr, int targetIndex)
{
char zbuf[500] = { 0 };
// srcStrの長さの制限チェック: 未実装
strcpy(zbuf, AnsiString(srcStr).c_str());
char *ptr = &zbuf[0];
int cnt = 0;
while(1) {
if (*ptr == NULL) {
return L"";
}
if (*ptr == ',') {
cnt++;
}
if (cnt == targetIndex) {
break;
}
ptr++;
}
if (cnt > 0) {
ptr++; // skip ','
}
char *start = ptr;
while(1) {
if (*ptr == ',' ||
*ptr == NULL) {
break;
}
ptr++;
}
*ptr = 0x00;
String res = AnsiString(start);
return res;
}
// } 文字列の抽出
//---------------------------------------------------------------------------
結果
表1. 処理時間(msec)
TStringList | Pos() | char[]+ポインタ操作 | |
---|---|---|---|
idx=2 | 7 | 2 | 2 |
idx=30 | 8 | 9 | 4 |
idx=54 | 8 | 15 | 5 |
備考
- 上記のコードはリファクタリングに時間を取れていないので、良いコードではないが性能評価として記事とした
- インデックスが要素数を超えた時の処理が未実装
参考文献
p117
ポインタを使用して書き直すと、若干ですがより高速になります。配列アクセスは、毎回「配列の先頭アドレスにアクセスしたい場合のオフセットを加える」という演算をしながらアクセスします。これに対してポインタを使用すると、このような連続処理ではポインタの差し先を1つ進めるだけで、アクセスする場所はすでに決まっているので演算が不要なためです。
from
プログラミングでメシが食えるか!? by 小俣光之 (こまたみつゆき)さん
実際のところ、ポインタ操作が上記の処理においてどれほど有効かはこちらでは未確認です。
実装
code v0.2
- fix bug: インデックスが要素数を超えた時にエラー
Unit1.cpp
//---------------------------------------------------------------------------
# include <vcl.h>
# pragma hdrstop
# include <memory>
# include "Unit1.h"
//---------------------------------------------------------------------------
# pragma package(smart_init)
# pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
/*
v0.2 Apr. 11, 2019
- fix bug: インデックスが要素数を超えた時にエラーが発生
v0.1 Apr. 11, 2019
- インデックス最後までの処理を確認
*/
static const String kString_target = // 処理対象テスト文字列
L"AAA1,BBB,CCC,DDD,EEE" // 0..4
",FFF,G2GG,HHH,III,JJJ" // ..9
",FFF,GGG,H3HH,III,JJJ" // ..14
",FFF,GGG,HHH,I4II,JJJ" // ..19
",FFF,GGG,HHH,III,J5JJ" // ..24
",FFF,GGG,HHH,II6I,JJJ" // ..29
",FFF,GGG,H7HH,III,JJJ" // ..34
",FFF,GG8G,HHH,III,JJJ" // ..39
",FF9F,GGG,HHH,III,JJJ" // ..44
",FFF,G10GG,HHH,III,JJJ" // ..49
",FFF,GGG,HH11H,III,ZZZ" // ..54
;
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
// デバッグ用
void __fastcall TForm1::debug_outputCurrentTimeWithMillisec(String amsg)
{
String infmsg = Now().FormatString(L"yyyy/mm/dd hh:nn:ss.zzz") + L" " + amsg;
Memo1->Lines->Add(infmsg);
}
//---------------------------------------------------------------------------
// イベント処理
void __fastcall TForm1::Button1Click(TObject *Sender)
{
Memo1->Lines->Clear();
checkIntegrity();
checkPerformance(/*targetIndex=*/2);
checkPerformance(/*targetIndex=*/30);
checkPerformance(/*targetIndex=*/54);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::checkIntegrity()
{
String res = L"";
Memo1->Lines->Add(L"=====Integrity check:");
for(int idx=0; idx < 4; idx++) {
res = getNthString_TStringList(kString_target, idx);
res += L",";
res += getNthString_Pos(kString_target, idx);
res += L",";
res += getNthString_char(kString_target, idx);
Memo1->Lines->Add(res);
}
for(int idx=52; idx < 55 + 2; idx++) { // +2: インデックスが要素数を超える状況にするため
res = getNthString_TStringList(kString_target, idx);
res += L",";
res += getNthString_Pos(kString_target, idx);
res += L",";
res += getNthString_char(kString_target, idx);
Memo1->Lines->Add(res);
}
}
void __fastcall TForm1::checkPerformance(int targetIndex)
{
Memo1->Lines->Add(L"=====Performance check (target:" + IntToStr(targetIndex) + L")");
String res;
debug_outputCurrentTimeWithMillisec("---- _TStringList");
for(int loop=0; loop<1000; loop++) {
res = getNthString_TStringList(kString_target, targetIndex);
// resを使った処理は未実装
int nop=1; // for breakpoint
}
debug_outputCurrentTimeWithMillisec(L"---- _Pos()");
for(int loop=0; loop<1000; loop++) {
res = getNthString_Pos(kString_target, targetIndex);
// resを使った処理は未実装
int nop=1; // for breakpoint
}
debug_outputCurrentTimeWithMillisec(L"----- _char + pointer");
for(int loop=0; loop<1000; loop++) {
res = getNthString_char(kString_target, targetIndex);
// resを使った処理は未実装
int nop=1; // for breakpoint
}
debug_outputCurrentTimeWithMillisec(L"End<<<");
}
//---------------------------------------------------------------------------
// { 文字列の抽出
String __fastcall TForm1::getNthString_TStringList(String srcStr, int targetIndex)
{
std::unique_ptr<TStringList> lineSL(new TStringList);
lineSL->StrictDelimiter = true;
lineSL->Delimiter = ',';
lineSL->DelimitedText = srcStr;
if (lineSL->Count <= targetIndex) {
return L"";
}
return lineSL->Strings[targetIndex];
}
String __fastcall TForm1::getNthString_Pos(String srcStr, int targetIndex)
{
int wrk_pos = 0; // 今のカンマ位置
int pre_pos = 0; // 前のカンマ位置
String wrk = srcStr;
for(int idx=0; idx <= targetIndex; idx++) {
pre_pos += wrk_pos;
wrk_pos = wrk.Pos(",");
if (wrk_pos == 0) { // 見つからない
if (idx != targetIndex ) {
return L"";
}
return srcStr.SubString(pre_pos + 1, MAXINT); // 1: カンマの分
}
wrk = wrk.SubString(wrk_pos + 1, MAXINT); // MAXINT: 最後まで
}
String res = srcStr.SubString(pre_pos + 1, wrk_pos - 1); // 1: カンマの分
return res;
}
String __fastcall TForm1::getNthString_char(String srcStr, int targetIndex)
{
char zbuf[500] = { 0 };
// srcStrの長さの制限チェック: 未実装
strcpy(zbuf, AnsiString(srcStr).c_str());
char *ptr = &zbuf[0];
int cnt = 0;
while(1) {
if (*ptr == NULL) {
return L"";
}
if (*ptr == ',') {
cnt++;
}
if (cnt == targetIndex) {
break;
}
ptr++;
}
if (cnt > 0) {
ptr++; // skip ','
}
char *start = ptr;
while(1) {
if (*ptr == ',' ||
*ptr == NULL) {
break;
}
ptr++;
}
*ptr = 0x00;
String res = AnsiString(start);
return res;
}
// } 文字列の抽出
//---------------------------------------------------------------------------
表2. 処理時間(msec)
TStringList | Pos() | char[]+ポインタ操作 | |
---|---|---|---|
idx=2 | 7 | 2 | 2 |
idx=30 | 8 | 9 | 3 |
idx=54 | 8 | 14 | 4 |