ファイルから配列データを動的配列への一括読み込み

解決


yTake  2023-11-07 05:38:50  No: 151220

yTakeです。
ファイルから配列データを動的配列に一括して読み込みたいと思います。
例えば、ファイルに次元が100x100x100の配列データがあります。それを動的配列へ一括して読み込む場合、
wdata  :  Array of Array of Array of Word;
fs        :  TFileStream; 
cnt     :  Integer;
begin
    SetLength( wdata, 100, 100, 100 );
    fs  :=  TFileStream.Create( filename, fmOpenRead );
    cnt := fs.Read( wdata[0][0][0], Sizeof(Word) * 100 * 100 * 100 );
    fs.Free();
end;

実際には、ファイル内に配列サイズも格納されていて、ファイル毎に配列サイズは異なります。
読み取った配列サイズで、wdataのサイズを定義して、先頭の要素から配列サイズ分Readさせていると言う事になっています。
DELPHIの構文チェックでは特に問題はありません。
実行すると、wadataは全てゼロになっています。
読み込まれた数が返されるcntもゼロの為、何も読み込まれていないと言う事と思います。
多分、コードが間違っていると思いますが、ご教授願えるでしょうか?

因みに、
ptr2  :  PWord;
i, j, k  :  Word;
begin
    GetMem( ptr, SIzeof(Word)*100*100*100);
    cnt := fs.Read( ptr^, SIzeof(Word)*100*100*100);
end;
では、データを読み出せていますが、この後、wdata配列へ要素ごとに代入するforループが必要となり、それは避けたいと思っています。

当方:DELPHI, XE6+FMX

よろしくお願いします。


AAAAA  2023-11-07 06:16:32  No: 151221

SetLength( wdata, 100, 100, 100 ); したやつはアドレスが連続していない
ので一括で読み込むのは無理

procedure TForm6.Button3Click(Sender: TObject);
var
    I,J: Integer;
    A:array of array of WORD;
begin
    SetLength(A,3,3);

    for I:=0 to 2 do
    begin
      for J:=0 to 2 do
      begin
        Memo1.Lines.Add(IntToStr(Integer(@A[I,J])));
      end;
    end;
end;


yTake  2023-11-07 08:41:37  No: 151222

AAAAAさん、ありがとうございます。

そうでした。動的配列は行毎に独立していて、連続していないのでしたね。
大変失礼しました。

静的配列を用意したら、そこへは一括読み出し可能と言う事になりますか?
(後で、やってはみます)


AAAAA  2023-11-07 12:27:31  No: 151226

静的配列 だとそうだけど
>実際には、ファイル内に配列サイズも格納されていて、ファイル毎に配列サイズは異なります。
だから 静的配列 は無理

type
  TA = array[0..0] of WORD;
  PA = ^TA;
var
  X,Y,Z: Integer;
  A: PA;

function AGET(X1,Y1,Z1: Integer): WORD;
begin
    RESULT := A[(X1*Y*Z) + (Y1 *Z) + Z1];
end;

procedure ASET(X1,Y1,Z1: Integer; VALUE: WORD);
begin
    A[(X1*Y*Z) + (Y1 *Z) + Z1] := VALUE;
end;

var
    I,J,K: Integer;
    C: WORD;
begin
    X := 3;
    Y := 3;
    Z := 3;
    GetMem(A,X*Y*Z*SizeOf(WORD));

    C := 0;
    for I:= 0 to 2 do
    begin
      for J:= 0 to 2 do
      begin
        for K:= 0 to 2 do
        begin
          ASET(I,J,K,C);
          C := C + 1;
        end;
      end;
    end;

    for I:= 0 to 2 do
    begin
      for J:= 0 to 2 do
      begin
        for K:= 0 to 2 do
        begin
          C := AGET(I,J,K);
          Memo1.Lines.Add(IntToStr(C));
        end;
      end;
    end;


yTake  2023-11-07 12:36:39  No: 151227

行毎にReadしてファイルから動的配列へ読み込み出来ました。
ありがとうございました。

ただ今まで出ていなかったエラーが出る様になりました。
進捗を示す様に、Progressbarを表示させる為、Application.Processmessageを呼び出していますが、
今回の動的配列への読み込み完了後、Application.Processmessageを実行したら、Access Violationが生じる様になりました。
始めに例示したポインターでの一括呼び出しの時はなりません。

ポインターと配列の違いの為、配列の要素数の問題かと思われ、
配列のサイズ:
(300x300x300)エラーあり データサイズ=54000000
(200x200x200)
(100x100x100)エラーなし
ポインター:
データサイズ=54000000(配列:300x300x300)でも問題なし

ただ、動的配列への直接読み込みで(300x300x300)の場合でも、読み込み自体は問題ありません。
あくまで、Application.Processmessageでエラーになります。

