64ビット環境 2GB 以上の生成オプション

対象 2GB以上 アドレス空間 コンパイルオプション

 PGI 64 ビット対応 Linux 版の製品における F77, F2003, C/C++ のコンパイラを使用して、2GB 以上の静的割付けされた単一オブジェクトがある場合のコンパイル・オプションの例を示します。2GB 以上のメモリ空間を利用する場合に必要なオプションです。ここでの説明は、Linux のみに適用できるものであり、Windows® や OS X においては、、「2GB以上の静的割付けの単一オブジェクト」を扱うことができる -mcmodel=medium と等価なプログラミングモデルは提供されていません
2013年3月26日更新 Copyright © 株式会社ソフテック

64ビット Windows®とMac OS 上での制約

 まず始めに、Linux 以外の OS 上での取り扱いについて説明します。Windows x64上、並びに Apple OS X 64bit 上では、「2GB以上の単一オブジェクト」を扱うことができる -mcmodel=medium オプションと等価な機能を提供しておりません。これは、Microsoft® Win64 プログラミング・モデルが、2GB を超える静的な単一データ・オブジェクトのハンドリングをサポートしていないためです。従って、単一あるいは、メモリ空間総量が 2GB を超える静的配列を扱うようなプログラムでは制約があります。例え Windows 64ビット版であっても、2GBを超える単一配列オブジェクトがプログラム上に存在する場合、静的な配列宣言はできません。これを回避する方法として、静的配列宣言を動的な配列宣言(Allocatable 配列宣言)に変更する方法がありますが、プログラムの改修が伴います。こうした制約は、PGI コンパイラの制約ではなく、Win64 プログラミングモデル上の制約です。Apple Mac OS 上においても、同様な理由で、2GB を超える静的な単一データオブジェクトを使用できません。従って、2GB以上のデータオブジェクトを扱うプログラムをコンパイルする場合は、現在のところ、このような制約がない Linux 版の方が適しています。

 以上の制約は、あくまでも 2GB 以上の静的配列を扱う場合のものですが、動的な配列宣言の配列を使用することにより、Windows(64bit) / OS X でも 2GB 以上のメモリ空間を使用することができます。特に Win64 上では、2GB 以上のアドレッシング(インデックスの計算)の処理を有効にするためのオプションがあります。Win64環境でのPGIコンパイラでは、以下のオプション(-Mlargeaddressaware) を付加することにより、アプリケーションが 2 GB を超えるアドレスを処理することをリンカに知らせることができます。これについては、マイクロソフト社のページを参照して下さい。なお、OS X には、こう言った類のオプションは存在しません。

pgfortran -Minfo -fastsse -Mlargeaddressaware -Mlarge_arrays test.f90
コンパイル時、リンク時共に指定して下さい。
  • -Mlargeaddressaware (PGI 7.2新設:Windowsのみ)Windows x64 用に 2GB 以上のアドレス・インデックシングを Windows のリンカーへ指示します。(RIP-relative addressing を使用する)。デフォルトは、noで、direct addressing 形式となります。
  • -Mlarge_arrays オプションは、配列添え字(インデックス)を 64 ビット整数で扱えるように変更します。この意味は、必要に応じて、64 ビット整数変数あるいは定数が、インデックスの計算において使用されることを意味します。但し、コンパイラが暗黙に 32 ビット整数から 64 ビット整数に変更することによって、思わぬところで副作用が現れるかもしれないことに注意してください。一般に、64 ビット・アドレッシングが必要なインデックス変数は、明示的に 64 ビット整数宣言をすることが最も安全な方法で、これ行ってかつ、このオプションを指定することを推奨します。

