特大な動的配列の領域確保について

解決


yTake  2020-01-04 11:26:16  No: 148415

明けましておめでとうございます。

早速ですが、
構造体をメンバーに持つ多次元(3次元)動的配列を使っています。配列要素数が概ね[100以下,100以下,200以下]の時は問題はなかったのですが、要素数が[200以上,200以上,300以上]の時に、SetLengthを実行したところで”例外クラスEOutOfMemory(メモリーが足りません)”が発生します。

別の領域確保方法はないでしょうか?
GetMemoryやVirtualAlloc等を検討しています。

GetMemoryやVirtualAllocの場合、ポインターとしての領域確保になると思います。
領域場所として構造体へのポインターを指定する場合、どの様に指定すれば良いでしょうか?

構造体名を"MTX_Dat"とした場合、
SetLength可能な場合は、
SetLength( MTX_Dat,  x, y, z );
としていました。
但し、
MTX : RMTX_Dat;
で、
RMTX_Data : Array of Array of Array of RMTX_Dat;
です。

一方で、GetMemの場合、
GetMem( pMTX, x * y * z * Sizeof( RMTX_Data ));
とするのだと思います。
ここで、
RMTX_Dat型へのポインター"pMTX"の定義が分かりません。

通常、Word型へのポインターは”PWord”、Single型へのポインターは”PSingle”として定義すると思います。

今回、RMTX_Dat型へのポインターを”PMTX_Dat”としてみましたが、(当然ですが)自動的に”PMTX_Dat”が作成される分けでありませんでした。

また、RMTX_Dat型へのポインターをpMTXで領域確保できたとして、その要素へのアクセスは、”pMTX^.メンバ名”でアクセス出来ると言う事で良いでしょうか?

RMTX_Dat型へのポインター変数の定義で詰まっています。

なお、SetLengthで確保可能な配列要素数の上限はいくつでしょうか?要素数でSetLengthで配列でアクセス、GetMemでポインターでアクセスを切り分けられればと思います。

当方、32bit Windows 10 Pro+DELPHI XE6 です。


yTake  2020-01-04 15:43:50  No: 148416

自己レスです。

構造体へのポインター、分かりました。
単に、構造体定義後に、ポインターの型名を指定して、構造体型のアドレス演算子として定義する様です。

ただ、状況はもう少し複雑で、構造体メンバも要素数固定の配列を為しています。

例えば、
要素数が固定の配列を形成しています。
type
    RMTX_Data = Record
             data1  :  LongWord;
             data2  :  Single;
             data3  :  Single;
             data4  :  Byte;
    End;

type
    RMTX_RGB = Record
               mtx  :  Array [ 1 .. 3 ] of RMTX_Data;
    end;
    pMTX  :  ^RMTX_RGB;                      //   要素配列へのポインターを定義

type
   RMTX  =  Record
     fname : String;
     ix, jy, kz : Word;     // 配列サイズ
          mtx  : Array of Array of Array of RMTX_RGB;      //  この配列が大き過ぎる場合がある
   End;

の場合、

ptrMTX=^RMTX;

を定義すると、
GetMem( ptrMTX, ix * jy * kz * SizeOf());
で、領域は確保される様です。(実行時エラーは出ません)

ptrMTX^.MTX[ 1 ].data1 := 0 ;
inc( ptrMTX, SizeOf( RMTX_Data ));

で、アクセス出来そうです。

ただ、連続的にアクセスする場合にはこれでOKですが、元々3次元の配列で任意の座標へアクセスする必要があります。
ポインターアドレスを配列座標で与える必要がありますが、
( ptrMTX + 式 )^.MTX[ 1 ].data1 := 0;
の様に、ポインターで演算するとコンパイルでエラーとなります。

実は、もっと単純なポインター(ptr : PLongWord;)で、
ptr^ + ( ptr + 1 )^ shl 8 + ( ptr + 2 )^ shl 16 + ( ptr + 3 )^ shl 24;
と言う演算ではコンパイル時エラーはなく実行時も問題はありません。

アドレス演算の式は、
(( ptrMTX + kz * k + jy * j + i ) * SizeOf( RMTX_RGB ))^.MTX[ 1 ].data1
の様になるのでは、と考え中です。

ポインターのアドレス演算のところでエラーとなっている原因が分かっていません。
アドバイス頂ければと思います。


yTake  2020-01-04 16:09:26  No: 148417

すみません。

ポインタ^アドレス演算の式が間違っていました。
正しくは、(ptrMTX + ix * jy * k + ix * j + i ) * SizrOf( RMTX_RGB ))^.MTX[ 1 ].data1
となると思います。