また、動的配列への直接読み込みの場合、受け皿の構造体の要素数が勝手変わる問題がみつかりました。
たぶんこの為、Application.Processmessageのエラーが出ていると思われます。

一旦ポインターで受けて、後に、このポインター領域から、動的配列へ行毎にコピー出来れば比較的早く処理できるのではないかと想像しています。
GetMemで確保したポインターの領域から、シーケンシャルに行データを動的配列へ読み込む手段はあるでしょうか?
"CopyArray"なるprocedureがある様ですが、使い方が分かりません。
ヘルプでもサンプルは載っていない様です。

CopyArrayに限定はしません。
ポインターのメモリー領域から配列へなるべく一括してデータをコピーする方法はあるでしょうか?

質問内容が分り難いかも知れません。不明な点はお問い合わせ下さい。


AAAAA  2023-11-07 12:48:52  No: 151228

 あ
  TA = array[0..0] of WORD;
  PA = ^TA;

  TArray3 = class
  private
    A: PA;
    SX: DWORD;
    SY: DWORD;
    SZ: DWORD;
    function GetItems(X,Y,Z: DWORD): WORD;
    procedure SetItems(X,Y,Z: DWORD; Value: WORD);
  public
    constructor Create(X,Y,Z: DWORD); overload;
    destructor Destroy; override;
    property Items[X,Y,Z: DWORD]: WORD read GetItems write SetItems; default;
  end;

function TArray3.GetItems(X,Y,Z: DWORD): WORD;
begin
    RESULT := A[(X * SY * SZ) + (Y * SZ) + Z];
end;

procedure TArray3.SetItems(X,Y,Z: DWORD; Value: WORD);
begin
    A[(X * SY * SZ) + (Y * SZ) + Z] := VALUE;
end;

constructor TArray3.Create(X,Y,Z: DWORD);
begin
    inherited Create;
    SX := X;
    SY := Y;
    SZ := Z;
    GetMem(A,X*Y*Z*SizeOf(WORD));
end;

destructor TArray3.Destroy;
begin
    FreeMem(A);
    inherited;
end;

var
    I,J,K: Integer;
    C: WORD;
begin
    X := 3;
    Y := 3;
    Z := 3;

    XXX := TArray3.Create(3,3,3);

    C := 0;
    for I:= 0 to 2 do
    begin
      for J:= 0 to 2 do
      begin
        for K:= 0 to 2 do
        begin
          XXX[I,J,K] := C;
          C := C + 1;
        end;
      end;
    end;

    for I:= 0 to 2 do
    begin
      for J:= 0 to 2 do
      begin
        for K:= 0 to 2 do
        begin
          C := XXX[I,J,K];
          Memo1.Lines.Add(IntToStr(C));
        end;
      end;
    end;


yTake  2023-11-07 13:48:38  No: 151231

AAAAAさん、ありがとうございます。
入れ違いになってしまっていました。
先に、GetMem領域からの入れるデータの取り出し方を示して頂けていました。
メモリー領域からポインターを用いて、配列要素を取り出せる関数(その逆は手続き)を自分で定義する分けですね。

試してみます。


yTake  2023-11-07 15:19:44  No: 151232

AAAAAさん、
後述頂けたTArray3の方ですが、ポインター領域(GetMem)から配列データを行毎に取り出すわけではなく、要素ごとに値を書き込んだり取り出したりするのですね。

現在、行毎にファイルから読み込んだポインター領域から要素ごとにポインターで配列データを取り出していて、(300x300x300)のデータで約8~9秒掛かっています。これを1~2秒程度まで短縮したいのですが、
今、I/O(外部)アクセスを伴いますが、ファイルからの一括読み込みで約1秒で完了しています。
I/O(外部)アクセスを伴わない(メモリー操作のみ)要素ごとにポインター領域から取り出しが8~9秒ととても遅く感じます。
ファイルからの一括読み出し:
ptr2  :  PWord;
i, j, k  : Word;
begin
  GetMem( ptr2, マトリックスサイズ );
  cnt  :=  fs.Read( ptr2^, マトリックスサイズ);   //  ファイルから一括呼び出し  ここは早い

  for  k := 0 to Z次元サイズ - 1 do
    for  j := 0 to Y次元サイズ - 1 do
      for  i := 0 to X次元サイズ - 1 do
      begin
          MTX[ i, j, k ]  :=  ptr2^;       //  シーケンシャルに取り出し、要素ごとに代入  ここの3重ループが遅い
          inc( ptr2 );
      end;
end;
この手法では8~9秒掛かってしまいます。

これまででファイルから行毎の読み出しでは、やはり1~2秒で完了出来ました。
各要素ごとに読み出すと、メモリー操作だけでも、遅くなってしまう様です。
ポインター領域から行毎に読み出し、動的配列に書き込むにはどの様にすると良いでしょう?