● Windows 上のメモリ割付制限

 マイクロソフト Windows 環境では、「静的な メモリallocation」領域は 64bit OS環境であっても 2GB 以内の領域しか使えません。これはマイクロソフト Windows の仕様です。プログラムどのような宣言を行うかによってメモリ配置が異なり、さらに、静的変数配列であればその最大長は 2GB に制約されることになります。 64-bitWindows の場合、プログラム上では以下のようなデータ割付制限があります。

  1. 静的データならびにコード :2GB

      Fortran の場合

    • メインプログラムおよびプロシージャで宣言された非ALLOCATABLE/非POINTER 配列(しかし、スカラ変数は静的とは限らない)
    • COMMON とモジュール内の変数
    • POINTER および Fortran モジュール内の ALLOCATABLE 変数の"記述子"は静的。(但し実態の割り当てられたデータは動的)
    • SAVEまたはデータ初期化された変数は静的
    • C/C++の場合

    • プロシジャーの外部でファイルのscoping 範囲内で宣言された変数あるいは構造体
  2. 動的データ :8TB
    • Fortrtan の場合は、実行時に ALLOCATE あるいは malloc で割り当てられた変数、配列
    • C/C++ の場合は、malloc あるいは new 関数で割り当てられた変数、配列
  3. スタックデータ :1GB (スタックサイズはリンカによって設定されますが、これはリンカのオプションで変更できる)
    • プロシジャー(サブルーチン)に動作移行する際に割り当てられ、プロシージャが終了するときに割当が解除されるメモリです。
    • Fortran の場合は、自動配列(仮引数の配列、COMMON あるいは MODULE 内の変数・配列)、OpenMP を有効にすると、変数がデフォルトでスタックに割り当てられる。Fortran の言語規約上、配列の一時コピーをスタックに置く場合がある。
    • C/C++ では、殆どのルーチン内のローカル変数やブロック内で宣言された変数はスタックに置かれる。

● 静的配列宣言 を 動的割付け配列に変更する方法(Windows あるいは Apple macOS 上)

 大規模な静的配列を ALLOCATABLE 配列で置き換え、プログラムの開始時に目的のサイズに割り当てる方法をとります。
例えば、

double precision a(n,n,n), b(n,n,n)
あるいは、
real*8 a(n,n,n), b(n,n,n)

と言った静的に割り当て宣言された配列で、そのサイズが大きくなりそうな配列に限って、プログラムを修正する必要があります。静的から動的割付の方法で配列の割付を変更する必要があります。プログラムの実行時に allocation されるようにします。

real(8),allocatable,dimension(:,:,:) :: a , b
..
N = 20000
..
allocate(a(n,n,n), b(n,n,n))
...

Linux 上での 64 ビットサポートオプション(2GB 以上の実行モジュール生成)

● 2GB を超えるプログラムの実行モジュールの生成オプション

pgf77/pgfortran -fastsse -mcmodel=medium -i8 test.f
pgcc/pgc++ -fastsse -mcmodel=medium test.c
  • -mcmodel=medium オプションは、64 ビットメモリ空間モデルの medium タイプを指定するものです。このオプションを付加することにより、2GB 以上のメモリ空間を利用することができます。
  • -Mlarge_arrays オプションは、PGI 5.2バージョンの場合は必要でしたが、PGI 6.0 以降では、
    -mcmodel=medium オプションの中に内包されましたので指定する必要はありません。このオプションを付加すると、単一オブジェクト(配列等)あるいは .bss のサイズが 2GB 以上をサポートすると共に、配列のインデックス計算において64 ビット整数(定数、変数)空間のサイズで計算されます。このオプションは、コンパイラが暗黙的に使用している32ビット整数を64ビット整数にプロモートします(この点は留意が必要です)この機能を無効にする場合は、-Mnolarge_arrays を -mcmodel=medium の後に記述指定します。
  • -i8 オプションは、INTEGER で宣言された、あるいは暗黙の宣言された整数を 8 バイト整数にコンパイラが置き換えてコードを生成します。4 バイトINTEGER は、2GB 以上のアドレス値を処理できないため、強制的にこの措置をプログラムに適用する場合に使用します。なお、プログラムコーディング上、明示的に INTEGER*4 として宣言されているものに対しては、このオプションは機能しません。この場合は、プログラムの修正が必要となります。-mcmodel=medium を使用する場合は、このオプションを使用しておくことが無難ですが、暗黙的に 8 バイト整数に変更されることに留意してください。
  • 上記のオプションは、64ビットサポートのオプションと -fastsse のみを例示しています。必要であれば、その他の最適化オプションも添えて、コンパイルしてください。

● プログラム内部に INTEGER 宣言があるが、4 Byte 整数以上の計算を行う場合

