模組 Bigarray

module Bigarray: sig .. end

大型、多維、數值陣列。

此模組實現整數和浮點數的多維陣列,以下稱為「Bigarrays」,以區別於 Array 中描述的標準 OCaml 陣列。

此實作允許 OCaml 程式碼和 C 或 Fortran 數值函式庫之間有效率地共享大型數值陣列。

「Bigarrays」和標準 OCaml 陣列之間的主要差異如下:

  • Bigarrays 的大小不受限制,這與 OCaml 陣列不同。(在 32 位元平台上,一般浮點數陣列限制為 2,097,151 個元素,而其他類型的一般陣列則限制為 4,194,303 個元素。)
  • Bigarrays 是多維的。支援 0 到 16 之間的任意維度數量。相比之下,OCaml 陣列是一維的,需要將多維陣列編碼為陣列的陣列。
  • Bigarrays 只能包含整數和浮點數,而 OCaml 陣列可以包含任意 OCaml 資料類型。
  • Bigarrays 提供比一般 OCaml 陣列更具空間效率的整數和浮點數元素儲存,特別是因為它們支援「小」類型,例如單精度浮點數以及 8 位和 16 位整數,此外還有標準 OCaml 雙精度浮點數以及 32 位和 64 位整數類型。
  • Bigarrays 的記憶體佈局完全相容於 C 和 Fortran 中陣列的記憶體佈局,允許大型陣列在 OCaml 程式碼和 C / Fortran 程式碼之間來回傳遞,而無需任何資料複製。
  • Bigarrays 支援標準陣列無法有效提供的有趣高階操作,例如擷取子陣列,以及沿著某些維度「切片」多維陣列,所有這些都無需任何複製。

鼓勵此模組的使用者在其原始碼中執行 open Bigarray,然後透過簡短的點表示法來引用陣列類型和操作,例如 Array1.tArray2.sub

Bigarrays 支援所有 OCaml 特設多型操作


元素種類

Bigarrays 可以包含以下種類的元素:

每個元素種類在類型層級上都由以下定義的 *_elt 類型之一表示(由於技術上的注入原因,使用單一建構函式而非抽象類型定義)。

type float16_elt = 
| Float16_elt
type float32_elt = 
| Float32_elt
type float64_elt = 
| Float64_elt
type int8_signed_elt = 
| Int8_signed_elt
type int8_unsigned_elt = 
| Int8_unsigned_elt
type int16_signed_elt = 
| Int16_signed_elt
type int16_unsigned_elt = 
| Int16_unsigned_elt
type int32_elt = 
| Int32_elt
type int64_elt = 
| Int64_elt
type int_elt = 
| Int_elt
type nativeint_elt = 
| Nativeint_elt
type complex32_elt = 
| Complex32_elt
type complex64_elt = 
| Complex64_elt
type ('a, 'b) kind = 
| Float32 : (float, float32_elt) kind
| Float64 : (float, float64_elt) kind
| Int8_signed : (int, int8_signed_elt) kind
| Int8_unsigned : (int, int8_unsigned_elt) kind
| Int16_signed : (int, int16_signed_elt) kind
| Int16_unsigned : (int, int16_unsigned_elt) kind
| Int32 : (int32, int32_elt) kind
| Int64 : (int64, int64_elt) kind
| Int : (int, int_elt) kind
| Nativeint : (nativeint, nativeint_elt) kind
| Complex32 : (Complex.t, complex32_elt) kind
| Complex64 : (Complex.t, complex64_elt) kind
| Char : (char, int8_unsigned_elt) kind
| Float16 : (float, float16_elt) kind

每個元素種類都與一個 OCaml 類型相關聯,這是可以儲存在 Bigarray 中或從 Bigarray 讀回的 OCaml 值的類型。此類型不一定與陣列元素本身的類型相同:例如,其元素種類為 float32_elt 的 Bigarray 包含 32 位元單精度浮點數,但是從 OCaml 讀取或寫入其中一個元素會使用 OCaml 類型 float,這是 64 位元雙精度浮點數。

GADT 類型 ('a, 'b) kind 捕獲 Bigarray 中讀取或寫入的值的 OCaml 類型 'a 與表示 Bigarray 實際內容的元素種類 'b 的關聯。其建構函式列出 OCaml 類型與元素種類的所有可能關聯,並為了向後相容性原因在下方重新匯出。

在此處使用廣義代數資料類型 (GADT) 可撰寫傳回類型取決於引數類型的類型良好的多型函式,例如

  let zero : type a b. (a, b) kind -> a = function
    | Float32 -> 0.0 | Complex32 -> Complex.zero
    | Float64 -> 0.0 | Complex64 -> Complex.zero
    | Float16 -> 0.0
    | Int8_signed -> 0 | Int8_unsigned -> 0
    | Int16_signed -> 0 | Int16_unsigned -> 0
    | Int32 -> 0l | Int64 -> 0L
    | Int -> 0 | Nativeint -> 0n
    | Char -> '\000'
