PEP 209 – 多维阵列

猫勺猫勺 04-29 84 阅读 0 评论

重要

PEP 已被撤回。

×

抽象

该 PEP 建议重新设计和重新实施 多维数组模块,Numeric,使其更容易添加 模块的新特性和功能。数字 2 的各个方面 将受到特别关注的是对阵列的有效访问 大小超过 1 GB,由不均匀的数据组成 结构或记录。所提出的设计使用了四个 Python 类:ArrayType、UFunc、Array 和 ArrayView;和低级 C-extension 模块,_ufunc,用于处理阵列操作 有效。此外,每种数组类型都有自己的 C 扩展名 定义强制规则、操作和方法的模块 对于该类型。此设计支持新的类型、功能和 以模块化方式添加的功能。新版本 将引入一些与当前 Numeric 的不兼容之处。

赋予动机

多维数组通常用于存储和操作 科学、工程和计算中的数据。Python 目前有 一个名为 Numeric(以下称为 Numeric 1)的扩展模块, 它为用户提供了一组令人满意的功能 操作中等大小(有序)的同构数据数组 10 MB)。对于访问更大的阵列(100 MB 或更多),请 可能是不均匀的数据,数字 1 的实现是 效率低下且繁琐。将来,由 用于附加功能的数字 Python 社区也是 可能是 PEP 211:向 Python 添加新的线性运算符,以及 225:Elementwise/Objectwise 运算符说明。

建议

该提案建议重新设计和重新实施 数字 1,以下称为数字 2,这将使新的 类型、特性和功能要添加到一个简单和 模块化方式。数字 2 的初始设计应侧重于 提供用于操作各种数组的通用框架 类型,并应启用添加新 数组类型和 UFuncs。更具体的功能方法 然后可以将各种学科分层到该核心之上。 这个新模块仍将称为 Numeric,并且大多数 在数字 1 中找到的行为将被保留。

建议的设计使用四个 Python 类:ArrayType、UFunc、 Array 和 ArrayView;以及一个低级 C 扩展模块来处理 数组高效操作。此外,每个数组类型 有自己的 C 扩展模块,用于定义强制规则, 操作,以及该类型的方法。在以后的日期,当核心 功能稳定,一些 Python 类可以转换为 C 扩展类型。


一些计划的功能包括:

  1. 改进了内存使用率

    此功能在处理大型数组时尤为重要 并且可以显着提高性能以及 内存使用情况。我们确定了内存使用情况的几个区域 可以改进:

    1. 使用局部强制模型

      而不是使用 Python 的全局强制模型,该模型创建 临时数组,数字 2 和数字 1 一样,将实现 PEP 208 中描述的局部胁迫模型推迟了 对经营者的胁迫责任。通过使用内部 缓冲区,可以对每个阵列进行强制操作 (包括输出数组),如有必要,在 操作。基准测试 [1] 表明,性能 大多数仅略有退化,并在 内部缓冲区小于 L2 缓存大小,并且 处理器负载过载。为了完全避免阵列强制, 允许在 数字 2.

    2. 避免创建临时数组

      在复杂的数组表达式中(即具有多个数组表达式) 操作),每个操作将创建一个临时数组,该数组 将被使用,然后由后续操作删除。一个 更好的方法是识别这些临时数组 并在可能的情况下重用其数据缓冲区,即当 数组的形状和类型与临时数组相同 创建。这可以通过检查临时数组的 引用计数。如果它是 1,那么它将被删除一次 操作已完成,并且是重用的候选项。

    3. 可选使用内存映射文件

      数字用户有时需要从非常大的数据访问数据 文件或处理大于可用数据的数据 记忆。内存映射数组提供了一种执行此操作的机制 通过将数据存储在磁盘上,同时使其看起来位于 记忆。内存映射阵列应改善对所有 文件,通过消除文件过程中的两个复制步骤之一 访问。Numeric 应该能够访问内存中和 内存映射数组透明。

    4. 记录访问

      在某些科学领域,数据以二进制形式存储在文件中 记录。例如,在天文学中,光子数据存储为 按到达时间顺序排列的光子的 1 维列表。这些 记录或类似 C 的结构包含有关 检测到的光子,例如它的到达时间、它在 探测器及其能量。每个字段可能属于不同的字段 类型,例如 char、int 或 float。这样的阵列引入了新的 必须处理的问题,特别是字节对齐 或者可能需要对数值执行字节交换 要正确访问的值(尽管字节交换也是 内存映射数据的问题)。数字 2 旨在 自动处理对齐和表示问题 当数据被访问或操作时。有两个 实施记录的方法;作为派生数组 类或特殊数组类型,具体取决于您的观点。 我们将此讨论推迟到“未解决的问题”部分。

  2. 其他阵列类型

    数值 1 有 11 种定义的类型:char、ubyte、sbyte、short、int、 long、float、double、cfloat、cdouble 和 object。没有 ushort、uint 或 ulong 类型,也没有更复杂的类型 例如对某些科学领域有用的位类型和 可能用于实现掩码数组。数字 1 的设计 使得添加这些和其他类型变得困难且 容易出错的过程。启用轻松添加(和删除)的步骤 新的数组类型,例如下面描述的位类型,重新设计 的 Numeric 是必要的。

    钻头类型

    数组之间丰富比较的结果是一个数组 布尔值。结果可以存储在以下类型的数组中 char,但这是不必要的内存浪费。一个更好的 实现将使用 bit 或 boolean 类型,压缩 数组大小是 8 倍。目前,这正在 为数字 1(由 Travis Oliphant 实现),并且应该是 包含在数字 2 中。

  3. 增强的数组索引语法

    扩展切片语法已添加到 Python 中,以提供更大的 通过允许操作数值数组时的灵活性 步长大于 1。这种语法可以很好地用作速记 获取规则间隔的索引列表。对于这些情况 如果需要不规则间隔的索引列表,则增强 数组索引语法将允许一维数组作为参数。

  4. 丰富的比较

    PEP 207 的实现:Python 2.1 中的丰富比较 在操作阵列时提供额外的灵活性。我们 打算在数字 2 中实现此功能。

  5. 阵列广播规则

    当标量和数组之间的操作完成时, 隐含的行为是创建一个具有相同形状的新数组 包含标量值的数组操作数。这叫做 阵列广播。它也适用于较低等级的数组, 例如向量。此隐式行为是在 Numeric 中实现的 1,也将在 Numeric 2 中实现。