AAAAA  2023-11-07 15:43:30  No: 151233

 for  k := 0 to Z次元サイズ - 1 do
    for  j := 0 to Y次元サイズ - 1 do
      for  i := 0 to X次元サイズ - 1 do
      begin
          MTX[ i, j, k ]  :=  ptr2^;       //  シーケンシャルに取り出し、要素ごとに代入  ここの3重ループが遅い
          inc( ptr2 );
      end;
end;

これいらない


AAAAA  2023-11-07 16:05:44  No: 151234

 ※WORD から BYTE に変更してある
  TA = array[0..0] of Byte;
  PA = ^TA;

 TArray3 = class
  private
    SX: DWORD;
    SY: DWORD;
    SZ: DWORD;
    function GetItems(X,Y,Z: DWORD): Byte;
    procedure SetItems(X,Y,Z: DWORD; Value: Byte);
  public
    DATA: PA;
    constructor Create(X,Y,Z: DWORD); overload;
    destructor Destroy; override;
    property Items[X,Y,Z: DWORD]: Byte read GetItems write SetItems; default;
  end;

function TArray3.GetItems(X,Y,Z: DWORD): Byte;
begin
    RESULT := DATA[(X * SY * SZ) + (Y * SZ) + Z];
end;

procedure TArray3.SetItems(X,Y,Z: DWORD; Value: Byte);
begin
    DATA[(X * SY * SZ) + (Y * SZ) + Z] := VALUE;
end;

constructor TArray3.Create(X,Y,Z: DWORD);
begin
    inherited Create;
    SX := X;
    SY := Y;
    SZ := Z;
    //GetMem(DATA,X*Y*Z*SizeOf(WORD));
end;

destructor TArray3.Destroy;
begin
    //FreeMem(DATA);
    inherited;
end;

procedure TForm6.Button1Click(Sender: TObject);
var
    I,J,K: Integer;
    B1,B2: Byte;
    MemoryStream: TMemoryStream;
    A3: TArray3;
begin
    MemoryStream := TMemoryStream.Create;
    A3 := TArray3.Create(3,3,3);
    MemoryStream.LoadFromFile('Project5.exe');

    A3.DATA := MemoryStream.Memory;

    for I:=0 to 2 do
    begin
      for J:=0 to 2 do
      begin
        for K:=0 to 2 do
        begin
          B1 := A3[I,J,K];
          MemoryStream.Read(B2,1);
          Memo1.Lines.Add(IntToStr(B1) + ' ' + IntToStr(B1));
        end;
      end;
    end;

    A3.Free;
    MemoryStream.Free;

end;


yTake  2023-11-07 17:03:32  No: 151235

AAAAAさん、ありがとうございます。

ファイルから動的配列へ読み出すルーチンと言う事でしょうか?
TMemoryStreanを使うのですね。
既にTFileStreamで作成しているので、修正は少々大変そうです。

現、TFileStreamを使用している状況での不可解な状況について、コメント頂く事は可能でしょうか?
先のRESでも触れました様に行毎のファイルからの直接読み込みでは、問題も生じています。
その為、一括で全て読み込み、動的配列へコピーする案が1つです。
動的配列へ直接ファイルから読み込めれば、コピーの手間が省けます。また、その読み込みも短時間です。
然しながら、問題は読み込み可能なマトリックサイズです。(300x300x300)ではAccess Violationが発生してしまいます。(100x100x100)でエラーなく進めます。
ただ、(300x300x300)でも動的配列への読み込み自体はエラーなく完了出来ています。
分ってきた事は、ファイルからのデータを保存する為の動的構造体配列があるのですが、そのサイズが、ファイルから行毎に配列データを読み込んでいる途中で、そのサイズが変わってしまい、その為、その後のデータ読み込みでエラーになっている様です。
ファイル毎に読み込むべきデータの数が違うため、受け皿としての構造体配列も動的です。
例えば、読み込むべきデータ数が50個として、上述の三次元配列はその内の一項目と言う事になります。
(300x300x300)を読み込む直前まではこの構造体配列の要素は50個ですが、三次元配列を読み込むと(エラーはありません)、構造体配列は131071個と膨大な数になっています。
この時、この受けの構造体に初めに読み込んだ、1番目と2番目のデータ内容が書き変わってしまっています。
どうも、配列操作がうまくいっていない様子ですが、その場所が分かりません。
idata2  :  Array of Array of Array of Word;                    //  実際には構造体のメンバとして定義
begin
    SetLength( RMTX[idx].idata2, 300, 300, 300 );
    for  k := 0  to  Z次元のサイズ-1  do
      for  j := 0  to  Y次元のサイズ-1  do
      begin
           fs.Read( idata2[0][j][k], Sizeof(Word)*X次元のサイズ);
      end;