val float16 : (float, float16_elt) kind

請參閱 Bigarray.char

val float32 : (float, float32_elt) kind

請參閱 Bigarray.char

val float64 : (float, float64_elt) kind

請參閱 Bigarray.char

val complex32 : (Complex.t, complex32_elt) kind

請參閱 Bigarray.char

val complex64 : (Complex.t, complex64_elt) kind

請參閱 Bigarray.char

val int8_signed : (int, int8_signed_elt) kind

請參閱 Bigarray.char

val int8_unsigned : (int, int8_unsigned_elt) kind

請參閱 Bigarray.char

val int16_signed : (int, int16_signed_elt) kind

請參閱 Bigarray.char

val int16_unsigned : (int, int16_unsigned_elt) kind

請參閱 Bigarray.char

val int : (int, int_elt) kind

請參閱 Bigarray.char

val int32 : (int32, int32_elt) kind

請參閱 Bigarray.char

val int64 : (int64, int64_elt) kind

請參閱 Bigarray.char

val nativeint : (nativeint, nativeint_elt) kind

請參閱 Bigarray.char

val char : (char, int8_unsigned_elt) kind

如上述值的類型所示,使用 OCaml 類型 float 來存取種類為 float16_eltfloat32_eltfloat64_elt 的 Bigarray。使用 OCaml 類型 Complex.t 來存取複數種類 complex32_eltcomplex64_elt 的 Bigarray。使用足以表示陣列元素的最小 OCaml 整數類型來存取整數種類的 Bigarray:對於 8 位和 16 位整數 Bigarray 以及 OCaml 整數 Bigarray,使用 int;對於 32 位元整數 Bigarray,使用 int32;對於 64 位元整數 Bigarray,使用 int64;對於平台原生整數 Bigarray,使用 nativeint。最後,使用種類值 char 而非 int8_unsigned,也可以將種類為 int8_unsigned_elt 的 Bigarray 作為字元陣列而非小整數陣列來存取。

val kind_size_in_bytes : ('a, 'b) kind -> int

kind_size_in_bytes k 是用於儲存類型為 k 之元素的位元組數。

陣列佈局

type c_layout = 
| C_layout_typ
type fortran_layout = 
| Fortran_layout_typ

為了方便與現有的 C 和 Fortran 程式碼互通,此函式庫支援 Bigarrays 的兩種不同記憶體佈局,一種與 C 慣例相容,另一種與 Fortran 慣例相容。

在 C 風格的佈局中,陣列索引從 0 開始,且多維陣列以行優先格式佈局。也就是說,對於二維陣列,第 0 行的所有元素在記憶體中都是連續的,然後是第 1 行的所有元素,依此類推。換句話說,(x,y)(x, y+1) 的陣列元素在記憶體中相鄰。

在 Fortran 風格的佈局中,陣列索引從 1 開始,且多維陣列以列優先格式佈局。也就是說,對於二維陣列,第 0 列的所有元素在記憶體中都是連續的,然後是第 1 列的所有元素,依此類推。換句話說,(x,y)(x+1, y) 的陣列元素在記憶體中相鄰。

每個佈局樣式都在類型層級上分別由虛擬類型 Bigarray.c_layoutBigarray.fortran_layout 識別。

支援的佈局

GADT 類型 'a layout 表示兩種支援的記憶體佈局之一:C 風格或 Fortran 風格。其建構函式作為值在下方重新匯出,以實現向後相容性。

type 'a layout = 
| C_layout : c_layout layout
| Fortran_layout : fortran_layout layout
val c_layout : c_layout layout
val fortran_layout : fortran_layout layout

通用陣列(任意維度)

module Genarray: sig .. end

零維陣列

module Array0: sig .. end

零維陣列。

一維陣列

module Array1: sig .. end

一維陣列。

二維陣列

module Array2: sig .. end

二維陣列。

三維陣列

module Array3: sig .. end

三維陣列。

通用 Bigarray 和固定維度 Bigarray 之間的強制轉換

val genarray_of_array0 : ('a, 'b, 'c) Array0.t -> ('a, 'b, 'c) Genarray.t

傳回對應於給定零維 Bigarray 的通用 Bigarray。

val genarray_of_array1 : ('a, 'b, 'c) Array1.t -> ('a, 'b, 'c) Genarray.t

傳回對應於給定一維 Bigarray 的通用 Bigarray。

val genarray_of_array2 : ('a, 'b, 'c) Array2.t -> ('a, 'b, 'c) Genarray.t

傳回對應於給定二維 Bigarray 的通用 Bigarray。

val genarray_of_array3 : ('a, 'b, 'c) Array3.t -> ('a, 'b, 'c) Genarray.t