yTake  2020-01-04 18:08:19  No: 148418

すみません。
以前、自分でポインターの使用方法で問い合わせて頂いた事がありました。
その際とアドレス演算部分は重なる部分もありますが、
今回はポインターを使わずに済ませられない状況の様に思えます。

配列へ要素番号でアクセス出来ればポインター操作は不要になるのですが、配列設定時にメモリー不足となってしまいます。
設定する配列が大きくない時は問題無く動作しているので、ソースコード自体の問題ではない様に思えますが、どでしょうか?
特大な配列を定義するSetLengthではない設定方法があるのでしょうか?

また、当時は”pointer math”を使うとポインターアドレスの代数演算が可能との指摘も受けましたが、”pointer math”をよく理解できていません。

以前の問い合わせの確認を上、再検討してみます。


igy  2020-01-04 19:20:07  No: 148419

> 当方、32bit Windows 10 Pro+DELPHI XE6 です。

もし、Windows 10 の 64bit版 をお持ちなら、
それに、64bit アプリを試すのは、ダメですか?


yTake  2020-01-04 19:25:26  No: 148420

自己レスです。

質問が不明瞭になったかもしれません。

要点は、固定長配列の構造体を単位とする動的3次元配列へデータを読み込むというものです。
問題点は配列領域の確保にSetLengthが使えない場合があり、GetMemを使用するにあたり、3次元配列へポインターでアクセスする必要がありキャストなどの方法でうまくできないでしょうか?
以前の問い合わせでは、受け側の配列も大き過ぎる事はなく、SetLengthで済んでいました。
今回は配列が3次元と言う事もあってか、確保すべき領域が巨大で、SetLengthが使えない様です。
GetMemで確保した領域に配列でアクセスできないでしょうか?

これで質問の意図はお分かり頂けるでしょうか?


yTake  2020-01-04 19:31:01  No: 148421

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

今現在、手元のマシンは32bitですが、休みが明ければ64bitマシンも使用可能です。
ただ、休み明けまでに済ませておきたいのですが、32bitでは難しいのでしょうか?
ハード的なサイズ制限なら無理でしょうね。


Mr.XRAY  2020-01-04 21:29:33  No: 148422

動的配列は SetLength でメモリ領域を確保しなければ使用できません.
GetMem でメモリを確保しても使用できません.
これは動的配列の構造のためです.

1 つのプロセスで使用可能なメモリは限られています.
32 ビットの EXE では,ユーザ自身がコードで使用可能なのは高々数百 MB です.
64 ビットの EXE であれば事実上制限はありません (現在のところ).


Mr.XRAY  2020-01-04 21:38:09  No: 148423

>これは動的配列の構造のためです.

GetMem は連続したメモリ領域を確保します.
動的配列は連続したメモリ領域にデータを格納しません.
静的配列の場合は連続したメモリ領域にデータを格納します.


yTake  2020-01-05 00:32:34  No: 148424

Mr.XRAYさん、ありがとうございます。

そうですか。
やはり、SetLengthを使用する必要がある分けですね。
また、32bit版ではアクセス上限によりSetLengthで確保できる領域に制限があると言う事ですね。
64bit版では(事実上)制限がないと言う事であれば、現状のまま64bit版で特大配列データを試してみます。


take  2020-01-06 09:34:59  No: 148425

3次元配列の全ての座標を使用する必要があるのか無いのかで変わってくると思うのですが
全ての座標を使用するわけでは無い場合は一次元配列に見立てて管理すれば
メモリ消費を抑えられるかも

TList型にクラスを管理させてそのクラスに管理させたいデータの他に
xyz : Int64;

を用意して

インデックス値取得関数

fuction IndexOfxyz(i,j,k) : Integer;
var
  i :Integer;
begin
  result := -1;
  for i:=0 count-1 to begin
    if Items[i].xyz = ix * jy * k + ix * j + i then begin
      result := i;
      exit;
    end;
  end;
end;

値格納時

i := IndexOfxyz(xi,j,k);
if i = -1 then begin
  d := Add();
  d.xyz := ix * jy * k + ix * j + i;
  d.xxx := xxx; // 保存したい値を保存
end
else begin
  d := Items[i];
  d.xxx := xxx; // 保存したい値を保存
end;


yTake  2020-01-07 09:42:42  No: 148433

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

三次元データは全て読み込む必要があります。
ご助言の手法も部分的な運用が必要な場合に検討させて頂きます。

64bit Windows10でターゲットプラットフォーム=”64ビットWindows”で実行しましたら、問題なく読み込めました。
64bitは凄いですね。

皆様、ありがとうございました。


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








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