end;
この様にコーディングして、ファイルからの読み込み自体は完了されます。
SetLengthで300x300x300を確保した時点では、構造体数は正常です。
三重forループのk=9まで構造体数は青樹ですが、k=10で構造体数が188となっていました。(元の構造体数は57)
非常に不可思議です。

如何でしょう。


AAAAA  2023-11-07 19:24:23  No: 151236

>ファイルから動的配列へ読み出すルーチンと言う事でしょうか?
ちがう
動的配列なんて使用していない

>然しながら、問題は読み込み可能なマトリックサイズです。(300x300x300)ではAccess Violationが発生してしまいます。(100x100x100)でエラーなく進めます。
おそらくメモリ不足


vram  2023-11-08 09:21:24  No: 151240

1年前に動的配列でメモリー不足の回避を質問されてましたが
その続きですかね?

>TMemoryStreanを使うのですね。
>既にTFileStreamで作成しているので、修正は少々大変そうです。

TFileStreamのReadを何回か繰り返して計測すれば気づくと思いますが
fs.Readが遅い原因では?

そのファイルサイズに比例してメモリを消費しますが
TMemoryStreanのようにファイルを一度にメモリに読み込めば処理はすぐ終わると思われます

ループ処理内にProgressbarとApplication.Processmessageを使ってるみたいですが
毎回呼んでるわけではないですよね?

※名前かぶるので20年前の名前に変更


yTake  2023-11-08 13:28:17  No: 151242

AAAAAさん、vramさん、ありがとうございます。

AAAAAさん
動的配列は使用せず、TMemoryStreamで読み込んだデータ領域へIndexで参照できる様にしたと言う事でしょうか?

メモリー不足・・・
一括読み込みした後に、ポインターでシーケンシャルに読み出し、動的配列へ代入した場合にはエラーにはなりません。
配列サイズが大き過ぎるとも思えない様です。

vramさん
以前の問い合わせと関連があるかも知れませんが、一括読み込みした場合、エラーにはならないのでメモリー不足ではない様にも思えます。

fs.readでもGetMemで一括で読みだしているので、ファイルからの読み出しとしては、満足しています。
GetMemした領域へはポインターでのアクセスなので、面倒なので、動的配列へ代入し直して、以降の処理に備えたいと思いました。
ただ、この代入作業が非常に時間が掛かっているので、高速化の為、ファイルから動的配列へ直接読み込めないか?と思いました。
行毎にfs.Readする事で、一括読み込みと遜色のない速度で読み込めていたので、その点は良かったのですが、何故かエラー(access violation)が生じる様になりました。(読み込み自体でエラーはありません。その後の処理で発生します)
一括読み出し後シーケンシャルに代入した動的配列とファイルから行毎に読み込んだ動的入れるは同じものです。念の為。
行毎に読み込むとその後の処理でエラーが生じる為、一括読み込みで進める事にしたいのですが、
データの代入で時間が掛かっている点を高速化したいと思っています。
一案は、AAAAAさんに示唆頂けたMemory上へIndex参照する関数などを作るなどを考えています。(動的配列へ代入せず、メモリー上へアクセスする)
然しながら、この配列要素の演算結果を各要素毎に格納する必要もあるので、動的配列は必要となり、演算結果を格納すると言う事は動作としては各要素を代入するのとほぼ同じであり、やはり時間が掛かってしまいます。
この様な配列要素での演算を高速に処理する方法はあるのでしょうか?

"Application.ProcessMesage"はループ内で毎回呼び出しています。セオリー知らずで失礼しました。
進捗を示す為なので、ProgessbarのValueを変更する度に呼び出していました。
いつ呼ぶのが、正しいタイミングなのでしょう?


yTake  2023-11-08 14:09:03  No: 151243

動的配列への代入で時間を要する要因として、受け側に構造体構造の影響と分かってきました。
元々、
type  RMTX_Data = Record
         data1  :  Word;
         data2  :  Single;
         data3  :  Single;
         data4  :  Byte;
    End;
type  RRGB  = Record
         RGB  :  Array [ 1 .. 5 ] of RMTX_Data;
    End;
type  RMTX = Record
         MTX  :  Array of Array of Array of RRGB;
    End;
この場合、ループさせるのは、MTXです。RGBが固定なら、(1)
for k := 0 to Zmax - 1 do
  for j := 0 to Ymax - 1 do
    for i := 0 to Xmax - 1 do
    begin
      MTX[ i, j, k ].RGB[1].data3 := ptr^;
      inc( ptr );
    end;
となります。
サイズの定義も
SetLength( MTX, 300, 300, 300 );
で済みます。

これを改変して、
type  RMTX_Data = Record
         data1  :  Array of Array of Array of Word;
         data2  :  Array of Array of Array of Single;
         data3  :  Array of Array of Array of Single;
         data4  :  Array of Array of Array of Byte;
    End;
type  RMTX = Record
         MTX  :  RMTX_Data;
    End;
