コンピュータ サイエンスでは、配列プログラミングとは、値のセット全体に一度に操作を適用できるソリューションを指します。このようなソリューションは、科学やエンジニアリングの環境でよく使用されます。
配列プログラミングをサポートする最新のプログラミング言語(ベクトル言語または多次元言語とも呼ばれる)は、スカラーに対する操作を一般化して、ベクトル、行列、高次元配列に透過的に適用できるように設計されています。これらには、 APL、J、Fortran、MATLAB、Analytica、Octave、R、Cilk Plus、Julia、Perl Data Language (PDL)が含まれます。これらの言語では、配列全体を操作する操作は、ベクトル命令を実装するベクトルプロセッサで実行されるかどうかに関係なく、ベクトル化された操作と呼ばれることがあります[1] 。配列プログラミングプリミティブは、データ操作に関する幅広いアイデアを簡潔に表現します。簡潔さのレベルは、場合によっては劇的になる可能性があります。配列プログラミング言語のワンライナーが、数ページのオブジェクト指向コードを必要とすることも珍しくありません[例が必要]。
配列の概念
配列プログラミングの基本的な考え方は、操作が値のセット全体に一度に適用されることにあります。これにより、プログラマーは個々のスカラー操作の明示的なループに頼ることなく、データの集合全体を考え、操作できるため、 配列プログラミングは高レベルのプログラミングモデルになります。
ケネス・E・アイバーソンは配列プログラミング(実際にはAPLを参照)の根拠を次のように説明しました。[2]
ほとんどのプログラミング言語は数学的記法に比べて明らかに劣っており、たとえば応用数学者にとって重要だと考えられるような思考ツールとして使われることはほとんどありません。
この論文のテーマは、プログラミング言語に見られる実行可能性と普遍性の利点は、数学表記法の利点と 1 つの一貫した言語で効果的に組み合わせることができるというものです。表記法を記述して学習することの難しさと、その意味を習得することの難しさを区別することが重要です。たとえば、行列積を計算する規則を学習するのは簡単ですが、その意味 (結合法則、加算に対する分配法則、線形関数や幾何学的演算を表す能力など) を習得するのは、別の、はるかに難しい問題です。
実際、表記法の示唆性そのものが、探索のために示唆する多くの特性のために、学習がより困難であるように思わせる可能性があります。
[...]
コンピュータやプログラミング言語のユーザーは、アルゴリズムの実行効率を主に気にすることが多いため、ここで紹介したアルゴリズムの多くを即座に却下してしまう可能性があります。アルゴリズムを明確に記述すれば、通常、より効率的なアルゴリズムを簡単に導き出すための基礎として使用できるため、このような却下は近視眼的です。
配列プログラミングと配列思考の根底にあるのは、個々の要素が類似または隣接しているデータの特性を見つけて活用することです。データを構成要素 (またはスカラー量) に暗黙的に分解するオブジェクト指向とは異なり、配列指向はデータをグループ化して均一な処理を適用することを目指します。
関数のランクは、数学のテンソルランクに似ており、配列プログラミング言語全般にとって重要な概念です。データを操作する関数は、操作する次元の数によって分類できます。たとえば、通常の乗算は、0 次元データ (個々の数値) を操作するため、スカラー ランク関数です。外積演算は、スカラーではなくベクトルを操作するため、ベクトル ランク関数の例です。行列乗算は、2 次元オブジェクト (行列) を操作するため、2 ランク関数の例です。集約演算子は、入力データ配列の次元を 1 次元以上削減します。たとえば、要素を合計すると、入力配列は 1 次元削減されます。
用途
配列プログラミングは暗黙的な並列化に非常に適しており、これは今日多くの研究のテーマとなっています。さらに、 1997年以降に開発・製造されたIntelおよびその互換CPUには、 MMXから始まり、 SSSE3や3DNow!に至るまで、基本的なSIMD配列機能を含むさまざまな命令セット拡張が含まれていました。これは2020年代まで続き、 AVX-512などの命令セットによって、現代のCPUは洗練されたベクトルプロセッサとなっています。配列処理は、1つの物理プロセッサがアイテムのグループに対して同時に操作を実行するのに対し、並列処理は大きな問題を小さな問題に分割し ( MIMD )、多数のプロセッサによって部分的に解決することを目的とするという点で、並列処理とは異なります。2023年の時点では、複数のコアを持つプロセッサや、数千の汎用コンピューティングコアを持つGPUが一般的です。
言語
配列プログラミング言語の標準的な例としては、 Fortran、APL、Jがあります。他には、A+、Analytica、Chapel、IDL、Julia、K、 Klong、Q、MATLAB、GNU Octave、Scilab、FreeMat、Perl Data Language (PDL) 、R、Raku、S-Lang、SAC、Nial、ZPL、Futhark、TI-BASIC などがあります。
スカラー言語
CやPascalなどのスカラー言語では、演算は単一の値にのみ適用されるため、a + b は2 つの数値の加算を表します。このような言語では、1 つの配列を別の配列に追加するにはインデックスとループが必要であり、そのコーディングは面倒です。
i = 0 ; i < n ; i ++ )の場合、j = 0 ; j < n ; j ++の場合、a [ i ] [ j ] + = b [ i ] [ j ] です。
配列ベースの言語、例えばFortranでは、上記のネストされたforループは1行の配列形式で記述できます。
a = a + b
あるいは、オブジェクトの配列の性質を強調するために、
a (:,:) = a (:,:) + b (:,:)
C のようなスカラー言語には、言語自体の一部としてネイティブの配列プログラミング要素はありませんが、これは、これらの言語で書かれたプログラムがベクトル化の基礎となる技術 (つまり、CPU のベクトルベースの命令がある場合はそれを使用するか、複数の CPU コアを使用する) をまったく利用しないという意味ではありません。GCC などの一部の C コンパイラは、最適化レベルで、ヒューリスティックによってメリットがあると判断したコード セクションを検出してベクトル化します。別のアプローチはOpenMP APIによって提供され、複数の CPU コアを利用して該当するコード セクションを並列化できます。
配列言語
配列言語では、演算はスカラーと配列の両方に適用されるように一般化されています。したがって、a + b は、 aとbがスカラーの場合は 2 つのスカラーの合計を表し、配列の場合は 2 つの配列の合計を表します。
配列言語はプログラミングを簡素化しますが、抽象化ペナルティと呼ばれるコストがかかる可能性があります。[3] [4] [5]加算はコーディングの残りの部分から独立して実行されるため、最適で最も効率的なコードが生成されない可能性があります。(たとえば、同じ実行中に同じ配列の他の要素の加算が後で発生し、不必要な繰り返し検索が発生する可能性があります。) 最も洗練された最適化コンパイラであっても、異なるプログラムセクションまたはサブルーチンに現れる可能性のある2つ以上の明らかに異なる関数を統合するのは非常に困難です。ただし、プログラマーはこれを簡単に実行し、配列上の同じパスで合計を集計してオーバーヘッドを最小化できます。
エイダ
前述のCコードは、配列プログラミング構文をサポートする Ada言語[6]では次のようになります。
A := A + B ;
オーストラリア
APL は、構文糖なしで単一文字の Unicode シンボルを使用します。
A ← A + B
この操作は、任意のランクの配列(ランク 0 を含む)と、スカラーおよび配列に対して機能します。Dyalog APL は、拡張された割り当てによって元の言語を拡張します。
A + ← B
アナリティカ
Analytica は Ada と同様の表現の経済性を提供します。
A := A + B;
ベーシック
ダートマス BASICの第 3 版 (1966 年) には、行列と配列の操作のための MAT ステートメントが含まれていました。
DIM A ( 4 ), B ( 4 ), C ( 4 ) MAT A = 1 MAT B = 2 * A MAT C = A + B MAT PRINT A , B , C
マタ
Stataの行列プログラミング言語 Mata は、配列プログラミングをサポートしています。以下では、加算、乗算、行列とスカラーの加算、要素ごとの乗算、添え字、および Mata の多くの逆行列関数の 1 つを示します。
.マタ:
: A = ( 1 , 2 , 3 ) \( 4 , 5 , 6 )
: あ
1 2 3
+---------------+
1 | 1 2 3 |
2 | 4 5 6 |
+-------------+
: B = ( 2 .. 4 ) \( 1 .. 3 )
: ば
1 2 3
+---------------+
1 | 2 3 4 |
2 | 1 2 3 |
+-------------+
: C = J ( 3 , 2 , 1 ) // 3行2列の1の行列
: こ
1 2
+--------+
1 | 1 1 |
2 | 1 1 |
3 | 1 1 |
+--------+
: D = A + B
: だ
1 2 3
+---------------+
1 | 3 5 7 |
2 | 5 7 9 |
+-------------+
: E = A * C
: え
1 2
+----------+
1 | 6 6 |
2 | 15 15 |
+----------+
: F = A: * B
: ふ
1 2 3
+----------------+
1 | 2 6 12 |
2 | 4 10 18 |
+----------------+
: G = E : + 3
: グ
1 2
+----------+
1 | 9 9 |
2 | 18 18 |
+----------+
: H = F[( 2 \ 1 ), ( 1 , 2 )] // 添字を付けてFの部分行列を取得し、
: // 行1と行2を入れ替える
: は
1 2
+----------+
1 | 4 10 |
2 | 2 6 |
+----------+
: I = invsym (F' * F) // 一般逆関数 (F*F^(-1)F=F)
: // 対称正半定値行列
: 私
[対称]
1 2 3
+------------------------------------------+
1 | 0 |
2 | 0 3.25 |
3 | 0 ~1.75 。9444444444 |
+------------------------------------------+
:終わり
マテリアライズド
MATLABでの実装により、Fortran 言語を使用した場合と同じ経済性が実現します。
A = A + B ;
MATLAB 言語のバリエーションとして、GNU Octave言語があります。これは、拡張された割り当てによって元の言語を拡張したものです。
A += B ;
MATLABとGNU Octaveはどちらも、行列乗算、行列の逆行列、線形方程式の数値解法などの線形代数演算をネイティブにサポートしており、ムーア・ペンローズ擬似逆行列も使用できます。[7] [8]
2 つの配列の内積の Nial の例は、ネイティブ行列乗算演算子を使用して実装できます。がサイズa[1 n] の行ベクトルで、 がbサイズ [n 1] の対応する列ベクトルである場合。
a * b;
対照的に、エントリワイズ積は次のように実装されます。
a .* b;
同じ数の要素を持つ 2 つの行列の内積は、(:)与えられた行列を列ベクトルに再形成する補助演算子 と転置演算子を使用して実装できます'。
A(:)' * B(:);
ラSQL
rasdamanクエリ言語は、データベース指向の配列プログラミング言語です。たとえば、次のクエリで 2 つの配列を追加できます。
A 、BからA + Bを選択
R
R 言語はデフォルトで配列パラダイムをサポートしています。次の例は、2 つの行列を乗算し、その後にスカラー (実際には 1 要素のベクトル) とベクトルを加算するプロセスを示しています。
> A <- matrix ( 1 : 6 , nrow = 2 ) # !!これはnrow=2です...そしてAは2行です> A [,1] [,2] [,3] [1,] 1 3 5 [2,] 2 4 6 > B <- t ( matrix ( 6 : 1 , nrow = 2 ) ) # t()は転置演算子です !!これはnrow=2です...そしてBは3行です --- Aの定義と明らかに矛盾しています> B [,1] [,2] [1,] 6 5 [2,] 4 3 [3,] 2 1 > C <- A %*% B > C [,1] [,2] [1,] 28 19 [2,] 40 28 > D <- C + 1 > D [,1] [,2] [1,] 29 20 [2,] 41 29 > D + c ( 1 , 1 ) # c() はベクトル [,1] [,2] [1,] 30 21 [2,] 42 30を作成します
楽
Rakuはメタ演算子を介して配列パラダイムをサポートしています。[9] 次の例は、ハイパー演算子とプラス演算子を組み合わせて配列@aと@bを加算する方法を示しています。
[ 0 ] >私の @a = [[ 1 , 1 ],[ 2 , 2 ],[ 3 , 3 ]];
[[ 1 1 ] [ 2 2 ] [ 3 3 ]]
[ 1 ] >私の @b = [[ 4 , 4 ],[ 5 , 5 ],[ 6 , 6 ]];
[[ 4 4 ] [ 5 5 ] [ 6 6 ]]
[ 2 ] > @a »+« @b ;
[[ 5 5 ] [ 7 7 ] [ 9 9 ]]
数学的推論と言語表記
行列の左除算演算子は、行列の意味特性を簡潔に表現します。スカラーの等価物と同様に、係数 (行列) の (行列式A)が null でない場合、A * x = b両辺にの逆数Aを左乗算することで (ベクトル) 方程式を解くことができます( MATLAB と GNU Octave の両方の言語で)。 がフルランクの正方行列である場合、次の数学的記述が成り立ちます。
A−1A^-1A
A^-1 *(A * x)==A^-1 * (b)(A^-1 * A)* x ==A^-1 * b(行列乗算の結合性)x = A^-1 * b
ここで、 は==同値関係演算子です。前のステートメントは、3 番目のステートメントが他のステートメントより先に実行される場合、有効な MATLAB 式でもあります (丸め誤差のため、数値比較は false になる可能性があります)。
システムが過剰決定系である場合、つまり のA行数が列数より多い場合、次のように
擬似逆関数(MATLAB および GNU Octave 言語では) を逆関数 に置き換えることができます。A+pinv(A)A−1
pinv(A) *(A * x)==pinv(A) * (b)(pinv(A) * A)* x ==pinv(A) * b(行列乗算の結合性)x = pinv(A) * b
しかし、これらの解法は最も簡潔なものでもなく(例えば、過剰決定系を表記的に微分する必要性が依然として残る)、最も計算効率が良いわけでもない。後者の点は、スカラー等価の を再び考えると簡単に理解できる。a * x = bこの場合、解法には、より効率的な ではなく 2 つの演算が必要となる。問題は、一般に行列の乗算は可換ではないということである。これは、スカラー解法を行列の場合に拡張するには、
x = a^-1 * b次の操作が必要になるためである。x = b / a
(a * x)/ a ==b / a(x * a)/ a ==b / a(行列には可換性はありません!)x * (a / a)==b / a(結合法則は行列にも当てはまります)x = b / a
\MATLAB 言語では、スカラーの場合との類似性の本質的な部分を維持するために
左除算演算子が導入され、数学的推論が簡素化され、簡潔さが保たれています。
A \ (A * x)==A \ b(A \ A)* x ==A \ b(結合法則は行列にも適用され、交換法則はもはや必要ありません)x = A \ b
これはコーディングの観点から簡潔な配列プログラミングの例であるだけでなく、計算効率の観点からも簡潔な配列プログラミングの例であり、いくつかの配列プログラミング言語ではATLASやLAPACKなどの非常に効率的な線形代数ライブラリの恩恵を受けています。[10]
アイバーソンの以前の引用に戻ると、その背後にある理論的根拠は明らかである。
表記法を記述したり習得したりすることの難しさと、その意味を習得することの難しさを区別することが重要です。たとえば、行列の積を計算する規則を習得するのは簡単ですが、その意味 (結合法則、加算に対する分配法則、線形関数や幾何学的演算を表す能力など) を習得するのは別の、はるかに難しい問題です。実際、表記法の示唆性そのものが、探索すべき多くの特性を示唆するため、習得するのがより困難に思えることがあります。
サードパーティライブラリ
より簡潔な抽象化を提供するために専門的で効率的なライブラリを使用することは、他のプログラミング言語でも一般的です。C ++では、いくつかの線形代数ライブラリが言語の演算子のオーバーロード機能を活用しています。場合によっては、これらの言語の非常に簡潔な抽象化は、 PythonのNumPy拡張ライブラリ、Armadillo、Blitz++ライブラリのように、配列プログラミングパラダイムによって明示的に影響を受けています。[11] [12]
参照
参考文献
- ^ Stéfan van der Walt、S. Chris Colbert、Gaël Varoquaux (2011)。「NumPy配列:効率的な数値計算のための構造」。科学と工学におけるコンピューティング。13 (2) 。IEEE : 22–30。arXiv : 1102.1523。Bibcode : 2011CSE .... 13b..22V。doi :10.1109 / mcse.2011.37。S2CID 16907816。
- ^ アイバーソン、KE ( 1980)。「思考ツールとしての表記法」。Communications of the ACM。23 ( 8): 444–465。doi : 10.1145/358896.358899。
- ^ Surana P (2006). 言語抽象化のメタコンパイル(論文)。
- ^ Kuketayev. 「Java の小さなオブジェクトのデータ抽象化ペナルティ (DAP) ベンチマーク」。2009 年 1 月 11 日時点のオリジナルよりアーカイブ。2008年 3 月 17 日閲覧。
- ^ Chatzigeorgiou、Stephanides (2002)。「オブジェクト指向プログラミング言語と手続き型プログラミング言語のパフォーマンスとパワーの評価」。Blieberger、Strohmeier (編)。Proceedings - 7th International Conference on Reliable Software Technologies - Ada-Europe'2002。Springer。p. 367。ISBN 978-3-540-43784-0。
- ^ Ada リファレンスマニュアル: G.3.1 実数ベクトルと行列
- ^ 「GNU Octave マニュアル。算術演算子」。2011 年 3 月 19 日閲覧。
- ^ 「MATLAB ドキュメント。算術演算子」。2010 年 9 月 7 日時点のオリジナルよりアーカイブ。2011 年 3 月 19 日閲覧。
- ^ 「Raku Operator ドキュメントのメタ演算子セクション」。
- ^ 「GNU Octave マニュアル。付録 G Octave のインストール」 。2011年 3 月 19 日閲覧。
- ^ 「Armadillo 1.1.8 のリファレンス。Matlab/Octave 構文と概念的に対応する Armadillo 構文の例」。2011 年 3 月 19 日閲覧。
- ^ 「Blitz++ ユーザーズ ガイド。3. 配列式」。2011 年 3 月 23 日時点のオリジナルよりアーカイブ。2011 年 3 月 19 日閲覧。
外部リンク
- 「悪臭のないループ」プログラミング
- 配列言語の発見
- 「配列の種類」プログラミング