pgfortran -mcmodel=medium -i8 test.f
  • -i8 オプションは、INTEGER で宣言された、あるいは暗黙の宣言された整数を 8byte整数(INTEGER*8)に自動的に変換します。これにより、配列のインデックス計算で 4byte整数の範囲を超えたとしても、エラーが生じません。-i8 あるいは INTEGER*8 宣言された整数は、8 Byte 長のストレージ長となり、さらに 64bit の処理が行われます。なお、明示的に INTEGER*4 として宣言されているものに対しては、このオプションは機能しません。
  • C 言語のルーチンと Fortran ルーチンが混在した場合は、-i8 は指定できません。 -i8 は Fortran のみに有効な機能オプションです。言語間の引数のバイト長が異なる形になります。

● Linux 64 ビットシステムでのプログラミングモデル

PGF77、pgfortran、PGCC、PGC++ は、x86-64 Application Binary Interface で定義されている、 -mcmodel=small 並びに -mcmodel=medium アドレッシング・モデルの両方をサポートします。次に示す表は、これらのプログラミング・モデルでの制約事項を示します。また、下表は様々なコンパイル/リンクオプションの組み合わせで32ビット、64ビット実行モジュールを作成した際の 32ビット、64ビットの処理形態を示しております。

Programming Models on 64-bit Linux86-64 Systems
Combined Options
to PGI Compilers
Address
Arithmetic
Maximum Data Size
in Gbytes
コメント
A I AS DS TS  
-tp {32bit target} 32 32 2 2 2 32bit linux86 互換
-tp {64bit target} 64 32 2 2 2 64bit Addressing だが、メモリモデルは -mcmodel=small であり、データ領域は 2GB に制約される
-tp {64bit target} -fpic 64 32 2 2 2 -mcmodel=medium を伴うコンパイルでは、-fpic は指定できない
-tp {64bit target}
-mcmodel=medium
64 64 >2 >2 >2 64ビットデータ・アドレッシングをフル・サポートする
(-Mlarge_arraysオプションを包括している)

情報

-tp オプションは、明示的な CPU ターゲットを指定してクロスコンパイルを行うためのオプションです。デフォルトでは、コンパイルを行う「システム」のプロセッサ・ターゲットがその最適化のために使用され、かつ、当該システムの OS のビット数(64bit or 32bit)に応じた実行モジュールが生成されます。従って、64bit Linux のシステムの場合は、デフォルトで 64bit 実行バイナリが生成されますので、そのもとで、2GB 以上のメモリ空間を必要とするプログラムの場合は、上記の -mcmodel=medium を指定して下さい。

Address Type (A) アドレス計算に使用されるデータのビット数のサイズ(32-bit or 64-bit)
Index Arithmetic (I) 配列並びに他のデータ構造のインデックス計算で使用されるデータのビット数のサイズ。もし I が 32bit の場合、単一データオブジェクトのサイズは、2GB に制約される。
Maximum Array Size (AS) 任意の単一データオブジェクトの最大サイズ
Maximum Data Size (DS) 実行モジュールの中の .bss セクションにおける全てのデータオブジェクトの総和最大サイズ
Maximum Total Size (TS) 実行プログラムにおける全てのデータオブジェクトと実行テキスト・コードの総和した際の最大サイズ

 プログラムエリアとは、Linux オペレーティングシステムとユーザプログラムによって使用されるそれらが総和された領域です。ほとんどの 32ビットLinuxシステムでは、32ビットアドレッシングですので理論的には 2GB までアクセス可能ですが、通常 1GB のみがデータ領域として使用することができます。一方、64ビットシステム上でのデフォルトである small memory model の環境においても、ユーザデータあるいは実行モジュールの総和量は、1GB までに制約されます。これは、32ビットシステムと同様にシステム・ルーチンと共有ライブラリ、スタック等のアドレスが、2GB空間の内、その他の 1GB 領域を使用するため、この部分が Linux のカーネル管理領域として占有されます。但し、共有ライブラリを使用せず、静的なリンクにより固定アドレスで開始されるプログラムは、ほとんどの領域のメモリアクセス(2GB以内)が可能となります。
 64ビットシステム上での medium memory model の環境を構築するためには、-mcmodel=medium オプションをコンパイル/リンク時に指定する必要があります。このメモリモデルでは、2GB以上のデータオブジェクト並びに .bss セクションのサイズであっても、問題なく動作します。 リンク時に、-mcmodel=medium オプションが必要となる生成実行モジュールにリンクされるオブジェクト・ファイルは、-mcmodel=medium オプションあるは、-fpic のどちらかでコンパイルされたものに限ります-fpic オプションと -mcmodel=medium オプションを同時に使用してコンパイルすることはできません。すなわち、位置独立な実行モジュール (-fpic) の作成を行う場合は、-mcmodel=medium を使用することはできません。