とした場合、(RRGBも省く)(2)
for k := 0 to Zmax - 1 do
  for j := 0 to Ymax - 1 do
    for i := 0 to Xmax - 1 do
    begin
      MTX.data3[ i, j, k ]  := ptr^;
      inc( ptr );
    end;
サイズ定義は、RMTX_Dataの要素ごとに行なう必要があり、少々手間です。
SetLength( MTX.data3, 300, 300, 300);
然しながら、動作時間は、(2)の方が(1)より約半分で済んでいる様です。
個人的には(1)の方が好みですが、速度には替えられません。

配列の操作で高速化の常套手段などあるでしょうか?

また、ふと疑問に思いましたが、例えば、動的配列と静的配列とで動作速度は大きく違いますか?


vram  2023-11-08 14:19:59  No: 151244

問題点の切り分け

1.データが正しく書き込み出来ているか?
2.データは正しく読み込めているか?
3.データは理想通りの処理時間で読み込めるか?
4.Application.ProcessMesageが適切な頻度で呼ばれているか?

(1)
書き込む処理で作ったファイルをバイナリエディタなどで確認し
予定通りの内容になっているかを確認

(2)
1バイトずつ読み込んだり
一気に読み込んだのであればそれをバイト単位でループさせて
正しく読めているかを(1)で書き込むデータと比較して確認

(3)
三次元のループ処理でそこまで時間がかかるとは思えません
(ファイルの読み書きや描画があると遅いですが)
自分なら1次元配列にファイルサイズ分のデータを読み込んでおいて
それを動的三次元配列にコピーします

(4)
繰り返す回数は事前にわかるはず
そしてProgessbarもMax 100で使われていると思います

ProgessbarのValueに書き込む値が整数の変数 nに入っているとして
1%ごとの進捗表示であれば nの値とValueを比較して同じならValueに入れる必要もありませんし
Application.ProcessMesageも必要ないので

if Progessbar1.Value = n then continue;
Progessbar1.Value := n;
Application.ProcessMesage;

こんな感じで
1%ごとの進捗は不要で10%でいいやって場合は if の前に
n := (n div 10) * 10;
としてnの精度を10単位にしてしまえばいいかと


yTake  2023-11-08 16:06:04  No: 151245

vramさん、ありがとうございます。

(1) 確認出来ています。問題ありません。
(2) 確認しています。
 一括読み込みしてポインターで動的配列へ代入した場合で、データは問題ありません。
 データは積層された面とみなして、画像化して、想定通りの画像が得られています。
(3) ここが問題です。
私も三次元のループ処理に時間がかかり過ぎな印象です。
本事例で要素数は300x300x300=27000000個です。
同数のデータを、一括ですが、ファイルから読み込む時は1秒ほどで完了しています。(ファイルアクセスとループを分ける為)
一方で、この読み込んだデータへ要素毎に全データへアクセスすると4秒ほど掛かっています。(代入処理のみで、描画処理はありません)
この後、正規化したり256階調化する必要があり画像が表示されるまで更に4~5秒掛かってしまい、トータルで10秒弱となっています。
同じデータを同様に画像化して表示するのに2秒程度で完了する実行ファイルの例があります。(exeファイルなのでソースは確認出来ていません)同程度の速度が実現出来ると思っています。約5倍の速度差はどこにあるか、特定出来ていません。
(4) Progressbar.Valueを変更したら、Application.Processmessageを呼ぶと言う事ですね。問題ありません。

何か、余計な事しているのでしょうが、分りません。
根本的に、やり方が違うのでしょうか???


vram  2023-11-08 16:30:58  No: 151246

 > Progressbar.Valueを変更したら、Application.Processmessageを呼ぶ

Progressbar.Valueの値が 1の時に
Progressbar.Value := 1 と、Application.Processmessageが呼ばれると
画面が更新されないのに再描画と、Application.Processmessageで処理に時間がかかりますよ
ループの一番真ん中において何千回と呼び出したらダメですよ
という話です

ファイル読み込みは手が空いたときこっちでも三次元動的配列を作ってファイルから読み書きするサンプルで確認してみます

手元にある似たようなもので 33メガの画像データを読み込んで描画するというのがあるのですが
読み込みに1秒、描画に1秒程度です


yTake  2023-11-08 22:43:51  No: 151247

ご助言、ありがとうございます。
Valueが変化した時に描画してApplication.Processmessageを実行させています。

> ファイル読み込みは手が空いたときこっちでも三次元動的配列を作ってファイルから読み書きするサンプルで確認してみます
ご面倒をお掛けします。よければ、お手隙の時にでもお願いします。

> 読み込みに1秒、描画に1秒程度です
33MBでもそれくらいですか、本事例でもそれくらいで実行出来れば良いのですが、、、


vram  2023-11-09 09:17:50  No: 151249

新規プロジェクトでボタンを4つ置いてそれぞれにプログラムを書いてみました