设计与实施

数值 2 的设计有四个主要类:

  1. 数组类型:

    这是一个描述基本属性的简单类 数组类型,例如它的名称、它的大小(以字节为单位)、它的强制 与其他类型的关系等,例如

Int32 = ArrayType('Int32', 4, 'doc-string')

它与其他类型的关系是在 C 扩展时定义的 导入该类型的模块。对应的 Python 代码 是:

Int32.astype[Real64] = Real64

这表示 Real64 数组类型的优先级高于 Int32 数组类型。

针对核心提出了以下属性和方法 实现。可以在 单个基础,例如位类型的 .bitsize 或 .bitstrides。

属性:

.name:                  e.g. "Int32", "Float64", etc.
.typecode:              e.g. 'i', 'f', etc.
                        (for backward compatibility)
.size (in bytes):       e.g. 4, 8, etc.
.array_rules (mapping): rules between array types
.pyobj_rules (mapping): rules between array and python types
.doc:                   documentation string

方法:

__init__():             initialization
__del__():              destruction
__repr__():             representation

C-API:这仍然需要充实。

UFunc:

这个类是数字 2 的核心。它的设计类似于 ArrayType 的 UFunc 创建一个单一实例可调用 对象,其属性为 name、total 和 input number 参数、文档字符串和空 CFunc 字典;例如:

add = UFunc('add', 3, 2, 'doc-string')

定义后,add 实例没有与 因此,它不能做任何工作。CFunc 字典是 稍后填充或注册 C 扩展模块时 array-type 被导入。register 方法的参数为: 函数名称、函数描述符和 CUFunc 对象。这 对应的 Python 代码是

add.register('add', (Int32, Int32, Int32), cfunc-add)

在数组类型模块的初始化函数中,例如 Int32,有两个 C API 函数:一个用于初始化 强制规则,另一个用于注册 CFunc 对象。

当操作应用于某些数组时,该方法 被调用。它获取每个数组的类型(如果输出数组 不是给出的,它是根据强制规则创建的)和检查 与参数类型匹配的键的 CFunc 字典。 如果存在,则立即执行操作,否则 强制规则用于搜索相关操作并设置 的转换函数。然后,该方法调用 用 C 编写的计算方法,用于遍历每个数组的切片, 即:__call____call__

_ufunc.compute(slice, data, func, swap, conv)