● -mcmodel=medium時において実際に生じる可能性のある制約事項と対処

 64ビット環境における64ビットアドレッシング機能は、データサイズが非常に大きな場合、予期せぬ問題が生じる場合があります。

  • 初期化における問題(Initializing)
    データ文での非常に大きな配列の初期化では、非常に大きなアセンブラあるいはオブジェクトファイルのサイズになることがあります。これは、アセンブラ・ソースのライン数が初期化する配列の個々の要素ごとに必要なためです。また、コンパイルとリンクの時間は極めて長い時間を費やします。これを避けるには、非常に大きな配列の初期化は、宣言文で行うよりもむしろループ処理において定義するように変更することです。
  • スタックスペース(stack space)
    スタックスペースは、スタック・ベースであるデータを使用する場合に生じる問題で、具体的にはスタックサイズが小さいと言う問題で異常終了する場合があります。シェル環境での limit stacksize unlimited コマンドは、できるだけ多くのスタックサイズの確保を指示するものですが、その明確なサイズが指定されていないことと、物理メモリ量に依存するという問題があります。そのために、まず、limit stacksize 512M と指定した時、unlimited で指定されたものより大きなサイズかどうかを確認してください。もしそうならば、OS で指定されたスタックサイズのハード・リミットが設定されている可能性があり、必要であれば、ハード・リミット等の変更が必要となります。
  • ページ・スワッピング(page swapping)
    もし、実行モジュールが実装されている物理メモリ量よりも大きな場合は、ページ・スワッピングが頻繁に起こり、プログラムの実行が非常に遅くなるか、もしくは、途中で異常終了します。これは、コンパイラの問題ではありません。問題がページ・スワッピングによる問題かどうかを判断するためには、データサイズを小さくして検証してください。小さくして正常に動作する場合は、このページ・スワッピングの問題が考えられます。
  • コンフィギュアド・スペース(configured space)
    アプリケーションが必要とする十分なシステムのスワップ領域を確保しているかどうかを確認してください。もし、メモリ+スワップ領域が十分大きくない場合、実行時にセグメンテーション・フォールトが生じる場合があります。

● -mcmodel=medium and Large Array in C (Linux)

 静的配列のサイズの総量が 2GB を越えた C プログラムの例で、セグメンテーションフォールトを起こしてみます。

● C プログラム bigadd.c

[kato@photon29 SAMPLES]$ cat bigadd.c
#include <stdio.h>
#define SIZE 600000000 /* > 2GB/4 */

static float a[SIZE], b[SIZE];
int main()
{
long long i, n, m;
float c[SIZE]; /* goes on stack */

n = SIZE;
m = 0;
  for (i = 0; i < n; i += 10000) {
  a[i] = i + 1;
  b[i] = 2.0 * (i + 1);
  c[i] = a[i] + b[i];
  m = i;
  }
printf("a[0]=%g b[0]=%g c[0]=%g\n", a[0], b[0], c[0]);
printf("m=%lld a[%lld]=%g b[%lld]=%gc[%lld]=%g\n",m,m,a[m],m,b[m],m,c[m]);
return 0;
}

● Linux上で実行してみる

【コンパイル】 -mcmodel=mediumオプション
							
[kato@photon29 SAMPLES]$ pgcc -mcmodel=medium -fastsse -Minfo bigadd.c
main:
     12, Loop not vectorized: mixed data types
         Loop not vectorized: may not be beneficial
         Unrolled inner loop 4 times
         
【実行】 