Button1 : 動的配列のサイズ決定とサンプルデータ作成
Button2 : 動的配列をファイルに書き込む 工夫してないので2~3秒 ファイルサイズ 1,954k
Button3 : 動的配列にファイルから読み込む 工夫してないので2~3秒
Button4 :  動的配列にファイルから読み込む 正確に計測していないけど 処理時間は数マイクロ秒程度

普段はバイト単位なんで「PWordArray」を始めて使った
これで良いのかはよく知らないがデータは読めた

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
  private
    { Private 宣言 }
    Fwdata  :  Array of Array of Array of Word;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

// 動的配列のサイズ決定とサンプルデータ書き込み
procedure TForm1.Button1Click(Sender: TObject);
var
  i,j,k,n: Integer;
begin
  SetLength( Fwdata, 100, 100, 100 );
  n := 0;
  for k := 0 to 99 do begin
    for j := 0 to 99 do begin
      for i := 0 to 99 do begin
        Fwdata[k,j,i] := n;
        n := (n + 1) and $FFFF;
      end;
    end;
  end;

end;

// 動的配列をファイルに書き込む※工夫無し
procedure TForm1.Button2Click(Sender: TObject);
var
  fs : TFileStream;
  w : Word;
  i,j,k: Integer;
begin
  fs := TFileStream.Create('test.bin',fmCreate);
  try
    w := 100;
    fs.Write(w,SizeOf(w));
    fs.Write(w,SizeOf(w));
    fs.Write(w,SizeOf(w));
    for k := 0 to 99 do begin
      for j := 0 to 99 do begin
        for i := 0 to 99 do begin
          w := Fwdata[k,j,i];
          fs.Write(w,SizeOf(w));
        end;
      end;
    end;

  finally
    fs.Free;
  end;
end;

// 動的配列にファイルから読み込む※工夫無し
procedure TForm1.Button3Click(Sender: TObject);
var
  fs : TFileStream;
  w,w1,w2,w3 : Word;
  i,j,k: Integer;
begin
  fs := TFileStream.Create('test.bin',fmOpenRead);
  try
    fs.Read(w1,SizeOf(w1));
    fs.Read(w2,SizeOf(w2));
    fs.Read(w3,SizeOf(w3));
    SetLength( Fwdata, w1, w2, w3 );

    for k := 0 to w1-1 do begin
      for j := 0 to w2-1 do begin
        for i := 0 to w3-1 do begin
          fs.Read(w,SizeOf(w));
           Fwdata[k,j,i] := w;
        end;
      end;
    end;

  finally
    fs.Free;
  end;
  Caption := 'Finish';

end;

// 動的配列にファイルから読み込む※工夫あり
procedure TForm1.Button4Click(Sender: TObject);
var
  fs : TFileStream;
  w,w1,w2,w3 : Word;
  i,j,k,size,n: Integer;
  pWTbl : PWordArray;
begin
  fs := TFileStream.Create('test.bin',fmOpenRead);
  try
    size := fs.Size;
    GetMem(pWTbl, size);
    fs.Position := 0;
    fs.Read(pWTbl[0], size);
    w1 := pWTbl[0];
    w2 := pWTbl[1];
    w3 := pWTbl[2];
    SetLength( Fwdata, w1, w2, w3 );

    n := 3;
    for k := 0 to w1-1 do begin
      for j := 0 to w2-1 do begin
        for i := 0 to w3-1 do begin
          w := pWTbl[n];
          Fwdata[k,j,i] := w;
          Inc(n);
        end;
      end;
    end;
  finally
    fs.Free;
  end;
  Caption := 'Finish';
end;


yTake  2023-11-09 15:02:57  No: 151250

vramさん、早速ありがとうございます。
サンプルを試してみました。
PWordArrayを使用する事で非常に高速に取り込める(300x300x300でも試しました)様です。動的配列への代入も300x300x300で1秒掛かっていないくらい高速の様です。

私の方でもPWordArrayを使用してみました。然しながら、一括読み込み後、要素を個別に取り出すforループで”範囲チェックエラー”が生じてしまいます。メモリー不足でしょうか?三重ループのインデックスはいずれも範囲内、PWordArayのインデックスも読み出したサイズ以内ですが、、、
ただ、サンプルで300x300x300も試しましたが、前記の通り問題ありませんでした。

ところで、サンプルのPWordArrayを使用する方法は、私のポインターPWordでアクセスする方法と近いと思います。
ptr2 :=  PWord;
と定義して、GetMem(ptr2,読み出すマトリックスのサイズ)で一括で読み出し、
三重ループで動的配列へ代入しています。
data2[i,j,k] := ptr2^;
inc(ptr2);
を回しています。
これで、範囲チェックエラーは発生しません。
但し、この三重ループ部分だけで何故か1~2秒掛かってしまっています。
ポインターでアクセスしているので速度的にはそれほど差はない様に思いますが、PWordArrayの方が高速と言う事でしょうか。
残念ながら、PWordArrayでは今のところ何故か範囲チェックエラーが発生して、データ読み込み自体が完了出来ません。

