はじめに:ALLOCATABLE 配列の自動再割付けの重要性
FORTRAN 2003 で導入された `ALLOCATABLE` 配列の自動再割付け機能は、プログラムの記述を劇的に簡潔にし、動的なデータ構造を扱う際の柔軟性を大幅に向上させました。特に、計算結果に応じて配列のサイズが変動する場合や、要素を追加・削除する必要がある場合に、手動でのメモリ管理の手間を省き、開発効率を高めることができます。この機能は、計算科学やエンジニアリング分野で、可変長のデータを扱う上で非常に強力な武器となります。
基礎知識:ALLOCATABLE 配列と自動再割付けとは
ALLOCATABLE 配列とは
`ALLOCATABLE` 配列とは、プログラムの実行中にメモリの割り当てや解放を動的に行うことができる配列のことです。宣言時にはサイズを指定せず、`ALLOCATE` 文によって実行時に必要なサイズを決定できます。これにより、プログラムの実行状況に応じてメモリ使用量を最適化することが可能になります。
自動再割付け (Automatic Reallocation)
FORTRAN 2003 以降、`ALLOCATABLE` 配列には「自動再割付け」という便利な機能が追加されました。これは、`ALLOCATABLE` 配列に値を代入する際に、代入される値のサイズと配列の現在のサイズが異なる場合に、コンパイラが自動的に配列のサイズを変更(再割り当て)してくれる機能です。
例えば、`A` という `ALLOCATABLE` 配列があり、`A = [A, new_val]` のような代入を行うと、`new_val` を追加するために配列 `A` のサイズが自動的に拡張されます。これは、配列の末尾に要素を追加するような操作を、非常に直感的に記述できることを意味します。
実装:自動再割付けによる配列の動的拡張
自動再割付けの最も典型的な使い方は、既存の配列に新しい要素を追加していく場合です。配列演算やスライシングと組み合わせることで、より柔軟なデータ操作が可能になります。
例えば、初期化された `ALLOCATABLE` 配列に、ループ処理の中で新しいデータを追加していくシナリオを考えます。
PROGRAM AutomaticReallocationExample
IMPLICIT NONE
! サイズが未定のALLOCATABLE配列を宣言
INTEGER, ALLOCATABLE :: dynamic_array(:)
INTEGER :: i, n
! 初期要素を代入し、配列を割り当てる
! ここでは、初期値として [1, 2] を代入することで、配列のサイズが自動的に2に割り当てられます。
dynamic_array = [1, 2]
PRINT , “Initial array: “, dynamic_array
PRINT , “Initial size: “, SIZE(dynamic_array)
! ループで要素を追加していく
n = 5 ! 追加したい要素の数
DO i = 1, n
! 新しい値を配列の末尾に追加する。
! dynamic_array に新しい値が含まれる配列を代入することで、
! 配列のサイズが自動的に拡張されます (自動再割付け)。
dynamic_array = [dynamic_array, i + 2]
PRINT , “After adding “, i + 2, “: “, dynamic_array, ” (Size: “, SIZE(dynamic_array), “)”
END DO
PRINT , “Final array: “, dynamic_array
PRINT , “Final size: “, SIZE(dynamic_array)
! 使用が終わったら、明示的に解放することも可能ですが、
! プログラム終了時には自動的に解放されます。
! IF (ALLOCATED(dynamic_array)) THEN
! DEALLOCATE(dynamic_array)
! END IF
END PROGRAM AutomaticReallocationExample
このプログラムでは、`dynamic_array = [dynamic_array, i + 2]` という行で、`dynamic_array` の現在の内容に新しい要素 `i + 2` を追加した配列を `dynamic_array` 自身に代入しています。この代入の際に、右辺の配列のサイズが左辺の `dynamic_array` の現在のサイズと異なるため、FORTRAN コンパイラは自動的に `dynamic_array` のサイズを拡張し、新しい要素を格納するためのメモリを再割り当てします。
サンプルプログラム:要素の削除と再構成
要素の追加だけでなく、配列の一部を削除し、残りの要素で新しい配列を再構成するような操作も、自動再割付けと配列演算を組み合わせることで実現できます。
PROGRAM ArrayDeletionExample
IMPLICIT NONE
INTEGER, ALLOCATABLE :: data(:)
INTEGER :: i, element_to_remove
! 初期データを割り当てる
data = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
PRINT , “Original data: “, data
PRINT , “Original size: “, SIZE(data)
! 削除したい要素のインデックスを指定 (例: 4番目の要素、つまり 40 を削除)
element_to_remove = 4
PRINT , “Removing element at index: “, element_to_remove
! 要素を削除し、新しい配列を再構成する
! data(1:element_to_remove-1) は、削除したい要素より前の部分
! data(element_to_remove+1:SIZE(data)) は、削除したい要素より後の部分
! これらを連結することで、削除された要素を除いた新しい配列を作成し、
! data に代入することで自動再割付けが行われます。
IF (element_to_remove > 0 .AND. element_to_remove <= SIZE(data)) THEN
IF (element_to_remove == 1) THEN
! 先頭の要素を削除する場合
data = data(element_to_remove+1:SIZE(data))
ELSE IF (element_to_remove == SIZE(data)) THEN
! 末尾の要素を削除する場合
data = data(1:element_to_remove-1)
ELSE
! 中間の要素を削除する場合
data = [data(1:element_to_remove-1), data(element_to_remove+1:SIZE(data))]
END IF
PRINT , "Data after removing element: ", data
PRINT , "New size: ", SIZE(data)
ELSE
PRINT , "Invalid index to remove."
END IF
! 別の例:偶数のみを残す
PRINT
PRINT , "Keeping only odd numbers..."
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
PRINT , "Original data for filtering: ", data
PRINT , "Original size: ", SIZE(data)
! 奇数のみを抽出して新しい配列を作成
! ここでは、INTENT(IN) の配列引数を持つ関数や、WHERE 文などを使って
! 効率的に抽出するのが一般的ですが、ここでは自動再割付けのデモとして
! 配列演算と結合による再構成を示します。
! 実際には、より効率的な方法がある場合があります。
INTEGER, ALLOCATABLE :: odd_numbers(:)
INTEGER :: k
! 奇数のみを格納するための仮の配列を作成 (サイズは不定)
odd_numbers = []
DO k = 1, SIZE(data)
IF (MOD(data(k), 2) /= 0) THEN
odd_numbers = [odd_numbers, data(k)]
END IF
END DO
data = odd_numbers ! 抽出された奇数配列を data に代入し、サイズを調整
PRINT , "Data after keeping odd numbers: ", data
PRINT , "New size: ", SIZE(data)
END PROGRAM ArrayDeletionExample
このサンプルでは、配列の一部(指定したインデックスの要素)を削除し、残りの部分を連結して新しい配列を作成しています。`data = [data(1:element_to_remove-1), data(element_to_remove+1:SIZE(data))]` の部分で、削除したい要素の前後のスライスを結合し、その結果を `data` に代入することで、自動再割付けが機能し、配列のサイズが調整されます。
応用と注意点:パフォーマンスへの影響
パフォーマンスへの影響
自動再割付けは非常に便利ですが、その動作には注意が必要です。配列のサイズが変更されるたびに、メモリの再割り当てとデータのコピーが発生します。この操作は、特に巨大な配列を扱う場合や、ループ処理の中で頻繁に実行される場合に、プログラムの実行時間を著しく増加させる可能性があります。
例えば、以下のようなコードは、ループの各イテレーションで配列が再割り当てされるため、非常に非効率的になる可能性があります。
! 非効率な例:ループ内で頻繁に配列を拡張
DO i = 1, 1000000
large_array = [large_array, some_value] ! 毎回再割り当てが発生
END DO
このような場合、あらかじめ十分な大きさの配列を `ALLOCATE` しておき、その配列にデータを格納していくか、あるいは、計算の終盤で一度だけまとめて配列のサイズを調整するなどの工夫が必要です。
メモリ管理のベストプラクティス
- ループ外での初期割り当て: 可能な限り、ループに入る前に配列の最大サイズを見積もり、一度に `ALLOCATE` しておくと効率的です。
- 要素数の事前見積もり: 処理の性質から、最終的に必要となる要素数が事前に分かっている場合は、そのサイズで `ALLOCATE` します。
- `MAXLOC` や `PACK`/`UNPACK` の活用: 特定の条件で要素を抽出・格納する際には、`PACK` や `UNPACK` といった標準的な配列操作関数や、`WHERE` 文と組み合わせることで、より効率的なコードを書くことができます。
- `RESIZE` ではなく `ALLOCATE` と `MOVE_ALLOC` を検討: FORTRAN 2008 以降では `RESIZE` という機能も導入されていますが、メモリの効率的な再利用や、パフォーマンスが重要な場面では、一度 `DEALLOCATE` して新しいサイズで `ALLOCATE` し、必要であれば `MOVE_ALLOC` を使うといった、より手動でのメモリ管理を検討することも有効です。
- デバッグとプロファイリング: 自動再割付けがパフォーマンスのボトルネックになっていないか、定期的にプロファイリングツールで確認することが重要です。
自動再割付けは、FORTRAN プログラミングにおける強力な機能ですが、その挙動を理解し、パフォーマンスへの影響を考慮して適切に使用することが、効率的で堅牢なプログラムを作成するための鍵となります。

コメント