[kato@photon29 SAMPLES]$ ./a.out
Segmentation fault
(セグメンテーションフォルト発生)

【コンパイル】 -Mchkstk(実行時のスタックサイズの出力)を追加し、ランタイムで stack sizeを把握する

[kato@photon29 SAMPLES]$ pgcc -mcmodel=medium -fastsse -Minfo -Mchkstk bigadd.c
main:
     12, Loop not vectorized: mixed data types
         Loop not vectorized: may not be beneficial
         Unrolled inner loop 4 times
         
[kato@photon29 SAMPLES]$ ./a.out
Error: in routine main there is a
stack overflow: thread 0, max 10228KB, used 0KB, request -1894967208B
スタックサイズが足りないことが分かる。ここで表示されるサイズの量は、あまり厳密に考えない。

【現在のスタックサイズの把握(bash)】 

[kato@photon29 SAMPLES]$ ulimit -s
10240

【スタックサイズの変更(bash) 3GB に変更後、実行】

[kato@photon29 SAMPLES]$ ulimit -s 3000000000

[kato@photon29 SAMPLES]$ ./a.out
a[0]=1 b[0]=2 c[0]=3
m=599990000 a[599990000]=5.9999e+08 b[599990000]=1.19998e+09c[599990000]=1.79997e+09

実行モジュール a.out の bss セクションのサイズ表示(2GB を越えていることが分かる)
[kato@photon29 SAMPLES]$ size --format=sysv a.out | grep bss
.bss             4800000064   6295360
[kato@photon29 SAMPLES]$ size --format=sysv a.out | grep Total
Total            4800003980  

● -mcmodel=medium and Large Array in Fortrran (Linux)

 2GB を越えるFortranプログラムで -mcmodel=medium を指定して実行する

● Fortran プログラム mat.f90 (6GB 以上)

[kato@photon30 SAMPLES]$ cat mat.f90
program mat
integer i, j, k, size, l, m, n
parameter (size=16000) ! >2GB
parameter (m=size,n=size)
real*8 a(m,n),b(m,n),c(m,n),d

do i = 1, m
do j = 1, n
  a(i,j)=10000.0D0*dble(i)+dble(j)
  b(i,j)=20000.0D0*dble(i)+dble(j)
enddo
enddo

!$omp parallel
!$omp do
do i = 1, m
do j = 1, n
  c(i,j) = a(i,j) + b(i,j)
enddo
enddo

!$omp do
do i=1,m
do j = 1, n
  d = 30000.0D0*dble(i)+dble(j)+dble(j)
  if(d .ne. c(i,j)) then
  print *,"err i=",i,"j=",j
  print *,"c(i,j)=",c(i,j)
  print *,"d=",d
  stop
  endif
enddo
enddo
!$omp end parallel

print *, "M =",M,", N =",N
print *, "c(M,N) = ", c(m,n)
end

● Linux上で実行してみる

【コンパイル】 -mcmodel=medium, -i8 オプション
							
[kato@photon30 SAMPLES]$ pgfortran -mcmodel=medium -i8 -mp -fast -Minfo mat.f90
mat:
      7, Loop interchange produces reordered loop nest: 8,7
         Unrolled inner loop 4 times
         Used combined stores for 2 stores
     14, Parallel region activated
     16, Parallel loop activated with static block schedule
         Loop interchange produces reordered loop nest: 17,16
         Generated an alternate version of the loop
         Generated vector sse code for the loop
         Generated 2 prefetch instructions for the loop
     22, Barrier
     23, Parallel loop activated with static block schedule
     24, Loop not vectorized/parallelized: contains call
     34, Barrier
         Parallel region terminated
         
【実行】 

[kato@photon30 SAMPLES]$ ulimit -s  (stacksize を調べる)
10240

[kato@photon30 SAMPLES]$ time ./a.out
 M =                    16000 , N =                    16000
 c(M,N) =     480032000.0000000

real    0m2.832s
user    0m7.645s
sys     0m1.899s

(a.out は 6GB 以上使用するプログラム)
[kato@photon30 SAMPLES]$ size --format=sysv a.out | grep bss
.bss             6144000416   6300672
[kato@photon30 SAMPLES]$ size --format=sysv a.out | grep Total
Total            6144008305