また、別の時間が掛かる処理が見つかりました。
ファイルから読み込んだ動的配列のデータに演算を施しているのですが、それに4~5秒くらい掛かっています。
三重ループを回して、同じ動的配列のdata2に対して、ある変数(param)を乗じて実数化し格納(data3)します。併せて、その実数の最大値(max)を見つけます。(1秒くらい)
data3[i,j,k] := data[i,j,k]*param;
もう一度三重ループを回して、各data3の最大値に対する比率を算出(data4)し格納します。更に、この比率を256階調化(data5)しています。(4秒くらい)
data4[i,j,k] := data3[i,j,k] / max;
data5[i,j,k] := Trunc(255*(1-data4[i,j,k]));
除算や関数”Trunc”を使っている為、時間が掛かっていると思います。ただ、外部とのI/Oを伴わないメモリー上の作業の為、もっと早くても良い様に思えます。何かこの様な処理を高速に行えないでしょうか?

なお、ファイルから読み出すデータが2バイト整数ならPWordArrayで良いですが、4バイト実数の場合、PSingleArrayと言う型は無さそうです。
今現在、PSingleで受ける事を考えてはいますが、、、


vram  2023-11-09 16:19:30  No: 151251

> 4バイト実数の場合

質問の表題と内容からするとここまでの答えになるのですが
実際は違う型だったり構造が異なるのでしょうか?

サンプルでは全ての要素がWord型であることを前提に作ったのでPWordArrayで一気に読み込み後で処理しました
しかし途中に他の型が挟まるような場合は厳しいですね

そういう場合はPByteArrayで読み込んで
w :=  ptr2^;
inc(ptr2);
data2[i,j,k] := w or (ptr2^ shl 8);
inc(ptr2);

のような感じにするかな

> GetMem(ptr2,読み出すマトリックスのサイズ)で一括で読み出し
読み込む回数が増えるとその分処理は時間がかかります
一度に読み込むとメモリの問題が出てくるかもしれませんが
読み込む回数は出来るだけ減らしましょう

メモリ間同士の演算であれば演算速度の問題だけで
あとはアルゴリズムを工夫して高速化するしかありません

そういえば思い出したけど
TFileStream の代わりに TBufferedFileStream使えばいいなじゃない?

どの処理に時間がかかっているのかは
QueryPerformanceFrequency
を使って各処理をミリ秒単位で計測しましょう
※Delphiデバッガに標準で欲しい機能ですが


yTake  2023-11-09 17:01:38  No: 151252

vramさん、ありがとうございます。

ファイルには様々な型のデータが混在しています。
ただ、マトリックス・データは全て同一の型で構成されます。
異なるファイルから読み込む場合にマトリックスサイズの他、データ型が異なる事があります。

ご助言ありがとうございます。読み込む回数はなるべく一回で考えています。
ただ、読み込み途中に範囲チェックエラーだったり、一括読み込みした後の演算で時間が掛かったりと、思い通りにいきません。
一括読み込み後の処理は、動的配列の各要素に実数を掛ける、各要素の最大値に対する比率を求める、その比率を255階調化するの3つだけです。
これらを三重ループを回しているだけです。乗算や除算はこれ以上工夫しようがない様に思います。255階調化はTruncを用いないで出来るか、試してみます。

TBufferedFileStreamですね。調べてみます。
ただ、一括読み込みで済めば、ファイルからの読み込み以上に時間が掛かっている処理が問題です。

QueryPerformanceFrequencyも活用を試みてみます。


yTake  2023-11-10 08:33:25  No: 151253

TBufferedFileStreamはヘルプでは見当たらない様です。

QueryPerformanceFrequencyもヘルプでは見つからない様です。
DELPHI標準機能ではないとの事です。追加で組み込むなど手順が必要なのでしょうか?

本題とは別件になると思うので、取りあえず、一旦閉めます。

大変、ありがとうございました。


take  2023-11-10 09:11:28  No: 151254

>ファイルには様々な型のデータが混在しています

他の人からも指摘あると思いますが
サンプルが三次元Wordの動的配列なので
それ前提で回答しておられます

追加要素を後出しされるとつらい

その様々な型のデータを混在ってところを説明しないと
回答は得られないと思います

混在ということなのでレコード型にしてみました

動的配列のレコード型に様々なデータを持たせて
それを書き込むサンプルです
3分で作ったので細かいところは適当です
※レコード型の初期化を忘れた

要は
動的三次元配列は連続したメモリにない?ので
じゃあ一次元配列なら連続してるんじゃね?
ということで作ってみました

これ以上追加情報が来るときつい
と思って検索したら