'func' 参数是 CFuncObject,而 'swap' 和 'conv' 参数是那些需要 pre- 或 后处理,否则使用“无”。data 参数为 缓冲区对象的列表,Slice 参数给出数字 每个维度的迭代次数以及缓冲区偏移量和 每个数组和每个维度的步长。

我们预定义了几个 UFunc,供该方法使用: cast、swap、getobj 和 setobj。强制转换和交换功能可以 胁迫和字节交换,分别是 getobj 和 setobj 函数在数值数组和 Python 序列之间执行强制。__call__

针对核心提出了以下属性和方法 实现。

属性:

.name:                  e.g. "add", "subtract", etc.
.nargs:                 number of total arguments
.iargs:                 number of input arguments
.cfuncs (mapping):      the set C functions
.doc:                   documentation string

方法:

__init__():             initialization
__del__():              destruction
__repr__():             representation
__call__():             look-up and dispatch method
initrule():             initialize coercion rule
uninitrule():           uninitialize coercion rule
register():             register a CUFunc
unregister():           unregister a CUFunc

C-API:这仍然需要充实。

数组:

此类包含有关数组的信息,例如 shape、 类型、数据的字节序等。它的运算符 '+', '-', 等等,只需调用相应的 UFunc 函数,例如

def __add__(self, other):
    return ufunc.add(self, other)

提出了以下属性、方法和功能 核心实现。

属性:

.shape:                 shape of the array
.format:                type of the array
.real (only complex):   real part of a complex array
.imag (only complex):   imaginary part of a complex array

方法:

__init__():             initialization
__del__():              destruction
__repr_():              representation
__str__():              pretty representation
__cmp__():              rich comparison
__len__():
__getitem__():
__setitem__():
__getslice__():
__setslice__():
numeric methods:
copy():                 copy of array
aslist():               create list from array
asstring():             create string from array


功能:

fromlist():             create array from sequence
fromstring():           create array from string
array():                create array with shape and value
concat():               concatenate two arrays
resize():               resize array
  1. C-API:这仍然需要充实。

  2. 数组视图

    此类类似于 Array 类,只是 reshape 而扁平方法将引发异常,因为非连续 数组不能仅使用指针和 步长信息。

    C-API:这仍然需要充实。

  3. C-扩展模块:

    Numeric2 将有几个 C 扩展模块。

    1. _ufunc:

      此集合的主要模块是 _ufuncmodule.c。这 本模块的目的是做最低限度的工作, 即使用指定的 C 函数遍历数组。这 这些函数的接口与数字 1 相同,即

      int (*CFunc)(char *data, int *steps, int repeat, void *func);

      并且它们的功能应该是相同的,即它们 遍历最内层的维度。

      针对核心提出了以下属性和方法 实现。

      属性:

      方法:

      compute():

      C-API:这仍然需要充实。

    2. _int32、_real64等:

      每种阵列类型还将有 C 扩展模块, 例如 _int32module.c、_real64module.c 等。如前所述 以前,当这些模块由 UFunc 导入时 模块,它们将自动注册其功能和 胁迫规则。这些模块的新版本或改进版本可以 易于实现和使用,而不会影响其余部分 数字 2.

