模块 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 数组更节省空间的整数和浮点元素存储,特别是因为除了标准的 OCaml 双精度浮点数和 32 位和 64 位整数类型外,它们还支持“小”类型,例如单精度浮点数和 8 位和 16 位整数。
  • 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 中或从中读取回 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

如上值类型所示,种类为 float16_eltfloat32_eltfloat64_elt 的 Bigarrays 使用 OCaml 类型 float 进行访问。复数种类 complex32_eltcomplex64_elt 的 Bigarrays 使用 OCaml 类型 Complex.t 进行访问。整数种类的 Bigarrays 使用足够大的最小 OCaml 整数类型来表示数组元素:对于 8 位和 16 位整数 Bigarrays 以及 OCaml 整数 Bigarrays 使用 int;对于 32 位整数 Bigarrays 使用 int32;对于 64 位整数 Bigarrays 使用 int64;对于平台原生整数 Bigarrays 使用 nativeint。最后,种类为 int8_unsigned_elt 的 Bigarrays 也可以作为字符数组而不是小整数数组进行访问,方法是使用种类值 char 而不是 int8_unsigned

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

三维数组。

通用 Bigarrays 和固定维度 Bigarrays 之间的强制转换

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。

重塑 Bigarrays

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 的专门版本,用于重塑为三维数组。

Bigarrays 和并发安全

当从多个域并发访问 bigarray 时,必须小心:访问 bigarray 永远不会导致程序崩溃,但不同步的访问可能会产生令人惊讶的(非顺序一致的)结果。

原子性

每个访问多个数组元素的 bigarray 操作都不是原子的。这包括切片、bliting 和填充 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)。