このときの質問の続きですよね?
https://www.petitmonte.com/bbs/answers?question_id=29967

回答の返事がないからそこで止まってるだけ
最初からわかってたらわざわざサンプル作らなかったのに・・・

ボツにするのは勿体ないのでサンプルほい

【追加】
type  TRec = record
    DataWord   : Word;
    DataSingle: Single;
  end;

【修正】

    { Private 宣言 }
    Fwdata  :  Array of Array of Array of TRec;

// 動的配列のサイズ決定とサンプルデータ書き込み
procedure TForm1.Button1Click(Sender: TObject);
var
  i,j,k,n: Integer;
begin
  SetLength( Fwdata, 100, 100, 100 );
  n := 0;
  for k := 0 to 99 do begin
    for j := 0 to 99 do begin
      for i := 0 to 99 do begin
        Fwdata[k,j,i].DataWord := n;
        Fwdata[k,j,i].DataSingle := n / 100;
        n := (n + 1) and $FFFF;
      end;
    end;
  end;

end;

// 動的配列をファイルに書き込む※工夫無し
procedure TForm1.Button5Click(Sender: TObject);
var
  fs : TFileStream;
  w : Word;
  s : Single;
  r : TRec;
  i,j,k: Integer;
begin
  fs := TFileStream.Create('test.bin',fmCreate);
  try
    w := 100;
    fs.Write(w,SizeOf(w));
    fs.Write(w,SizeOf(w));
    fs.Write(w,SizeOf(w));
    for k := 0 to 99 do begin
      for j := 0 to 99 do begin
        for i := 0 to 99 do begin
          //r := Fwdata[k,j,i];
          fs.Write(Fwdata[k,j,i],SizeOf(TRec));
        end;
      end;
    end;

  finally
    fs.Free;
  end;

end;

// 動的配列にファイルから読み込む※工夫無し
procedure TForm1.Button6Click(Sender: TObject);
var
  fs : TFileStream;
  w,w1,w2,w3 : Word;
  r : TRec;
  i,j,k,size,n: Integer;
  pWTbl : PWordArray;
begin
  fs := TFileStream.Create('test.bin',fmOpenRead);
  try
    fs.Read(w1,SizeOf(w1));
    fs.Read(w2,SizeOf(w2));
    fs.Read(w3,SizeOf(w3));
    SetLength( Fwdata, w1, w2, w3 );

    for k := 0 to w1-1 do begin
      for j := 0 to w2-1 do begin
        for i := 0 to w3-1 do begin
          fs.Read(r,SizeOf(r));
          Fwdata[k,j,i] := r;
        end;
      end;
    end;
  finally
    fs.Free;
  end;
  Caption := 'Finish';
end;

// 動的配列にファイルから読み込む※工夫あり
procedure TForm1.Button7Click(Sender: TObject);
var
  fs : TFileStream;
  w,w1,w2,w3 : Word;
  r : TRec;
  i,j,k,size,n: Integer;
  pWTbl : array of TRec;
begin
  fs := TFileStream.Create('test.bin',fmOpenRead);
  try
    fs.Read(w1,SizeOf(w1));
    fs.Read(w2,SizeOf(w2));
    fs.Read(w3,SizeOf(w3));
    SetLength( Fwdata, w1, w2, w3 );

    size := w1 * w2 * w3;
    //GetMem(pWTbl, size);
    SetLength(pWTbl,size);
    fs.Read(pWTbl[0],  size);

    n := 0;
    for k := 0 to w1-1 do begin
      for j := 0 to w2-1 do begin
        for i := 0 to w3-1 do begin
          Fwdata[k,j,i] := pWTbl[n];
          Inc(n);
        end;
      end;
    end;
  finally
    fs.Free;
  end;
  Caption := 'Finish';
end;


vram  2023-11-10 09:13:17  No: 151255

混同しないように名前変えてたの忘れてた、こっちの名前です


yTake  2023-11-10 10:05:31  No: 151256

vramさん、
行き違いの様で申し訳ありません。
ただ、続きかと言われると違います。
今回は、少なくとも、一括読み出しでメモリー上へ読み出して、そこからポインターで動的配列へ代入する事は出来ています。
この代入処理に時間が掛かってしまう為、動的配列へ直接読み込めないか?或いは、代入処理の高速化又は時間が掛かってしまっている要因がもし分かればと言う感じです。
いずれにしましても、誤解やご迷惑となってしまった点につきましては、お詫び致します。

サンプルは利用させて頂いています。
代入処理はやはり非常に高速である事が確認されました。
また、少し改変させて頂き、ポインターで代入しても同様に高速である事も確認出来ました。

私の方では、何か、読み出しループ以外の影響があると言う事の様です。別件になるので、また改めます。


※返信する前に利用規約をご確認ください。








  このエントリーをはてなブックマークに追加