未解决的问题

  1. 切片语法是否默认为复制或查看行为?

    Python 的默认行为是返回子列表的副本 或元组(当使用切片语法时),而数字 1 返回 查看数组。为数字 1 所做的选择显然是 出于性能原因:开发人员希望避免 在每次分配和复制数据缓冲区的惩罚 数组操作,并觉得需要对数组进行深层拷贝 很少见。然而,一些人认为 Numeric 的切片符号 还应该具有与 Python 列表一致的复制行为。 在这种情况下,与复制行为相关的性能损失 可以通过实现写入时复制来最小化。该计划具有 两个数组共享一个数据缓冲区(如视图行为),直到 为任一数组分配新数据,此时 建立数据缓冲区。然后,视图行为将通过以下方式实现 一个 ArrayView 类,其行为类似于 Numeric 1 数组, 即 .shape 不能为非连续数组设置。使用 ArrayView 类还明确了数组的数据类型 包含。

  2. 项语法是否默认为复制或查看行为?

    项目语法也出现了类似的问题。例如,如果 和 ,则更改也会更改 ,因为 是 a 的第一行的引用或视图。 因此,如果 c 是一个二维数组,那么它似乎应该返回一个一维数组,该数组是视图,而不是副本 的,c 表示一致性。然而,可以认为只是一个 简写意味着复制行为假设 切片语法返回一个副本。 数字 2 的行为是否相同 方式作为列表并返回视图,或者它应该返回副本。a = [[0,1,2], [3,4,5]]b = a[0]b[0]a[0][0]a[0]c[i]c[i]c[i,:]

  3. 如何实现标量强制?

    Python 的数值类型比 Numeric 少,这可能会导致 胁迫问题。例如,在乘以 Python 标量时 类型 float 和 float 类型的数值数组,即 Numeric 数组 转换为 double,因为 Python float 类型实际上是 一个双倍。这通常不是所需的行为,因为 数值数组的大小将增加一倍,很可能是 烦人,特别是对于非常大的数组。我们更希望 数组类型胜过相同类型类的 Python 类型,即 整数、浮点数和复数。因此,在 Python 整数和 Int16(短)数组将返回 Int16 数组。而 Python 浮点数和 Int16 之间的操作 array 将返回一个 Float64(双精度)数组。之间的操作 两个数组使用正常的强制规则。

  4. 如何处理整数除法?

    在 Python 的未来版本中,整数除法的行为 会改变。操作数将转换为浮点数,因此 结果将是一个浮点数。如果我们实现建议的标量 数组优先于 Python 标量的强制规则, 然后将数组除以整数将返回一个整数数组 并且不会与 Python 的未来版本不一致 将返回 double 类型的数组。科学程序员是 熟悉整数和浮点数之间的区别 除法,那么数字 2 是否应该继续这种行为?

  5. 记录应该如何实施?

    有两种方法可以实现记录,具体取决于您的 观点。第一种是将两个除法阵列分成单独的数组 类取决于其类型的行为。例如 数值数组是一个类,字符串是第二个类,记录一个 第三,因为每个类的操作范围和类型 不同。因此,记录数组不是一种新类型,而是 更灵活的数组形式的机制。为了方便访问和 处理如此复杂的数据,该类由数值组成 在数据缓冲区中具有不同字节偏移量的数组。为 例如,可能有一个由 Int16 数组组成的表, Real32 值。两个数值数组,一个偏移量为 0 字节 一个 6 个字节的步幅被解释为 Int16,一个字节的步幅是 偏移量为 2 个字节,步幅为 6 个字节,解释为 Real32 将表示记录数组。两个数值数组 将引用相同的数据缓冲区,但具有不同的偏移量和 步幅属性和不同的数值类型。

    第二种方法是将记录视为多个数组之一 类型,尽管数组操作较少,而且可能不同 比数值数组。此方法将数组类型视为 是固定长度字符串的映射。映射可以是 简单,如整数和浮点数,或复杂,如 复数、字节字符串和 C 结构。记录 type 有效地将 struct 和 Numeric 模块合并到 多维结构数组。这种方法意味着某些 对数组接口的更改。例如,“typecode” 关键字参数可能应该更改为更多 描述性“format”关键字。

    1. 如何定义和实现记录语义?

      无论采用哪种实施方法进行记录,都 如何访问它们的语法和语义,以及 如果一个人希望获得 记录的子字段。在这种情况下,记录类型可以 本质上被认为是一个不齐质的列表,就像一个元组 由 struct 模块的 unpack 方法返回;和 1-D 记录数组可以解释为具有 第二个维度是字段列表的索引。 这种增强的数组语义使得访问一个数组 或更多的字段简单明了。它还 允许用户在自然的字段上执行数组操作 和直观的方式。如果我们假设记录是实现的 作为数组类型,则 last dimension 默认为 0 并且可以 因此,对于由简单类型组成的数组,可以忽略不计, 就像数字一样。

  6. 掩码数组是如何实现的?

    数值 1 中的掩码数组作为单独的数组实现 类。由于能够向数值 2 添加新的数组类型,它 可以实现数值 2 中的掩码数组 作为新的数组类型,而不是数组类。

  7. 如何处理数值错误(IEEE 浮点错误 特别)?

    提议者(保罗·巴雷特和特拉维斯)不清楚 Oliphant)处理错误的最佳或首选方法是什么。 由于大多数执行操作的 C 函数都进行了迭代 数组的最内层(最后)维度。这个维度 可能包含一千个或更多具有一个或多个错误的项目 不同类型的,例如除以零、下溢和 溢出。此外,跟踪这些错误可能会出现在 性能的代价。因此,我们建议几个 选项:

    1. 打印最严重错误的消息,将其留给 用户来查找错误。

    2. 打印一条包含发生的所有错误和数字的消息 的发生次数,让用户来定位错误。

    3. 打印发生的所有错误的消息以及 它们发生的地方。

    4. 或者使用混合方法,只打印最严重的 错误,但要跟踪错误的内容和位置 发生。这将允许用户找到错误 同时保持错误消息简短。

  8. 需要哪些功能来简化 FORTRAN 的集成 库和代码?