傳回對應於給定三維 Bigarray 的通用 Bigarray。

val array0_of_genarray : ('a, 'b, 'c) Genarray.t -> ('a, 'b, 'c) Array0.t

傳回對應於給定泛型 Bigarray 的零維 Bigarray。

val array1_of_genarray : ('a, 'b, 'c) Genarray.t -> ('a, 'b, 'c) Array1.t

傳回對應於給定泛型 Bigarray 的一維 Bigarray。

val array2_of_genarray : ('a, 'b, 'c) Genarray.t -> ('a, 'b, 'c) Array2.t

傳回對應於給定泛型 Bigarray 的二維 Bigarray。

val array3_of_genarray : ('a, 'b, 'c) Genarray.t -> ('a, 'b, 'c) Array3.t

傳回對應於給定泛型 Bigarray 的三維 Bigarray。

重新塑形 Bigarray

val reshape : ('a, 'b, 'c) Genarray.t ->
int array -> ('a, 'b, 'c) Genarray.t

reshape b [|d1;...;dN|] 將 Bigarray b 轉換為維度為 d1...dNN 維陣列。返回的陣列和原始陣列 b 共享它們的資料並具有相同的佈局。例如,假設 b 是一個維度為 12 的一維陣列,reshape b [|3;4|] 返回一個維度為 3 和 4 的二維陣列 b'。如果 b 具有 C 佈局,則 b' 的元素 (x,y) 對應於 b 的元素 x * 3 + y。如果 b 具有 Fortran 佈局,則 b' 的元素 (x,y) 對應於 b 的元素 x + (y - 1) * 4。返回的 Bigarray 必須與原始 Bigarray b 具有完全相同的元素數量。也就是說,b 的維度乘積必須等於 i1 * ... * iN。否則,會引發 Invalid_argument 異常。

val reshape_0 : ('a, 'b, 'c) Genarray.t -> ('a, 'b, 'c) Array0.t

Bigarray.reshape 專門用於重新塑形為零維陣列的版本。

val reshape_1 : ('a, 'b, 'c) Genarray.t -> int -> ('a, 'b, 'c) Array1.t

Bigarray.reshape 專門用於重新塑形為一維陣列的版本。

val reshape_2 : ('a, 'b, 'c) Genarray.t ->
int -> int -> ('a, 'b, 'c) Array2.t

Bigarray.reshape 專門用於重新塑形為二維陣列的版本。

val reshape_3 : ('a, 'b, 'c) Genarray.t ->
int -> int -> int -> ('a, 'b, 'c) Array3.t

Bigarray.reshape 專門用於重新塑形為三維陣列的版本。

Bigarray 與並行安全

當從多個域並行存取 bigarray 時,必須小心:存取 bigarray 永遠不會使程式崩潰,但未同步的存取可能會產生令人驚訝的(非循序一致的)結果。

原子性

每個存取多個陣列元素的 bigarray 操作都不是原子的。這包括切片、複製和填充 bigarray。

例如,考慮以下程式

open Bigarray
let size = 100_000_000
let a = Array1.init Int C_layout size (fun _ -> 1)
let update f a () =
  for i = 0 to size - 1 do a.{i} <- f a.{i} done
let d1 = Domain.spawn (update (fun x -> x + 1) a)
let d2 = Domain.spawn (update (fun x -> 2 * x + 1) a)
let () = Domain.join d1; Domain.join d2

執行此程式碼後,bigarray a 的每個欄位都是 2345。如果需要原子性,則使用者必須實作自己的同步機制(例如,使用 Mutex.t)。

資料競爭

如果兩個域僅存取 bigarray 的不相交部分,則觀察到的行為等同於來自兩個域的操作的某些循序交錯。

當兩個域在沒有同步的情況下存取相同的 bigarray 元素,且至少有一個存取是寫入時,就會發生資料競爭。在沒有資料競爭的情況下,觀察到的行為等同於來自不同域的操作的某些循序交錯。

在可能的情況下,應避免資料競爭,方法是使用同步來協調對 bigarray 元素的存取。

實際上,在存在資料競爭的情況下,程式不會崩潰,但觀察到的行為可能不等同於來自不同域的操作的任何循序交錯。

撕裂

在存在資料競爭的情況下,Bigarray 有一個明顯的警告:由於撕裂,並行的 bigarray 操作可能會產生令人驚訝的值。更精確地說,部分寫入和讀取的交錯可能會產生在循序執行中不存在的值。例如,在

let res = Array1.init Complex64 c_layout size (fun _ -> Complex.zero)
let d1 = Domain.spawn (fun () -> Array1.fill res Complex.one)
let d2 = Domain.spawn (fun () -> Array1.fill res Complex.i)
let () = Domain.join d1; Domain.join d2

之後,res bigarray 可能包含既不是 Complex.i 也不是 Complex.one 的值(例如 1 + i)。