TListに参照させているクラスを
ファイルに書き込む処理があります。
件数が2万件以上あるため負荷が高く困っています。
書き込むタイミングはTListに追加されてから
一定時間は待つ「遅延書き込み」を行っていますが
それでも負荷が高いのは変わりません。
一定件数以上のデータが追加された分は
古い順に消していきます。
これをTStringStreamなどのストリームクラスを使用して
循環バッファが使えれば負荷が減るかと考えています。
固定長なら最終書き込み位置を管理すれば
循環バッファが作成できそうなのですが
可変長の場合は無理なのでしょうか?
【イメージ】
00003 ← 最終書き込み位置
xxxx 99999999 xxxxxxxxxxxxxxx
xxxx 99999999 xxxxxxxxxxxxxxxxxxxxxx
xxxx 99999999 xxxxxxxxxxxxxxx ←最後に書き込まれた部分
xxxx 99999999 xxxxxxxxxxxxx ←最も古いデータ
最も古いデータが一番下にあるということは・・・
単純にファイルに追加していってない?
>一定件数以上のデータが追加された分は
>古い順に消していきます。
負荷とはこの事?
何をさしているのかがわからないw
>固定長なら最終書き込み位置を管理すれば
可変長でも Indexファイル作れば できるとおもうよ?
古い順に消すのではなく
ファイル切り替えちゃったほうが早いと思うけど!
レスありがとうございます。
2万件のデータをファイルに出力するのに時間がかかり
そこが負荷となって現れています。
>古い順に消すのではなく
>ファイル切り替えちゃったほうが早いと思うけど!
ほかの箇所はログデータなのでそのようにしています。
そこで同じようにTMemoryStreamやTFileStreamを使って
古い順に消すほうのログも扱えないかなと思いました。
>可変長でも Indexファイル作れば できるとおもうよ?
ファイルに対してSeekする量が不明になるので
どのようなIndexファイルを作ればいいのか想像つきません。
この辺を参考にして作り始めてみました。
http://www.ichibachi.com/delphi/bufstream.html
2万件のデータをファイルに出力するのに時間がかかり
そこが負荷となって現れています。
2万件一気に吐き出してる???
>書き込むタイミングはTListに追加されてから
>一定時間は待つ「遅延書き込み」を行っていますが
じゃなくて少量ずつ吐き出したほうがいいと思う!
>一定件数以上のデータが追加された分は
>古い順に消していきます。
となると一定件数っていくつ?
>ファイルに対してSeekする量が不明になるので
>どのようなIndexファイルを作ればいいのか想像つきません。
テキストファイルだとして
各行の位置(バイト数)を保存してくだけ
それで頭5行消すとしたら 5行目のIndexを見て
Read側で6行目の頭までSeekして残りをWrite
Indexずれるので修正忘れずに
あとは別スレッドにするってのもありかなと
> 2万件一気に吐き出してる???
> じゃなくて少量ずつ吐き出したほうがいいと思う!
たしかにおっしゃる通りなのですが
はき出している途中に強制シャットダウンする可能性もあるので
その都度はき出す仕様としています。
スレッドでも同様ですね。
>>一定件数以上のデータが追加された分は
>>古い順に消していきます。
となると一定件数っていくつ?
書き方が悪かったですね。
一定件数は2万件です。
ただし設定で最大保持件数を変更できる仕様のため
その辺もややっこしくなっています。
> テキストファイルだとして
> 各行の位置(バイト数)を保存してくだけ
> それで頭5行消すとしたら 5行目のIndexを見て
> Read側で6行目の頭までSeekして残りをWrite
> Indexずれるので修正忘れずに
なるほど、各行のIndexを持つわけですね。
謎なのが下記のような時です。3件保持と仮定します。
--- 1件追加 ---
xxxxxxx
--- 2件追加 ---
xxxxxxx
yyyyy
--- 3件追加 ---
xxxxxxx
yyyyy
zzzzzzzzzz
--- 4件追加 ---
aaaaaxx
yyyyy
zzzzzzzzzz
4件目追加の時1件目が不要なので
そこにseekして書き込めばいいかなと考えて
でもそこに書き込むと可変長のため
前に書き込まれているデータ長と合わないため
可変長データで循環バッファファイルは使えないなと思いました。
もうちょっと考えたいと思います。
>たしかにおっしゃる通りなのですが
>はき出している途中に強制シャットダウンする可能性もあるので
>その都度はき出す仕様としています。
強制シャットダウンってのはいきなり電源OFFとかでなければ
少量づつ吐き出して、シャットダウン感知したら
シャットダウン待たせて
残ってるの全部吐き出すようにすれば
いいだけじゃないのか?
シャットダウン感知時の大量出力の負荷は関係ないだろ?
メモリ上にある件数が2万件で
一度に吐き出す件数が2万件で
>一定件数以上のデータが追加された分は
>古い順に消していきます。
>書き方が悪かったですね。
>一定件数は2万件です。
ということは
2万件丸ごと入れかえじゃないのか?
いっそ一件一ファイルにしてしまうとか。
2万個のファイルが出来てしまうけれどもランダムアクセスはやりやすくなるだろうし。
FindFirstやFindNextでワイルドカードが使えるので検索や絞込みもファイル名を工夫すればそれほど苦労せずにできそうでもあるし。
ファイルの更新日時でソートすれば書き込み順で読み込めるし。
こんなことをするのがデーターベースの機能でしょう。
データーベースに任せたらどうでしょう。
レスありがとうございます。
>>一定件数は2万件です。
>ということは
>2万件丸ごと入れかえじゃないのか?
20,000件までは蓄積して
20,001件目の書き込みで1件目は削除
という処理です。
>いっそ一件一ファイルにしてしまうとか。
それおもしろい考え方ですね。
専用のフォルダを作ってしまえば結構楽かもしれません。
>こんなことをするのがデーターベースの機能でしょう。
>データーベースに任せたらどうでしょう。
やはりそうですかね・・・
「数件まで蓄積して古い順に削除」
と言う処理を頻繁に使うため
早くて安全な処理にできないかと思っていたのですが
何パターンか作成してみたいと思います。
ありがとうございました。
解決とさせて頂きます
出遅れましたが、
私も以前データロガーで同じような事を妄想したことが有ります。
せっかくの機会なので書いてみました。
ただTListは循環式のデータを扱うのに適していないので独自クラスで書きました。
原理は
ディスク領域に対して
データ1.........
データ2.......................
データ3...
データ4..........
データ5...............
とデータ追加時に追加分のデータをファイルに書き込み
データサイズが規定を超えたら
データ6......................
データ3...
データ4..........
データ5...............
と古いデータのあったファイル領域に新しいデータを書きこみます。
メモリー内ではでは古いデータは消去、
新しい領域に新しいデータを格納します。
つまり、全データ読み込み式、逐次書き込みタイプのデータベースです。
サンプルの意味を込めてなるべく簡潔に書こうと努力しましたが、
読み返してみるとちょっと読みづらいですね。
要約するとTVariableDataが前後のデータと手を繋ぐ形のデータ型で、
1-2-3-4-5
2-3-4-5-6
とデータの前後の追加削除に最適な形式を取っています。
TVariableDataListはそれを包括したクラスで、ファイル出力を備えています。
数キロのファイルでしか実験していませんが、理論上はどこまで大きなファイルサイズでも同じ速度で書き込み出来るはずです。
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComCtrls, StdCtrls;
type
TVariableData = class(TObject)
private
procedure SetNext(const Value: TVariableData);
procedure SetPre(const Value: TVariableData);
public
ID:Integer; //追加順ユニークID
Str:string; //可変長データ
fPre :TVariableData;
fNext:TVariableData;
function Size:Integer;
procedure WriteFile(aStream:TFileStream);
procedure SaveToStream(aStream:TMemoryStream);
function LoadFromStream(aStream:TMemoryStream):Boolean;
property Next:TVariableData read fNext write SetNext;
property Pre :TVariableData read fPre write SetPre;
end;
//全データをメモリに保持する方式
//ハードディスクへの書き込みはリアルタイム
TVariableDataList = class(TObject)
private
LastDataPos,FirstDataPos:Integer;
public
MaxSize:Integer; //ディスクに格納できる最大サイズ
fFileName:string;
FirstData,LastData: TVariableData;
constructor Create;
function AddData(str:string):Integer;
procedure Clear;
procedure RemoveMinimum;
procedure LoadFromFile(aFileName:string);
end;
TForm1 = class(TForm)
Button1: TButton;
ListView1: TListView;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
fDataFile:string;
VariableDatList: TVariableDataList;
public
{ Public 宣言 }
procedure ListUp;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TVariableData }
function TVariableData.LoadFromStream(aStream: TMemoryStream):Boolean;
var
strLen:Integer;
begin
if aStream.Position + SizeOf(Integer)*2 > aStream.Size then
begin
Result := False;
Exit;
end;
aStream.Read(ID,Sizeof(Integer));
aStream.Read(strLen,Sizeof(Integer));
if aStream.Position + strLen*2 > aStream.Size then
begin
Result := False;
Exit;
end;
SetLength(Str,strLen);
aStream.Read(Str[1],strLen*2);
Result := True;
end;
procedure TVariableData.SaveToStream(aStream: TMemoryStream);
var
strLen:Integer;
begin
aStream.Write(ID,Sizeof(Integer));
strLen := Length(Str);
aStream.Write(strLen,Sizeof(Integer));
aStream.Write(Str[1],strLen*2);
end;
procedure TVariableData.SetNext(const Value: TVariableData);
begin
if Value<>nil then
Value.fPre := Self;
fNext := Value;
end;
procedure TVariableData.SetPre(const Value: TVariableData);
begin
if Value<>nil then
Value.fNext := Self;
fPre := Value;
end;
function TVariableData.Size: Integer;
begin
Result := Sizeof(Integer)*2 +
Length(Str)*2;
end;
procedure TVariableData.WriteFile(aStream: TFileStream);
var
strLen:Integer;
begin
aStream.Write(ID,Sizeof(Integer));
strLen := Length(Str);
aStream.Write(strLen,Sizeof(Integer));
aStream.Write(Str[1],strLen*2);
end;
{ TVariableDataList }
function TVariableDataList.AddData(str: string): Integer;
var
aData:TVariableData;
aSize,aSize2:Integer;
aFileStream:TFileStream;
begin
aData := TVariableData.Create;
aData.Str := str;
if FirstData=nil then
begin
FirstData := aData;
aData.ID := 0;
end else
aData.ID := LastData.ID +1;
Result := aData.ID;
aData.Pre := LastData;
LastData := aData;
aFileStream := TFileStream.Create(fFileName,fmOpenReadWrite);
aSize := aData.Size;
if (LastDataPos < MaxSize) and
(LastDataPos >= FirstDataPos) then
begin
//最大サイズ以下の場合
aFileStream.Position := LastDataPos + SizeOf(Integer)*2;
aData.WriteFile(aFileStream);
LastDataPos := LastDataPos + aSize;
end else begin
//最大サイズ超過
aSize2 := 0;
repeat
aSize2 := aSize2 + FirstData.Size;
RemoveMinimum;
until aSize2 >= aSize;
FirstDataPos:= FirstDataPos + aSize2;
if FirstDataPos >= MaxSize then
FirstDataPos := 0;
if LastDataPos >= MaxSize then
LastDataPos := 0;
aFileStream.Position := LastDataPos + SizeOf(Integer)*2;
aData.WriteFile(aFileStream);
LastDataPos := LastDataPos + aSize;
end;
aFileStream.Position:=0;
aFileStream.Write(FirstDataPos,SizeOf(Integer));
aFileStream.Write(LastDataPos,SizeOf(Integer));
aFileStream.Free;
end;
procedure TVariableDataList.Clear;
var
aData1,aData2:TVariableData;
begin
aData1 := FirstData;
while aData1<>nil do
begin
aData2 := aData1.Next;
aData1.Free;
aData1 := aData2;
end;
FirstData := nil;
LastData := nil;
FirstDataPos:= 0;
LastDataPos := 0;
end;
constructor TVariableDataList.Create;
begin
inherited;
fFileName := 'data.dat';
FirstData := nil;
LastData := nil;
FirstDataPos:= 0;
LastDataPos := 0;
MaxSize := 256*2*10; //ディスクに格納できる最大サイズ
end;
procedure TVariableDataList.LoadFromFile(aFileName: string);
var
aStream:TMemoryStream;
aData,aDataPre:TVariableData;
aCanLoad:Boolean;
begin
Clear;
fFileName := aFileName;
if FileExists(aFileName)=False then
begin
aStream := TMemoryStream.Create;
aStream.SaveToFile(aFileName);
aStream.Free;
Exit;
end;
aStream := TMemoryStream.Create;
aStream.LoadFromFile(aFileName);
aStream.Read(FirstDataPos,SizeOf(Integer));
aStream.Read(LastDataPos,SizeOf(Integer));
aStream.Position:=FirstDataPos + SizeOf(Integer)*2;
aDataPre := nil;
repeat
aData := TVariableData.Create;
aCanLoad := aData.LoadFromStream(aStream);
if aCanLoad then
begin
aData.Pre := aDataPre;
aDataPre := aData;
if FirstData = nil then
FirstData := aData;
LastData := aData;
end;
until (aCanLoad=False);
aStream.Position := SizeOf(Integer)*2;
if LastDataPos < FirstDataPos then
repeat
aData := TVariableData.Create;
aCanLoad := aData.LoadFromStream(aStream);
if aCanLoad then
begin
aData.Pre := aDataPre;
aDataPre := aData;
LastData := aData;
end;
until aStream.Position + SizeOf(Integer)*2 >= LastDataPos;
aStream.Free;
end;
procedure TVariableDataList.RemoveMinimum;
begin
FirstData := FirstData.next;
FirstData.pre.Free;
FirstData.pre:=nil;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
i: Integer;
begin
for i := 0 to 100 - 1 do
begin
VariableDatList.AddData(DateTimeToStr(Now)+ ' - ' +IntToStr(i));
end;
ListUp;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
VariableDatList := TVariableDataList.Create;
fDataFile := ExtractFilePath(Application.ExeName)+'test.dat';
VariableDatList.LoadFromFile(fDataFile);
ListUp;
end;
procedure TForm1.ListUp;
var
item:TListItem;
aData: TVariableData;
begin
ListView1.Clear;
aData := VariableDatList.FirstData;
while aData <> nil do
begin
item := ListView1.Items.Add;
item.Caption := IntToStr(aData.ID);
item.SubItems.Add(aData.Str);
aData := aData.Next;
end;
end;
end.
一応ですが、D2009なのでStringのサイズを2倍しています。
Sizeof(Char)で書き忘れていますので、D7の場合はご注意を。
monaa様へ
貴重なソースの提示をありがとうございます。
非常に参考になります。
原理はまさにその通りです!!。
多少の断片化は仕方ないと思っています。
>こんなことをするのがデーターベースの機能でしょう。
>データーベースに任せたらどうでしょう。
データベースはデータを削除しないのでファイルはどんどんでかくなる。
最近のは実際にデータを削除してくるのか?
ところで
全レコード同じサイズなのか?
>データベースはデータを削除しないのでファイルはどんどんでかくなる。
>最近のは実際にデータを削除してくるのか?
データーベースによります。
削除した場所にデーターを書き込むものもあります。
ツイート | ![]() |