在这个阶段,考虑如何缓解 在 Numeric 2 中集成 FORTRAN 库和用户代码。

实施步骤

  1. 实现基本的 UFunc 功能

    1. Minimal Array 类:

      必要的类属性和方法,例如 .shape、.data、 .type 等。

    2. Minimal ArrayType 类:

      Int32、Real64、Complex64、Char、Object

    3. 最小 UFunc 类:

      UFunc 实例化、CFunction 注册、UFunc 调用 一维数组,包括用于对齐的规则, 字节交换和胁迫。

    4. 最小的 C 扩展模块:

      _UFunc,它在 C 中执行最内层的数组循环。

      此步骤实现需要执行的任何操作:'c = add(a, b)' 其中 a、b 和 c 是一维数组。它教我们如何添加 新的 UFuncs,以强制数组,传递必要的 信息到 C 迭代器方法并实际执行 计算。

  2. 继续增强 UFunc 迭代器和 Array 类

    1. 实现 Array 类的一些访问方法: print、repr、getitem、setitem 等。

    2. 实现多维数组

    3. 使用 UFuncs 实现一些基本的 Array 方法: +、-、*、/等

    4. 使 UFuncs 能够使用 Python 序列。

  3. 完成标准的 UFunc 和 Array 类行为

    1. 实现 getslice 和 setslice 行为

    2. 处理阵列广播规则

    3. 实现记录类型

  4. 添加其他功能

    1. 添加更多 UFuncs

    2. 实现缓冲区或 mmap 访问

不兼容

以下是行为不兼容的列表 数字 1 和数字 2。

  1. 标量强制规则

    数值 1 具有一组用于数组和 Python 的强制规则 数值类型。这可能会导致意外和烦人的问题 在数组表达式的计算过程中。数字 2 打算 通过制定两套强制规则来克服这些问题: 一个用于数组和 Python 数值类型,另一个仅用于 阵 列。

  2. 无 savespace 属性

    数值 1 中的 savespace 属性使用以下数组创建数组 属性集优先于未设置属性集的属性集。 数字 2 不会有这样的属性,因此是正常的 阵列强制规则将生效。

  3. 切片语法返回副本

    数值 1 中的切片语法返回原始视图 数组。数字 2 的切片行为将是副本。你 应使用 ArrayView 类将视图获取到数组中。

  4. 布尔比较返回布尔数组

    数值 1 中数组之间的比较得到一个布尔值 标量,因为 Python 中的当前限制。的出现 Python 2.1 中的丰富比较将允许布尔数组 被退回。

  5. 不推荐使用类型字符

    数值 2 将具有一个由 Type 实例组成的 ArrayType 类, 例如,有符号整数的 Int8、Int16、Int32 和 Int。这 数字 1 中的类型代码方案将可用于向后 兼容性,但将被弃用。

附录

  1. 隐式子数组迭代

    计算机动画由许多二维图像或 相同形状的框架。通过将这些图像堆叠成一个 内存块,则创建一个 3-D 数组。然而,操作是 执行的不是针对整个 3-D 阵列,而是在现场执行 的二维子阵列。在大多数数组语言中,每个帧都必须是 提取、操作,然后重新插入到输出数组中 使用 for 类循环。J 语言允许程序员 通过为帧设置等级来隐式执行此类操作 和数组。默认情况下,这些排名在 创建数组。这是数字 1 的意图 开发人员来实现此功能,因为它基于 语言 J.数字 1 代码具有所需的变量 实现此行为,但从未实现。我们打算 在数值 2 中实现隐式子数组迭代,如果 数值 1 中的数组广播规则不完全支持 此行为。


The End 微信扫一扫

文章声明:以上内容(如有图片或视频在内)除非注明,否则均为腾龙猫勺儿原创文章,转载或复制请以超链接形式并注明出处。

本文作者:猫勺本文链接:https://www.jo6.cn/post/93.html

上一篇 下一篇

相关阅读

发表评论

访客 访客
快捷回复: 表情:
评论列表 (暂无评论,84人围观)

还没有评论,来说两句吧...

取消
微信二维码
微信二维码
支付宝二维码