原文:
numpy.org/doc/
数组迭代器 API
原文:
numpy.org/doc/1.26/reference/c-api/iterator.html
在 1.6 版中新增。
数组迭代器
数组迭代器封装了通用函数中的许多关键功能,允许用户代码支持输出参数、保留内存布局和使用错误对齐或类型的数据缓冲,而无需进行困难的编码。
本页记录了迭代器的 API。迭代器命名为NpyIter,函数命名为NpyIter_*。
有一个数组迭代入门指南,对于使用这个 C API 的人可能会有所帮助。在许多情况下,通过在 Python 中创建迭代器来测试想法是一个好主意,然后再编写 C 迭代代码。
迭代示例
熟悉迭代器的最佳方法是查看其在 NumPy 代码库中的使用情况。例如,这里是稍微改进的PyArray_CountNonzero代码的版本,它计算数组中非零元素的数量。
npy_intp PyArray_CountNonzero(PyArrayObject* self)
{
/* Nonzero boolean function */
PyArray_NonzeroFunc* nonzero = PyArray_DESCR(self)->f->nonzero;
NpyIter* iter;
NpyIter_IterNextFunc *iternext;
char** dataptr;
npy_intp nonzero_count;
npy_intp* strideptr,* innersizeptr;
/* Handle zero-sized arrays specially */
if (PyArray_SIZE(self) == 0) {
return 0;
}
/*
* Create and use an iterator to count the nonzeros.
* flag NPY_ITER_READONLY
* - The array is never written to.
* flag NPY_ITER_EXTERNAL_LOOP
* - Inner loop is done outside the iterator for efficiency.
* flag NPY_ITER_NPY_ITER_REFS_OK
* - Reference types are acceptable.
* order NPY_KEEPORDER
* - Visit elements in memory order, regardless of strides.
* This is good for performance when the specific order
* elements are visited is unimportant.
* casting NPY_NO_CASTING
* - No casting is required for this operation.
*/
iter = NpyIter_New(self, NPY_ITER_READONLY|
NPY_ITER_EXTERNAL_LOOP|
NPY_ITER_REFS_OK,
NPY_KEEPORDER, NPY_NO_CASTING,
NULL);
if (iter == NULL) {
return -1;
}
/*
* The iternext function gets stored in a local variable
* so it can be called repeatedly in an efficient manner.
*/
iternext = NpyIter_GetIterNext(iter, NULL);
if (iternext == NULL) {
NpyIter_Deallocate(iter);
return -1;
}
/* The location of the data pointer which the iterator may update */
dataptr = NpyIter_GetDataPtrArray(iter);
/* The location of the stride which the iterator may update */
strideptr = NpyIter_GetInnerStrideArray(iter);
/* The location of the inner loop size which the iterator may update */
innersizeptr = NpyIter_GetInnerLoopSizePtr(iter);
nonzero_count = 0;
do {
/* Get the inner loop data/stride/count values */
char* data = *dataptr;
npy_intp stride = *strideptr;
npy_intp count = *innersizeptr;
/* This is a typical inner loop for NPY_ITER_EXTERNAL_LOOP */
while (count--) {
if (nonzero(data, self)) {
nonzero_count;
}
data = stride;
}
/* Increment the iterator to the next inner loop */
} while(iternext(iter));
NpyIter_Deallocate(iter);
return nonzero_count;
} 多重迭代示例
这里是使用迭代器的复制函数。order参数用于控制分配结果的内存布局,通常希望使用NPY_KEEPORDER。
PyObject *CopyArray(PyObject *arr, NPY_ORDER order)
{
NpyIter *iter;
NpyIter_IterNextFunc *iternext;
PyObject *op[2], *ret;
npy_uint32 flags;
npy_uint32 op_flags[2];
npy_intp itemsize, *innersizeptr, innerstride;
char **dataptrarray;
/*
* No inner iteration - inner loop is handled by CopyArray code
*/
flags = NPY_ITER_EXTERNAL_LOOP;
/*
* Tell the constructor to automatically allocate the output.
* The data type of the output will match that of the input.
*/
op[0] = arr;
op[1] = NULL;
op_flags[0] = NPY_ITER_READONLY;
op_flags[1] = NPY_ITER_WRITEONLY | NPY_ITER_ALLOCATE;
/* Construct the iterator */
iter = NpyIter_MultiNew(2, op, flags, order, NPY_NO_CASTING,
op_flags, NULL);
if (iter == NULL) {
return NULL;
}
/*
* Make a copy of the iternext function pointer and
* a few other variables the inner loop needs.
*/
iternext = NpyIter_GetIterNext(iter, NULL);
innerstride = NpyIter_GetInnerStrideArray(iter)[0];
itemsize = NpyIter_GetDescrArray(iter)[0]->elsize;
/*
* The inner loop size and data pointers may change during the
* loop, so just cache the addresses.
*/
innersizeptr = NpyIter_GetInnerLoopSizePtr(iter);
dataptrarray = NpyIter_GetDataPtrArray(iter);
/*
* Note that because the iterator allocated the output,
* it matches the iteration order and is packed tightly,
* so we don't need to check it like the input.
*/
if (innerstride == itemsize) {
do {
memcpy(dataptrarray[1], dataptrarray[0],
itemsize * (*innersizeptr));
} while (iternext(iter));
} else {
/* For efficiency, should specialize this based on item size... */
npy_intp i;
do {
npy_intp size = *innersizeptr;
char *src = dataptrarray[0], *dst = dataptrarray[1];
for(i = 0; i < size; i , src = innerstride, dst = itemsize) {
memcpy(dst, src, itemsize);
}
} while (iternext(iter));
}
/* Get the result from the iterator object array */
ret = NpyIter_GetOperandArray(iter)[1];
Py_INCREF(ret);
if (NpyIter_Deallocate(iter) != NPY_SUCCEED) {
Py_DECREF(ret);
return NULL;
}
return ret;
} 多索引追踪示例
此例显示了如何使用NPY_ITER_MULTI_INDEX标志。为简单起见,我们假设参数是一个二维数组。
int PrintMultiIndex(PyArrayObject *arr) {
NpyIter *iter;
NpyIter_IterNextFunc *iternext;
npy_intp multi_index[2];
iter = NpyIter_New(
arr, NPY_ITER_READONLY | NPY_ITER_MULTI_INDEX | NPY_ITER_REFS_OK,
NPY_KEEPORDER, NPY_NO_CASTING, NULL);
if (iter == NULL) {
return -1;
}
if (NpyIter_GetNDim(iter) != 2) {
NpyIter_Deallocate(iter);
PyErr_SetString(PyExc_ValueError, "Array must be 2-D");
return -1;
}
if (NpyIter_GetIterSize(iter) != 0) {
iternext = NpyIter_GetIterNext(iter, NULL);
if (iternext == NULL) {
NpyIter_Deallocate(iter);
return -1;
}
NpyIter_GetMultiIndexFunc *get_multi_index =
NpyIter_GetGetMultiIndex(iter, NULL);
if (get_multi_index == NULL) {
NpyIter_Deallocate(iter);
return -1;
}
do {
get_multi_index(iter, multi_index);
printf("multi_index is [%" NPY_INTP_FMT ", %" NPY_INTP_FMT "]n",
multi_index[0], multi_index[1]);
} while (iternext(iter));
}
if (!NpyIter_Deallocate(iter)) {
return -1;
}
return 0;
} 当使用一个 2x3 数组调用上述示例时,将打印:
代码语言:javascript复制multi_index is [0, 0]
multi_index is [0, 1]
multi_index is [0, 2]
multi_index is [1, 0]
multi_index is [1, 1]
multi_index is [1, 2] 迭代器数据类型
迭代器布局是一个内部细节,用户代码只看到一个不完整的结构。
代码语言:javascript复制type NpyIter这是一个迭代器的不透明指针类型。通过迭代器 API 才能访问其内容。
代码语言:javascript复制type NpyIter_Type这种类型向 Python 暴露了迭代器。目前,还没有暴露任何 API 来访问由 Python 创建的迭代器的值。如果在 Python 中创建了迭代器,必须在 Python 中使用,反之亦然。这样的 API 可能会在未来版本中创建。
代码语言:javascript复制type NpyIter_IterNextFunc这是用于迭代循环的函数指针,由NpyIter_GetIterNext返回。
type NpyIter_GetMultiIndexFunc这是用于获取当前迭代器多索引的函数指针,由NpyIter_GetGetMultiIndex返回。
构造和销毁
代码语言:javascript复制*NpyIter_New( *op, flags, order, casting, *dtype)为给定的 numpy 数组对象op创建一个迭代器。
在flags中可能传递的标志是NpyIter_MultiNew中记录的全局和每个操作数标志的任意组合,除了NPY_ITER_ALLOCATE。
可以将任何NPY_ORDER枚举值传递给order。为了进行高效的迭代,NPY_KEEPORDER是最佳选项,其他顺序则需要特定的迭代模式。
可以将任何NPY_CASTING枚举值传递给casting。这些值包括NPY_NO_CASTING、NPY_EQUIV_CASTING、NPY_SAFE_CASTING、NPY_SAME_KIND_CASTING和NPY_UNSAFE_CASTING。为了允许发生转换,还必须启用复制或缓冲。
如果dtype不是NULL,则它需要那种数据类型。如果允许复制,则在数据可以转换时会进行临时复制。如果启用了NPY_ITER_UPDATEIFCOPY,则在迭代器销毁时还会使用另一种转换方式将数据复制回去。
如果发生错误,则返回 NULL,否则返回分配的迭代器。
要创建类似于旧迭代器的迭代器,可以使用以下方法。
代码语言:javascript复制iter = NpyIter_New(op, NPY_ITER_READWRITE,
NPY_CORDER, NPY_NO_CASTING, NULL); 如果要使用对齐的double代码编辑数组,但顺序无关���要,则应使用此选项。
dtype = PyArray_DescrFromType(NPY_DOUBLE);
iter = NpyIter_New(op, NPY_ITER_READWRITE|
NPY_ITER_BUFFERED|
NPY_ITER_NBO|
NPY_ITER_ALIGNED,
NPY_KEEPORDER,
NPY_SAME_KIND_CASTING,
dtype);
Py_DECREF(dtype); 代码语言:javascript复制*NpyIter_MultiNew( nop, **op, flags, order, casting, *op_flags, **op_dtypes)创建用于对提供给op的nop数组对象进行广播的迭代器,使用常规的 NumPy 广播规则。
可以将任何NPY_ORDER枚举值传递给order。为了进行高效的迭代,NPY_KEEPORDER是最佳选项,其他顺序则需要特定的迭代模式。在使用NPY_KEEPORDER时,如果你还希望确保迭代沿轴不发生反转,则应该传递标志NPY_ITER_DONT_NEGATE_STRIDES。
可以将任何NPY_CASTING枚举值传递给casting。这些值包括NPY_NO_CASTING、NPY_EQUIV_CASTING、NPY_SAFE_CASTING、NPY_SAME_KIND_CASTING和NPY_UNSAFE_CASTING。为了允许发生转换,还必须启用复制或缓冲。
如果op_dtypes不是NULL,则它为op[i]指定一个数据类型或NULL。
如果发生错误,则返回 NULL,否则返回分配的迭代器。
可以传递给 flags 的标志,应用于整个迭代器,包括:
NPY_ITER_C_INDEX使迭代器跟踪与 C 顺序相匹配的展平索引。此选项不能与 NPY_ITER_F_INDEX 一起使用。
NPY_ITER_F_INDEX使迭代器跟踪与 Fortran 顺序相匹配的展平索引。此选项不能与 NPY_ITER_C_INDEX 一起使用。
NPY_ITER_MULTI_INDEX使迭代器跟踪多索引。这会防止迭代器将轴合并为生成更大的内部循环。如果循环也没有缓冲区,并且没有跟踪索引(NpyIter_RemoveAxis 可以被调用),则迭代器大小可以为 -1,表示迭代器过大。这可能是由于复杂的广播而发生,并且将在设置迭代器范围、移除多索引或获取下一个函数时创建错误。但是,如果移除轴后大小足够小,则仍然可以再次移除轴并正常使用迭代器。
NPY_ITER_EXTERNAL_LOOP使迭代器跳过最内层循环的迭代,需要迭代器的使用者处理它。
此标志与 NPY_ITER_C_INDEX、NPY_ITER_F_INDEX 和 NPY_ITER_MULTI_INDEX 不兼容。
NPY_ITER_DONT_NEGATE_STRIDES仅当为 order 参数指定 NPY_KEEPORDER 时,此选项才会影响迭代器。默认情况下,使用 NPY_KEEPORDER 时,迭代器会颠倒具有负步长的轴,以便以正向方向遍历内存。这会禁用此步骤。如果要使用轴的底层内存顺序但不想颠倒轴,则使用此标志。例如,numpy.ravel(a, order='K') 的行为就是这样的。
NPY_ITER_COMMON_DTYPE使迭代器将所有操作数转换为基于 ufunc 类型提升规则计算的公共数据类型。必须启用复制或缓冲区。
如果已经预先知道公共数据类型,请不要使用此标志。而是为所有操作数设置请求的 dtype。
代码语言:javascript复制NPY_ITER_REFS_OK表示可能接受并在迭代器中使用具有引用类型(对象数组或包含对象类型的结构化数组)的数组。如果启用了此标志,则调用者必须确保检查是否 NpyIter_IterationNeedsAPI(iter) 为 true,在这种情况下,可能在迭代期间不释放 GIL。
代码语言:javascript复制NPY_ITER_ZEROSIZE_OK表示应允许大小为零的数组。由于典型的迭代循环不会自然地处理大小为零的数组,因此在进入迭代循环之前,必须检查 IterSize 是否大于零。当前仅检查操作数,而不是强制形状。
代码语言:javascript复制NPY_ITER_REDUCE_OK允许带有零步长和大小大于一的维度的可写操作数。请注意,此类操作数必须是可读/可写的。
启用缓冲处理时,这还切换到一种特殊的缓冲模式,根据需要减少循环长度,以避免对正在减少的值造成干扰。
请注意,如果要对自动分配的输出进行减少运算,必须使用NpyIter_GetOperandArray获取其引用,然后在执行迭代循环之前将每个值设置为减少单位。在进行缓冲减少运算时,这意味着你还必须指定标志NPY_ITER_DELAY_BUFALLOC,然后在初始化分配的操作数以准备缓冲区后重置迭代器。
NPY_ITER_RANGED启用对完整iterindex范围0, NpyIter_IterSize(iter))的子范围进行迭代的支持。使用函数[NpyIter_ResetToIterIndexRange来指定迭代范围。
只有在启用NPY_ITER_BUFFERED时,此标志才能与NPY_ITER_EXTERNAL_LOOP一起使用。这是因为没有缓冲处理时,内部循环总是最内部迭代维度的大小,并且允许它被切割需要特殊处理,实际上更像是缓冲版本。
NPY_ITER_BUFFERED使迭代器存储缓冲数据,并使用缓冲来满足数据类型、对齐和字节顺序要求。要对操作数进行缓冲处理,不要指定NPY_ITER_COPY或NPY_ITER_UPDATEIFCOPY标志,因为它们会覆盖缓冲处理。缓冲对于使用迭代器的 Python 代码特别有用,允许一次处理更大块的数据,以分摊 Python 解释器的开销。
如果与NPY_ITER_EXTERNAL_LOOP一起使用,调用者的内部循环可能会获得比没有缓冲更大的块,这是因为步幅的布局方式的原因。
请注意,如果一个操作数被赋予标志NPY_ITER_COPY或NPY_ITER_UPDATEIFCOPY,会首选进行复制而不是进行缓冲处理。当数组进行广播时,元素需要被复制以获得常量步幅,仍然会进行缓冲处理。
在正常的缓冲处理中,每个内部循环的大小等于缓冲区的大小,或者如果指定了NPY_ITER_GROWINNER,可能更大。如果启用了NPY_ITER_REDUCE_OK并且发生了减少,那么内部循环可能会因减少的结构而变小。
NPY_ITER_GROWINNER当启用缓冲时,这允许内部循环的大小在不需要缓冲时增长。如果你正在直接通过所有数据,而不是任何具有小缓存友好的临时值数组的内部循环,则最好使用该选项。
代码语言:javascript复制NPY_ITER_DELAY_BUFALLOC当启用缓冲时,这延迟缓冲区的分配,直到调用NpyIter_Reset或另一个重置函数。该标志存在是为了在多线程迭代时避免多次复制缓冲区数据的浪费。
另一个使用该标志的方法是设置缩减操作。创建迭代器后,通过迭代器自动分配缩减输出(确保使用 READWRITE 访问),其值可以初始化为缩减单元。使用NpyIter_GetOperandArray获取该对象。然后调用NpyIter_Reset来分配并填充缓冲区的初始值。
NPY_ITER_COPY_IF_OVERLAP如果任何写操作数与任何读操作数存在重叠,通过制作临时副本(必要时启用 WRITEIFCOPY 以避免重叠),消除所有重叠。如果有一个内存地址包含两个数组的共同数据,则一对操作数具有重叠。
由于精确的重叠检测在维度数量上具有指数级的运行时间,因此决策是基于启发式方法的,该方法具有假阳性(在不寻常的情况下产生不必要的副本),但没有假阴性。
如果存在任何读/写重叠,此标志可确保操作的结果与所有操作数进行复制时的结果相同。在需要进行复制的情况下,如果没有此标志,计算结果可能是不确定的!
可以传递到op_flags[i]的标志,其中0 <= i < nop:
NPY_ITER_READWRITE代码语言:javascript复制NPY_ITER_READONLY代码语言:javascript复制NPY_ITER_WRITEONLY指示迭代器的使用者将如何对op[i]进行读取或写入。对于每个操作数,必须指定这些标志中的一个。对于用户提供的操作数,使用NPY_ITER_READWRITE或NPY_ITER_WRITEONLY可能会触发WRITEBACKIFCOPY语义。在调用NpyIter_Deallocate时,数据将被写回原始数组。
NPY_ITER_COPY如果op[i]的复制不符合构造函数的标志和参数指定的数据类型或对齐要求,则允许进行复制。
NPY_ITER_UPDATEIFCOPY触发NPY_ITER_COPY,当数组操作数被标记为可写并进行复制时,在调用NpyIter_Deallocate时,会导致在副本中的数据被再次复制回op[i]。
如果操作数被标记为仅写,并且需要复制,则将创建一个未初始化的临时数组,然后在调用NpyIter_Deallocate时复制回op[i],而不是执行不必要的复制操作。
NPY_ITER_NBO代码语言:javascript复制NPY_ITER_ALIGNED代码语言:javascript复制NPY_ITER_CONTIG导致迭代器为op[i]提供原生字节顺序的数据,根据 dtype 要求对齐,连续,或任何组合。
默认情况下,迭代器生成指向提供的数组的指针,这些指针可以对齐或不对齐,并且具有任何字节顺序。如果未启用复制或缓冲,并且操作数数据不满足约束条件,则会引发错误。
连续约束仅适用于内部循环,连续的内部循环可以具有任意的指针更改。
如果请求的数据类型是非本机字节顺序,则 NBO 标志将其覆盖,并且请求的数据类型将转换为本机字节顺序。
代码语言:javascript复制NPY_ITER_ALLOCATE这是用于输出数组的,并且需要设置标志NPY_ITER_WRITEONLY或NPY_ITER_READWRITE。如果 op[i] 为 NULL,则创建一个具有最终广播维度和与迭代器的迭代顺序匹配的布局的新数组。
当 op[i] 为 NULL 时,请求的数据类型 op_dtypes[i] 也可以为 NULL,此时它将自动从标记为可读的数组的数据类型中生成。生成数据类型的规则与 UFuncs 相同。特别注意的是选择的数据类型中的字节顺序处理。如果只有一个输入,则使用输入的数据类型。否则,如果将多个输入数据类型组合在一起,则输出将为本机字节顺序。
带有此标志分配后,调用者可以通过调用NpyIter_GetOperandArray来检索新的数组,并获取返回的 C 数组中的第 i 个对象。调用者必须调用 Py_INCREF 来声明对数组的引用。
NPY_ITER_NO_SUBTYPE用于NPY_ITER_ALLOCATE时,此标志禁用为输出分配数组子类型,强制其成为直接的 ndarray。
TODO: 或许引入一个函数 NpyIter_GetWrappedOutput 并删除此标志会更好?
NPY_ITER_NO_BROADCAST确保输入或输出与迭代维度完全匹配。
代码语言:javascript复制NPY_ITER_ARRAYMASK1.7 版本中的新功能。
表示这个操作数是在写入操作数时要使用的掩码。当应用了NPY_ITER_WRITEMASKED标志时,只能有一个操作数应用了NPY_ITER_ARRAYMASK标志。
具有此标志的操作数的数据类型应为NPY_BOOL、NPY_MASK或所有字段都是有效掩码数据类型的结构 dtype。在后一种情况下,它必须与 WRITEMASKED 的结构操作数匹配,因为它在指定该数组的每个字段的掩码。
此标志仅影响从缓冲区写回数组。这意味着如果操作数还是NPY_ITER_READWRITE或NPY_ITER_WRITEONLY,执行迭代的代码可以写入此操作数以控制哪些元素将不被修改,哪些元素将被修改。当掩码应为输入掩码的组合时,这很有用。
NPY_ITER_WRITEMASKED从版本 1.7 开始新增。
此数组是所有writemasked 操作数的掩码。代码使用writemasked标志,指示只有选择的 ARRAYMASK 操作数为 True 的元素才会被写入。一般情况下,迭代器不会强制执行此操作,代码执行迭代时应遵循该约定。
当使用writemasked标志并且此操作数已缓冲时,这会改变从缓冲区复制数据到数组的方式。将使用掩码复制例程,该例程仅复制在数组掩码的相应元素中writemasked返回 true 的元素。
NPY_ITER_OVERLAP_ASSUME_ELEMENTWISE在内存重叠检查中,假设启用了NPY_ITER_OVERLAP_ASSUME_ELEMENTWISE的操作数仅按照迭代器顺序访问。
这使得迭代器能够推断数据依赖关系,可能避免不必要的复制。
此标志仅在迭代器上启用了NPY_ITER_COPY_IF_OVERLAP时才有效。
*NpyIter_AdvancedNew( nop, **op, flags, order, casting, *op_flags, **op_dtypes, int oa_ndim, int **op_axes, const *itershape, buffersize)使用几个高级选项扩展了NpyIter_MultiNew,提供了对广播和缓冲的更多控制。
如果将-1/NULL 值传递给oa_ndim、op_axes、itershape和buffersize,则等效于NpyIter_MultiNew。
当参数oa_ndim不为零或-1 时,指定将使用自定义广播进行迭代的维数的数量。如果提供了它,op_axes必须提供,并且itershape也可以提供。op_axes参数让您可以详细控制操作数组的轴如何匹配在一起并进行迭代。在op_axes中,您必须提供一个指向大小为oa_ndim的数组的指针数组,其类型为npy_intp。如果op_axes中的条目为 NULL,则将应用正常的广播规则。在op_axes[j][i]中存储的是op[j]的一个有效轴,或者是-1,表示newaxis。在每个op_axes[j]数组内,轴不得重复。以下示例是如何将正常的广播应用于三维数组、二维数组、一维数组和标量的。
注意:在 NumPy 1.8 之前,oa_ndim == 0用于表示op_axes和itershape未使用。这已被弃用,应替换为-1。可以通过使用NpyIter_MultiNew来获得更好的向后兼容性。
int oa_ndim = 3; /* # iteration axes */
int op0_axes[] = {0, 1, 2}; /* 3-D operand */
int op1_axes[] = {-1, 0, 1}; /* 2-D operand */
int op2_axes[] = {-1, -1, 0}; /* 1-D operand */
int op3_axes[] = {-1, -1, -1} /* 0-D (scalar) operand */
int* op_axes[] = {op0_axes, op1_axes, op2_axes, op3_axes}; itershape 参数允许您强制迭代器具有特定的迭代形状。它是长度为 oa_ndim 的数组。当条目为负时,其值来自运算数。此参数允许自动分配输出获得额外的维度,这些维度与任何输入的维度不匹配。
如果 buffersize 是零,则使用默认的缓冲区大小,否则指定要使用多大的缓冲区。建议使用 4096 或 8192 等的 2 的幂大小的缓冲区。
如果出现错误,返回 NULL,否则返回分配的迭代器。
代码语言:javascript复制*NpyIter_Copy( *iter)复制给定迭代器。此函数主要用于启用数据的多线程迭代。
TODO:将此移到有关多线程迭代的部分。
推荐的多线程迭代方法是首先使用标志 NPY_ITER_EXTERNAL_LOOP、NPY_ITER_RANGED、NPY_ITER_BUFFERED、NPY_ITER_DELAY_BUFALLOC 和可能的 NPY_ITER_GROWINNER 创建一个迭代器。为每个线程创建此迭代器的副本(第一个迭代器除外)。然后,将迭代索引范围 0, NpyIter_GetIterSize(iter)) 分成任务进行处理,例如使用 TBB 的 parallel_for 循环。当线程获得任务并执行时,它将使用自己的迭代器副本,通过调用 [NpyIter_ResetToIterIndexRange 并对整个范围进行迭代。
在多线程代码中使用迭代器或在不持有 Python GIL 的代码中使用时,必须注意只调用在该上下文中安全的函数。不能在没有 Python GIL 的情况下安全调用 NpyIter_Copy,因为它会增加 Python 引用。通过将 errmsg 参数传递为非 NULL,可以安全调用 Reset* 和一些其他函数,这样函数将通过它传回错误,而不是设置 Python 异常。
每个副本都必须调用 NpyIter_Deallocate。
int NpyIter_RemoveAxis( *iter, int axis)从迭代中移除一个轴。这要求在迭代器创建时设置了 NPY_ITER_MULTI_INDEX,并且如果启用了缓冲或正在跟踪索引,则不起作用。此函数还将迭代器重置到初始状态。
这对于设置累加循环非常有用。迭代器可以首先使用包括累加轴在内的所有维度创建,以便输出正确创建。然后,累加轴可以被移除,并且计算以嵌套的方式进行。
警告:此函数可能会改变迭代器的内部内存布局。必须重新获取迭代器的任何缓存函数或指针!迭代范围也将被重置。
返回 NPY_SUCCEED 或 NPY_FAIL。
int NpyIter_RemoveMultiIndex( *iter)如果迭代器正在跟踪多索引,则取消对它们的支持,并进行进一步的迭代器优化,如果不需要多索引。此函数也会将迭代器重置回初始状态。
警告:此函数可能会改变迭代器的内部内存布局。必须重新获取迭代器的任何缓存函数或指针!
调用此函数后,NpyIter_HasMultiIndex(iter) 将返回 false。
返回 NPY_SUCCEED 或 NPY_FAIL。
int NpyIter_EnableExternalLoop( *iter)如果调用了 NpyIter_RemoveMultiIndex ,可能需要启用标志 NPY_ITER_EXTERNAL_LOOP 。此标志与 NPY_ITER_MULTI_INDEX 不允许同时存在,因此提供了这个函数在调用 NpyIter_RemoveMultiIndex 后启用该功能。此函数也会将迭代器重置回初始状态。
警告:此函数更改了迭代器的内部逻辑。必须重新获取迭代器的任何缓存函数或指针!
返回 NPY_SUCCEED 或 NPY_FAIL。
int NpyIter_Deallocate( *iter)释放迭代器对象并解决任何需要的写回。
返回 NPY_SUCCEED 或 NPY_FAIL。
int NpyIter_Reset( *iter, char **errmsg)将迭代器重置为初始状态,在迭代范围的起始位置。
返回 NPY_SUCCEED 或 NPY_FAIL。如果 errmsg 非空,则在返回NPY_FAIL时不会设置 Python 异常。相反,*errmsg 被设置为错误消息。当 errmsg 非空时,可以安全地调用该函数而不持有 Python GIL。
int NpyIter_ResetToIterIndexRange( *iter, istart, iend, char **errmsg)重置迭代器并将其限制为iterindex范围istart, iend)。参见 [NpyIter_Copy 了解如何在多线程迭代中使用此功能。这要求在迭代器构造函数中传递了标志 NPY_ITER_RANGED。
如果想同时重置iterindex范围和基本指针,可以按照以下方式操作以避免额外的缓冲区复制(在复制此代码时,请确保添加返回代码错误检查)。
/* Set to a trivial empty range */
NpyIter_ResetToIterIndexRange(iter, 0, 0);
/* Set the base pointers */
NpyIter_ResetBasePointers(iter, baseptrs);
/* Set to the desired range */
NpyIter_ResetToIterIndexRange(iter, istart, iend); 返回 NPY_SUCCEED 或 NPY_FAIL。如果 errmsg 非空,则在返回NPY_FAIL时不会设置 Python 异常。相反,*errmsg 被设置为错误消息。当 errmsg 非空时,可以安全地调用该函数而不持有 Python GIL。
int NpyIter_ResetBasePointers( *iter, char **baseptrs, char **errmsg)将迭代器重置回初始状态,但使用baseptrs中的值作为数据而不是从正在迭代的数组的指针中获取。此函数预期与op_axes参数一起由具有两个或多个迭代器的嵌套迭代代码一起使用。
返回NPY_SUCCEED或NPY_FAIL。如果errmsg非空,则在返回NPY_FAIL时不设置 Python 异常。相反,errmsg将设置为错误消息。当errmsg非空时,可以安全地调用该函数而不持有 Python GIL。
待办事项:将以下内容移动到关于嵌套迭代的特殊部分。
创建嵌套迭代器需要一些小心。所有迭代器操作数必须完全匹配,否则调用NpyIter_ResetBasePointers将无效。这意味着不能随意使用自动复制和输出分配。仍然可以通过启用所有转换参数创建迭代器之一,然后使用NpyIter_GetOperandArray函数获取分配的操作数,并将它们传递给其他迭代器的构造函数来使用迭代器的自动数据转换和类型转换功能。
警告:在为嵌套迭代创建迭代器时,代码不能在不同的迭代器中重复使用维度。如果这样做,嵌套迭代将在迭代过程中产生越界指针。
警告:在为嵌套迭代创建迭代器时,只能将缓冲应用于最内部的迭代器。如果使用缓冲迭代器作为baseptrs的源,则它将指向一个小缓冲区而不是数组,内部迭代将无效。
使用嵌套迭代的模式如下。
代码语言:javascript复制NpyIter *iter1, *iter1;
NpyIter_IterNextFunc *iternext1, *iternext2;
char **dataptrs1;
/*
* With the exact same operands, no copies allowed, and
* no axis in op_axes used both in iter1 and iter2.
* Buffering may be enabled for iter2, but not for iter1.
*/
iter1 = ...; iter2 = ...;
iternext1 = NpyIter_GetIterNext(iter1);
iternext2 = NpyIter_GetIterNext(iter2);
dataptrs1 = NpyIter_GetDataPtrArray(iter1);
do {
NpyIter_ResetBasePointers(iter2, dataptrs1);
do {
/* Use the iter2 values */
} while (iternext2(iter2));
} while (iternext1(iter1)); 代码语言:javascript复制int NpyIter_GotoMultiIndex( *iter, const *multi_index)调整迭代器以指向由multi_index指向的ndim索引。如果没有跟踪多索引、索引越界或禁用内部循环迭代,则返回错误。
返回NPY_SUCCEED或NPY_FAIL。
int NpyIter_GotoIndex( *iter, index)调整迭代器以指向指定的index。如果迭代器是用标志NPY_ITER_C_INDEX构造的,则index是 C 顺序索引;如果迭代器是用标志NPY_ITER_F_INDEX构造的,则index是 Fortran 顺序索引。如果没有正在跟踪索引、索引越界或禁用内部循环迭代,则返回错误。
返回NPY_SUCCEED或NPY_FAIL。
NpyIter_GetIterSize( *iter)返回正在迭代的元素数量。这是形状中所有维度的乘积。当跟踪多索引(并且可能调用NpyIter_RemoveAxis)时,大小可能为-1,表示迭代器太大。这样的迭代器是无效的,但在调用NpyIter_RemoveAxis后可能变为有效。不需要检查这种情况。
NpyIter_GetIterIndex( *iter)获取迭代器的iterindex,这是与迭代器的迭代顺序匹配的索引。
void NpyIter_GetIterIndexRange( *iter, *istart, *iend)获取正在进行迭代的iterindex子范围。如果未指定NPY_ITER_RANGED,则始终返回范围0, NpyIter_IterSize(iter))。
int NpyIter_GotoIterIndex( *iter, iterindex)调整迭代器以指向指定的iterindex。如果iterindex越界,启用了缓冲区或禁用内部循环迭代,则返回错误。
返回NPY_SUCCEED或NPY_FAIL。
NpyIter_HasDelayedBufAlloc( *iter)如果向迭代器构造器传递了标志[NPY_ITER_DELAY_BUFALLOC,并且尚未调用 Reset 函数之一,返回 1,否则返回 0。
NpyIter_HasExternalLoop( *iter)如果调用者需要处理最内层的 1 维循环,则返回 1,如果迭代器处理所有循环,则返回 0。这由构造函数标志NPY_ITER_EXTERNAL_LOOP或NpyIter_EnableExternalLoop控制。
NpyIter_HasMultiIndex( *iter)如果迭代器是用NPY_ITER_MULTI_INDEX标志创建的,则返回 1,否则返回 0。
NpyIter_HasIndex( *iter)如果迭代器是用NPY_ITER_C_INDEX或NPY_ITER_F_INDEX标志创建的,则返回 1,否则返回 0。
NpyIter_RequiresBuffering( *iter)如果迭代器需要缓冲,则返回 1,这发生在操作数需要转换或对齐,因此无法直接使用。
代码语言:javascript复制NpyIter_IsBuffered( *iter)如果迭代器是用NPY_ITER_BUFFERED标志创建的,则返回 1,否则返回 0。
NpyIter_IsGrowInner( *iter)如果迭代器是用NPY_ITER_GROWINNER标志创建的,则返回 1,否则返回 0。
NpyIter_GetBufferSize( *iter)如果迭代器被缓冲,则返回正在使用的缓冲区的大小,否则返回 0。
代码语言:javascript复制int NpyIter_GetNDim( *iter)返回正在进行迭代的维度数。如果在迭代器构造函数中未请求多索引,则此值可能小于原始对象中的维度数。
代码语言:javascript复制int NpyIter_GetNOp( *iter)返回迭代器中的操作数数量。
代码语言:javascript复制*NpyIter_GetAxisStrideArray( *iter, int axis)获取指定轴的步幅数组。要求迭代器跟踪多索引,并且未启用缓冲区。
当您想以某种方式匹配操作数轴然后使用NpyIter_RemoveAxis手动处理它们时,可以使用此功能。在删除轴之前调用此函数,可以获取手动处理的步幅。
出现错误时返回NULL。
int NpyIter_GetShape( *iter, *outshape)返回outshape中迭代器的广播形状。这只能在正在跟踪多索引的迭代器上调用。
返回NPY_SUCCEED或NPY_FAIL。
**NpyIter_GetDescrArray( *iter)这将返回指向正在迭代的对象的nop数据类型 Descrs 的指针。结果指向iter,所以调用者不会获得对 Descrs 的任何引用。
在迭代循环之前可以缓存此指针,调用iternext不会改变它。
**NpyIter_GetOperandArray( *iter)这将返回指向正在迭代的nop操作PyObjects的指针。结果指向iter,所以调用者不会获得对PyObjects的任何引用。
*NpyIter_GetIterView( *iter, i)这将返回对新的 ndarray 视图的引用,该视图是一个对数组NpyIter_GetOperandArray中第 i 个对象的视图,其维度和步幅与内部优化的迭代模式匹配。对此视图的 C 顺序迭代等同于迭代器的迭代顺序。
例如,如果使用单个数组作为输入创建了迭代器,并且可以重排所有轴,然后将其折叠为单个分段迭代,那么这将返回一个视图,即一个一维数组。
代码语言:javascript复制void NpyIter_GetReadFlags( *iter, char *outreadflags)填充nop标志。如果可以从op[i]读取,则将outreadflags[i]设置为 1,否则设置为 0。
void NpyIter_GetWriteFlags( *iter, char *outwriteflags)填充nop标志。如果可以写入op[i],则将outwriteflags[i]设置为 1,否则设置为 0。
int NpyIter_CreateCompatibleStrides( *iter, itemsize, *outstrides)构建的步幅集(strides)与使用NPY_ITER_ALLOCATE标志创建的输出数组的步幅相同,其中对于op_axes传递的是 NULL。这适用于数据按紧密方式打包,但未必按照 C 或 Fortran 顺序。这应与传入构造函数的标志NPY_ITER_MULTI_INDEX一起使用NpyIter_GetShape和NpyIter_GetNDim。
此函数的用例是匹配迭代器的形状和布局,并添加一个或多个维度。例如,为了为数值梯度生成每个输入值的向量,您传入ndim*itemsize用于itemsize,然后在末尾添加一个大小为ndim且步幅为itemsize的维度。要进行 Hessian 矩阵操作,您可以做同样的事情,但添加两个维度,或者利用对称性并使用特定编码将其打包为 1 个维度。
只有在迭代器跟踪多索引且使用NPY_ITER_DONT_NEGATE_STRIDES防止轴按相反顺序进行迭代时,才可以调用此函数。
如果使用此方法创建数组,则每次迭代只需为itemsize添加即可遍历新数组与迭代器匹配。
返回NPY_SUCCEED或NPY_FAIL。
NpyIter_IsFirstVisit( *iter, int iop)版本 1.7 中的新增内容。
检查指定的缩减操作数的元素是否是第一次被迭代器指向的。该函数对于缩减操作数和禁用缓冲的情况下返回合理的答案。对于带有缓冲区的非缩减操作数,答案可能不正确。
此函数仅适用于 EXTERNAL_LOOP 模式,并且在未启用该模式时将产生一些错误答案。
如果此函数返回 true,则调用者还应检查操作数的内部循环跨度,因为如果该跨度为 0,则只有第一个元素的内部最外部循环正在首次访问。
警告:出于性能原因,‘iop’没有经过边界检查,没有确认‘iop’实际上是一个减少的操作数,并且没有确认已启用 EXTERNAL_LOOP 模式。这些检查是调用者的责任,并应在任何内部循环之外进行。
用于迭代的函数
代码语言:javascript复制*NpyIter_GetIterNext( *iter, char **errmsg)返回一个迭代的函数指针。该函数的专门版本可以由这个函数计算而不是存储在迭代器结构中。因此,为了获得良好的性能,需要将函数指针保存在变量中,而不是在每次循环迭代中检索。
如果发生错误,则返回 NULL。如果 errmsg 非 NULL,则在返回NPY_FAIL时不会设置 Python 异常。相反,*errmsg 会设置为错误消息。当 errmsg 非 NULL 时,可以在不持有 Python GIL 的情况下安全调用该函数。
典型的循环构造如下。
代码语言:javascript复制NpyIter_IterNextFunc *iternext = NpyIter_GetIterNext(iter, NULL);
char** dataptr = NpyIter_GetDataPtrArray(iter);
do {
/* use the addresses dataptr[0], ... dataptr[nop-1] */
} while(iternext(iter)); 当指定NPY_ITER_EXTERNAL_LOOP时,典型的内部循环结构如下。
NpyIter_IterNextFunc *iternext = NpyIter_GetIterNext(iter, NULL);
char** dataptr = NpyIter_GetDataPtrArray(iter);
npy_intp* stride = NpyIter_GetInnerStrideArray(iter);
npy_intp* size_ptr = NpyIter_GetInnerLoopSizePtr(iter), size;
npy_intp iop, nop = NpyIter_GetNOp(iter);
do {
size = *size_ptr;
while (size--) {
/* use the addresses dataptr[0], ... dataptr[nop-1] */
for (iop = 0; iop < nop; iop) {
dataptr[iop] = stride[iop];
}
}
} while (iternext()); 注意我们在迭代器内部使用 dataptr 数组,而不是将值复制到本地临时位置。这是可能的,因为在调用iternext()时,这些指针将被覆盖为新值,而不是增量更新。
如果使用编译时固定缓冲区(两个标志NPY_ITER_BUFFERED 和 NPY_ITER_EXTERNAL_LOOP),则内部大小也可以用作信号。当iternext()返回 false 时保证大小将变为零,从而实现以下循环构造。请注意,如果使用此构造,则不应将NPY_ITER_GROWINNER作为标志传递,因为在某些情况下会导致更大的大小。
/* The constructor should have buffersize passed as this value */
#define FIXED_BUFFER_SIZE 1024
NpyIter_IterNextFunc *iternext = NpyIter_GetIterNext(iter, NULL);
char **dataptr = NpyIter_GetDataPtrArray(iter);
npy_intp *stride = NpyIter_GetInnerStrideArray(iter);
npy_intp *size_ptr = NpyIter_GetInnerLoopSizePtr(iter), size;
npy_intp i, iop, nop = NpyIter_GetNOp(iter);
/* One loop with a fixed inner size */
size = *size_ptr;
while (size == FIXED_BUFFER_SIZE) {
/*
* This loop could be manually unrolled by a factor
* which divides into FIXED_BUFFER_SIZE
*/
for (i = 0; i < FIXED_BUFFER_SIZE; i) {
/* use the addresses dataptr[0], ... dataptr[nop-1] */
for (iop = 0; iop < nop; iop) {
dataptr[iop] = stride[iop];
}
}
iternext();
size = *size_ptr;
}
/* Finish-up loop with variable inner size */
if (size > 0) do {
size = *size_ptr;
while (size--) {
/* use the addresses dataptr[0], ... dataptr[nop-1] */
for (iop = 0; iop < nop; iop) {
dataptr[iop] = stride[iop];
}
}
} while (iternext()); 代码语言:javascript复制*NpyIter_GetGetMultiIndex( *iter, char **errmsg)返回一个函数指针以获取迭代器的当前多索引。如果迭代器没有跟踪多索引,则返回 NULL。建议在迭代循环之前将该函数指针缓存到本地变量中。
如果发生错误,则返回 NULL。如果 errmsg 非 NULL,则在返回NPY_FAIL时不会设置 Python 异常。相反,*errmsg 会设置为错误消息。当 errmsg 非 NULL 时,可以在不持有 Python GIL 的情况下安全调用该函数。
char **NpyIter_GetDataPtrArray( *iter)这会返回指向nop数据指针的指针。如果未指定NPY_ITER_EXTERNAL_LOOP,则每个数据指针指向迭代器的当前数据项。如果未指定内部迭代,则指向内部循环的第一个数据项。
此指针可能在迭代循环之前被缓存,调用iternext不会改变它。此函数可以在不持有 Python 全局解释器锁(GIL)的情况下安全地调用。
char **NpyIter_GetInitialDataPtrArray( *iter)直接将数据指针数组放入数组中(而不是放入缓冲区),对应迭代索引 0。
这些指针与NpyIter_ResetBasePointers接受的指针不同,因为沿某些轴的方向可能已经被颠倒。
此函数在不持有 Python 全局解释器锁(GIL)的情况下可以安全地调用。
代码语言:javascript复制*NpyIter_GetIndexPtr( *iter)这会返回指向正在跟踪的索引的指针,如果没有正在跟踪的索引,则返回 NULL。只有在构建过程中指定了标志NPY_ITER_C_INDEX或NPY_ITER_F_INDEX之一时才能使用它。
当使用标志NPY_ITER_EXTERNAL_LOOP时,代码需要知道执行内部循环的参数。这些函数提供了这些信息。
*NpyIter_GetInnerStrideArray( *iter)返回一个指向nop步幅数组的指针,每个迭代对象使用一个步幅,以供内部循环使用。
此指针可能在迭代循环之前被缓存,调用iternext不会改变它。此函数可以在不持有 Python 全局解释器锁(GIL)的情况下安全地调用。
警告:虽然指针可能被缓存,但如果迭代器被缓冲,其值可能会发生变化。
代码语言:javascript复制*NpyIter_GetInnerLoopSizePtr( *iter)返回一个指向内部循环应执行的迭代次数的指针。
此地址可能在迭代循环之前被缓存,调用iternext不会改变它。值本身在迭代期间可能会发生变化,特别是如果启用了缓冲区。此函数可以在不持有 Python 全局解释器锁(GIL)的情况下安全地调用。
void NpyIter_GetInnerFixedStrideArray( *iter, *out_strides)获取一组固定的或在整个迭代过程中不会改变的步幅。对于可能发生变化的步幅,位置NPY_MAX_INTP的值将被放置在步幅中。
一旦迭代器准备好进行迭代(如果使用了NPY_ITER_DELAY_BUFALLOC,则在重置后),调用此函数以获取可用于选择快速内循环函数的步长。例如,如果步幅为 0,这意味着内部循环总是可以将其值加载到变量中一次,然后在整个循环中使用变量,或者如果步幅等于项大小,则可以使用该操作数的连续版本。
此函数在不持有 Python 全局解释器锁(GIL)的情况下可以安全地调用。
从以前的 NumPy 迭代器转换
旧的迭代器 API 包括 PyArrayIter_Check、PyArray_Iter* 和 PyArray_ITER_* 等函数。多迭代器数组包括 PyArray_MultiIter*、PyArray_Broadcast 和 PyArray_RemoveSmallest。新的迭代器设计用单个对象和关联的 API 替换了所有这些功能。新 API 的一个目标是,现有迭代器的所有用法都应该能够毫不费力地替换为新迭代器。在 1.6 版本中,唯一的例外是邻域迭代器,在这个迭代器中没有对应的功能。
以下是与新迭代器一起使用的函数的转换表:
Iterator Functions | |
|---|---|
PyArray_IterNew | NpyIter_New |
PyArray_IterAllButAxis | NpyIter_New axes 参数 或 迭代器标志 NPY_ITER_EXTERNAL_LOOP |
PyArray_BroadcastToShape | NOT SUPPORTED (Use the support for multiple operands instead.) |
PyArrayIter_Check | 将需要在 Python 暴露中添加此功能 |
PyArray_ITER_RESET | NpyIter_Reset |
PyArray_ITER_NEXT | NpyIter_GetIterNext 中的函数指针 |
PyArray_ITER_DATA | NpyIter_GetDataPtrArray |
PyArray_ITER_GOTO | NpyIter_GotoMultiIndex |
PyArray_ITER_GOTO1D | NpyIter_GotoIndex 或 NpyIter_GotoIterIndex |
PyArray_ITER_NOTDONE | iternext 函数指针的返回值 |
多迭代器功能 | |
PyArray_MultiIterNew | NpyIter_MultiNew |
PyArray_MultiIter_RESET | NpyIter_Reset |
PyArray_MultiIter_NEXT | NpyIter_GetIterNext 中的函数指针 |
PyArray_MultiIter_DATA | NpyIter_GetDataPtrArray |
PyArray_MultiIter_NEXTi | 不支持(始终为锁定步进迭代) |
PyArray_MultiIter_GOTO | NpyIter_GotoMultiIndex |
PyArray_MultiIter_GOTO1D | NpyIter_GotoIndex或NpyIter_GotoIterIndex |
PyArray_MultiIter_NOTDONE | iternext函数指针的返回值 |
PyArray_Broadcast | 由NpyIter_MultiNew处理 |
PyArray_RemoveSmallest | 迭代器标志 NPY_ITER_EXTERNAL_LOOP |
其他功能 | |
PyArray_ConvertToCommonType | 迭代器标志 NPY_ITER_COMMON_DTYPE |
数组迭代器
数组迭代器封装了 ufuncs 中许多关键功能,允许用户代码支持功能,如输出参数、保留内存布局和缓冲具有错误对齐或类型的数据,而无需编写困难的编码。
该页面记录了迭代器的 API。该迭代器命名为NpyIter,函数命名为NpyIter_*。
有一个数组迭代介绍指南,对于使用该 C API 的人来说可能会感兴趣。在许多情况下,通过在 Python 中创建迭代器来尝试想法是一个好主意,然后再编写 C 迭代代码。
迭代示例
熟悉迭代器的最佳方法是查看其在 NumPy 代码库内部的使用情况。例如,这是对PyArray_CountNonzero的略微调整版本的代码,该版本计算数组中非零元素的数量。
npy_intp PyArray_CountNonzero(PyArrayObject* self)
{
/* Nonzero boolean function */
PyArray_NonzeroFunc* nonzero = PyArray_DESCR(self)->f->nonzero;
NpyIter* iter;
NpyIter_IterNextFunc *iternext;
char** dataptr;
npy_intp nonzero_count;
npy_intp* strideptr,* innersizeptr;
/* Handle zero-sized arrays specially */
if (PyArray_SIZE(self) == 0) {
return 0;
}
/*
* Create and use an iterator to count the nonzeros.
* flag NPY_ITER_READONLY
* - The array is never written to.
* flag NPY_ITER_EXTERNAL_LOOP
* - Inner loop is done outside the iterator for efficiency.
* flag NPY_ITER_NPY_ITER_REFS_OK
* - Reference types are acceptable.
* order NPY_KEEPORDER
* - Visit elements in memory order, regardless of strides.
* This is good for performance when the specific order
* elements are visited is unimportant.
* casting NPY_NO_CASTING
* - No casting is required for this operation.
*/
iter = NpyIter_New(self, NPY_ITER_READONLY|
NPY_ITER_EXTERNAL_LOOP|
NPY_ITER_REFS_OK,
NPY_KEEPORDER, NPY_NO_CASTING,
NULL);
if (iter == NULL) {
return -1;
}
/*
* The iternext function gets stored in a local variable
* so it can be called repeatedly in an efficient manner.
*/
iternext = NpyIter_GetIterNext(iter, NULL);
if (iternext == NULL) {
NpyIter_Deallocate(iter);
return -1;
}
/* The location of the data pointer which the iterator may update */
dataptr = NpyIter_GetDataPtrArray(iter);
/* The location of the stride which the iterator may update */
strideptr = NpyIter_GetInnerStrideArray(iter);
/* The location of the inner loop size which the iterator may update */
innersizeptr = NpyIter_GetInnerLoopSizePtr(iter);
nonzero_count = 0;
do {
/* Get the inner loop data/stride/count values */
char* data = *dataptr;
npy_intp stride = *strideptr;
npy_intp count = *innersizeptr;
/* This is a typical inner loop for NPY_ITER_EXTERNAL_LOOP */
while (count--) {
if (nonzero(data, self)) {
nonzero_count;
}
data = stride;
}
/* Increment the iterator to the next inner loop */
} while(iternext(iter));
NpyIter_Deallocate(iter);
return nonzero_count;
} 多次迭代示例
这是使用迭代器的复制函数。order参数用于控制已分配结果的内存布局,通常希望使用NPY_KEEPORDER。
PyObject *CopyArray(PyObject *arr, NPY_ORDER order)
{
NpyIter *iter;
NpyIter_IterNextFunc *iternext;
PyObject *op[2], *ret;
npy_uint32 flags;
npy_uint32 op_flags[2];
npy_intp itemsize, *innersizeptr, innerstride;
char **dataptrarray;
/*
* No inner iteration - inner loop is handled by CopyArray code
*/
flags = NPY_ITER_EXTERNAL_LOOP;
/*
* Tell the constructor to automatically allocate the output.
* The data type of the output will match that of the input.
*/
op[0] = arr;
op[1] = NULL;
op_flags[0] = NPY_ITER_READONLY;
op_flags[1] = NPY_ITER_WRITEONLY | NPY_ITER_ALLOCATE;
/* Construct the iterator */
iter = NpyIter_MultiNew(2, op, flags, order, NPY_NO_CASTING,
op_flags, NULL);
if (iter == NULL) {
return NULL;
}
/*
* Make a copy of the iternext function pointer and
* a few other variables the inner loop needs.
*/
iternext = NpyIter_GetIterNext(iter, NULL);
innerstride = NpyIter_GetInnerStrideArray(iter)[0];
itemsize = NpyIter_GetDescrArray(iter)[0]->elsize;
/*
* The inner loop size and data pointers may change during the
* loop, so just cache the addresses.
*/
innersizeptr = NpyIter_GetInnerLoopSizePtr(iter);
dataptrarray = NpyIter_GetDataPtrArray(iter);
/*
* Note that because the iterator allocated the output,
* it matches the iteration order and is packed tightly,
* so we don't need to check it like the input.
*/
if (innerstride == itemsize) {
do {
memcpy(dataptrarray[1], dataptrarray[0],
itemsize * (*innersizeptr));
} while (iternext(iter));
} else {
/* For efficiency, should specialize this based on item size... */
npy_intp i;
do {
npy_intp size = *innersizeptr;
char *src = dataptrarray[0], *dst = dataptrarray[1];
for(i = 0; i < size; i , src = innerstride, dst = itemsize) {
memcpy(dst, src, itemsize);
}
} while (iternext(iter));
}
/* Get the result from the iterator object array */
ret = NpyIter_GetOperandArray(iter)[1];
Py_INCREF(ret);
if (NpyIter_Deallocate(iter) != NPY_SUCCEED) {
Py_DECREF(ret);
return NULL;
}
return ret;
} 多索引跟踪示例
此示例向您展示如何处理NPY_ITER_MULTI_INDEX标志。为简单起见,我们假设参数是二维数组。
int PrintMultiIndex(PyArrayObject *arr) {
NpyIter *iter;
NpyIter_IterNextFunc *iternext;
npy_intp multi_index[2];
iter = NpyIter_New(
arr, NPY_ITER_READONLY | NPY_ITER_MULTI_INDEX | NPY_ITER_REFS_OK,
NPY_KEEPORDER, NPY_NO_CASTING, NULL);
if (iter == NULL) {
return -1;
}
if (NpyIter_GetNDim(iter) != 2) {
NpyIter_Deallocate(iter);
PyErr_SetString(PyExc_ValueError, "Array must be 2-D");
return -1;
}
if (NpyIter_GetIterSize(iter) != 0) {
iternext = NpyIter_GetIterNext(iter, NULL);
if (iternext == NULL) {
NpyIter_Deallocate(iter);
return -1;
}
NpyIter_GetMultiIndexFunc *get_multi_index =
NpyIter_GetGetMultiIndex(iter, NULL);
if (get_multi_index == NULL) {
NpyIter_Deallocate(iter);
return -1;
}
do {
get_multi_index(iter, multi_index);
printf("multi_index is [%" NPY_INTP_FMT ", %" NPY_INTP_FMT "]n",
multi_index[0], multi_index[1]);
} while (iternext(iter));
}
if (!NpyIter_Deallocate(iter)) {
return -1;
}
return 0;
} 当调用一个 2x3 数组时,上面的示例会打印:
代码语言:javascript复制multi_index is [0, 0]
multi_index is [0, 1]
multi_index is [0, 2]
multi_index is [1, 0]
multi_index is [1, 1]
multi_index is [1, 2] 迭代器数据类型
迭代器布局是内部细节,用户代码只看到不完整的结构体。
代码语言:javascript复制type NpyIter这是迭代器的一个不透明指针类型。只能通过迭代器 API 访问其内容。
代码语言:javascript复制type NpyIter_Type这是一种类型,将迭代器暴露给 Python。目前,没有暴露的 API 提供访问以 Python 创建的迭代器的值。如果在 Python 中创建迭代器,那么必须在 Python 中使用,反之亦然。这样的 API 可能会在将来的版本中创建。
代码语言:javascript复制type NpyIter_IterNextFunc这是一个迭代循环的函数指针,由NpyIter_GetIterNext返回。
type NpyIter_GetMultiIndexFunc这是一个函数指针,用于获取当前迭代器多重索引,由NpyIter_GetGetMultiIndex返回。
构造和销毁
代码语言:javascript复制*NpyIter_New( *op, flags, order, casting, *dtype)为给定的 numpy 数组对象op创建一个迭代器。
可以在flags中传递的标志是NpyIter_MultiNew中记录的全局和每个操作数标志的任意组合,除了NPY_ITER_ALLOCATE。
任何NPY_ORDER枚举值都可以传递给order。为了进行高效迭代,NPY_KEEPORDER是最佳选项,其他顺序强制执行特定的迭代模式。
任何NPY_CASTING枚举值都可以传递给casting。这些值包括NPY_NO_CASTING、NPY_EQUIV_CASTING、NPY_SAFE_CASTING、NPY_SAME_KIND_CASTING和NPY_UNSAFE_CASTING。为了允许转换发生,还必须启用复制或缓冲。
如果dtype不是NULL,则需要该数据类型。如果允许复制,则如果数据可转换,将进行临时复制。如果启用了NPY_ITER_UPDATEIFCOPY,在迭代器销毁时也将使用另一种转换再次复制数据。
如果出现错误,则返回 NULL,否则返回分配的迭代器。
要使迭代器类似于旧迭代器,这样处理应该可以。
代码语言:javascript复制iter = NpyIter_New(op, NPY_ITER_READWRITE,
NPY_CORDER, NPY_NO_CASTING, NULL); 如果你想用对齐double代码编辑数组,但顺序无关紧要,你可以这样做。
dtype = PyArray_DescrFromType(NPY_DOUBLE);
iter = NpyIter_New(op, NPY_ITER_READWRITE|
NPY_ITER_BUFFERED|
NPY_ITER_NBO|
NPY_ITER_ALIGNED,
NPY_KEEPORDER,
NPY_SAME_KIND_CASTING,
dtype);
Py_DECREF(dtype); 代码语言:javascript复制*NpyIter_MultiNew( nop, **op, flags, order, casting, *op_flags, **op_dtypes)创建一个迭代器,用于广播op中提供的nop数组对象,使用常规的 NumPy 广播规则。
order参数可以传递NPY_ORDER 枚举值中的任意一个。为了有效迭代,NPY_KEEPORDER 是最佳选项,其他顺序强制执行特定的迭代模式。当使用NPY_KEEPORDER时,如果您还希望确保迭代沿某个轴不被翻转,应该传递标志NPY_ITER_DONT_NEGATE_STRIDES。
casting参数可以传递NPY_CASTING 枚举值中的任意一个。这些值包括NPY_NO_CASTING、NPY_EQUIV_CASTING、NPY_SAFE_CASTING、NPY_SAME_KIND_CASTING和NPY_UNSAFE_CASTING。要允许进行强制转换,还必须启用复制或缓冲。
如果op_dtypes不是NULL,它为每个op[i]指定一个数据类型或NULL。
如果出现错误,则返回 NULL,否则返回分配的迭代器。
可以在flags中传递的整个迭代器应用的标志有:
NPY_ITER_C_INDEX使迭代器跟踪匹配 C 顺序的展平索引。此选项不能与NPY_ITER_F_INDEX一起使用。
NPY_ITER_F_INDEX使迭代器跟踪匹配 Fortran 顺序的展平索引。此选项不能与NPY_ITER_C_INDEX一起使用。
NPY_ITER_MULTI_INDEX使迭代器跟踪多重索引。这会防止迭代器将轴合并为更大的内层循环。如果循环也没有被缓冲且未跟踪任何索引(可以调用NpyIter_RemoveAxis),则迭代器大小可以为-1,表示迭代器太大。这可能是由于复杂的广播导致的,将导致在设置迭代器范围、移除多重索引或获取下一个函数时创建错误。但是,如果在移除后大小足够小,则可以再次移除轴并正常使用迭代器。
NPY_ITER_EXTERNAL_LOOP使迭代器跳过内层循环的迭代,需要迭代器使用者处理它。
此标志与NPY_ITER_C_INDEX、NPY_ITER_F_INDEX和NPY_ITER_MULTI_INDEX不兼容。
NPY_ITER_DONT_NEGATE_STRIDES当指定顺序参数为NPY_KEEPORDER时,这仅影响迭代器。默认情况下,使用NPY_KEEPORDER时,迭代器颠倒具有负步幅的轴,以便内存以正向顺序遍历。这会禁用此步骤。如果要使用轴的底层内存顺序,但不希望反转轴,则使用此标志。例如,numpy.ravel(a, order='K')的行为。
NPY_ITER_COMMON_DTYPE使迭代器将所有操作数转换为一个共同的数据类型,根据 ufunc 类型提升规则计算。必须启用复制或缓冲。
如果已经提前知道了共同的数据类型,请不要使用此标志。而是为所有操作数设置请求的 dtype。
代码语言:javascript复制NPY_ITER_REFS_OK表示接受并在迭代器中使用具有引用类型(对象数组或包含对象类型的结构化数组)的数组。如果启用了此标志,则调用方必须确保检查是否在迭代期间需要 API(iter)为真,这种情况下可能不会在迭代期间释放 GIL。
代码语言:javascript复制NPY_ITER_ZEROSIZE_OK表示应允许大小为零的数组。由于典型的迭代循环不自然地适用于大小为零的数组,因此在进入迭代循环之前必须检查 IterSize 是否大于零。当前仅检查操作数,而不是强制形状。
代码语言:javascript复制NPY_ITER_REDUCE_OK允许具有零步幅和大小大于一的维度的可写操作数。注意,这样的操作数必须是可读/可写的。
启用缓冲时,这还会切换到一种特殊的缓冲模式,根据需要减少循环长度,以免踩到正在减少的值。
请注意,如果要对自动分配的输出进行缩减,必须使用NpyIter_GetOperandArray获取其引用,然后在执行迭代循环之前将每个值设置为缩减单元。对于缓冲缩减,这意味着您还必须指定标志NPY_ITER_DELAY_BUFALLOC,然后在初始化已分配操作数以准备缓冲区后重置迭代器。
NPY_ITER_RANGED启用对完整iterindex范围0, NpyIter_IterSize(iter))的子范围的迭代支持。使用函数[NpyIter_ResetToIterIndexRange来指定迭代范围。
当启用NPY_ITER_BUFFERED时,此标志只能与NPY_ITER_EXTERNAL_LOOP一起使用。这是因为如果没有缓冲,内部循环总是内层迭代维度的大小,并且允许它被分割将需要特殊处理,实际上使其更像缓冲版本。
NPY_ITER_BUFFERED导致迭代器存储缓冲区数据,并使用缓冲来满足数据类型、对齐和字节顺序要求。 要缓冲操作数,请不要指定NPY_ITER_COPY或NPY_ITER_UPDATEIFCOPY标志,因为它们会覆盖缓冲。 对于使用迭代器的 Python 代码,缓冲特别有用,允许一次处理更大的数据块以摊销 Python 解释器的开销。
如果与NPY_ITER_EXTERNAL_LOOP一起使用,则调用者的内部循环可能会比没有缓冲区时得到更大的块,这是因为步幅的布局方式。
请注意,如果给一个操作数设置了标志NPY_ITER_COPY或NPY_ITER_UPDATEIFCOPY,则首选制作副本而不是缓冲。 当数组进行广播时仍然会发生缓冲,因此需要复制元素以获得常量步幅。
在正常缓冲中,每个内部循环的大小等于缓冲区大小,如果指定了NPY_ITER_GROWINNER,则可能更大。 如果启用了NPY_ITER_REDUCE_OK并发生了减少,内部循环的大小可能会变小,具体取决于减少的结构。
NPY_ITER_GROWINNER当启用缓冲时,这允许内部循环的大小在不需要缓冲时增长。 如果您正在直接通过所有数据进行传递,而不是对每个内部循环进行小的缓存友好数组的任何操作,则最好使用此选项。
代码语言:javascript复制NPY_ITER_DELAY_BUFALLOC当启用缓冲时,这将延迟分配缓冲区,直到调用了NpyIter_Reset或另一个重置函数。 此标志存在是为了在多线程迭代中制作多个缓冲迭代器的多个副本时避免浪费缓冲区数据的复制。
此标志的另一个用途是设置减少操作。 创建迭代器后,迭代器会自动分配减少输出(确保使用 READWRITE 访问),其值可以初始化为减少单位。 使用NpyIter_GetOperandArray获取对象。 然后,调用NpyIter_Reset来分配并填充缓冲区的初始值。
NPY_ITER_COPY_IF_OVERLAP如果任何写操作数与任何读操作数存在重叠,通过制作临时副本来消除所有重叠(如果需要,为写操作数启用 UPDATEIFCOPY)。 如果一对操作数存在重叠,则存在包含两个数组共有数据的内存地址。
由于精确的重叠检测在维数的指数运行时,决策是基于启发式方法的,它具有误报(在不寻常情况下不必要的副本),但没有误报。
如果存在读/写重叠,此标志确保操作的结果与所有操作数都被复制时相同。在需要进行复制的情况下,没有此标志可能导致计算结果是未定义的!
可以在 op_flags[i] 中传递的标志,其中 0 <= i < nop:
NPY_ITER_READWRITE代码语言:javascript复制NPY_ITER_READONLY代码语言:javascript复制NPY_ITER_WRITEONLY指示迭代器的用户如何读取或写入 op[i]。每个操作数都必须指定其中一个标志。使用NPY_ITER_READWRITE或NPY_ITER_WRITEONLY用于用户提供的操作数可能触发WRITEBACKIFCOPY语义。在调用 NpyIter_Deallocate 时,数据将写回原始数组。
NPY_ITER_COPY如果 op[i] 不符合构造函数标志和参数指定的数据类型或对齐要求,则允许对 op[i] 进行复制。
NPY_ITER_UPDATEIFCOPY触发 NPY_ITER_COPY,当一个数组操作数被标记为写入并被复制时,在调用 NpyIter_Deallocate 时导致副本中的数据被复制回 op[i]。
如果操作数被标记为只写,并且需要复制,将创建一个未初始化的临时数组,然后在调用 NpyIter_Deallocate 时将其复制回 op[i],而不是进行不必要的复制操作。
NPY_ITER_NBO代码语言:javascript复制NPY_ITER_ALIGNED代码语言:javascript复制NPY_ITER_CONTIG使迭代器提供适配于op[i]的数据,其是按照本机字节顺序对齐,符合 dtype 要求,并且是连续的,或者任何组合。
默认情况下,迭代器生成指向所提供数组的指针,这些指针可能对齐或者不对齐,并且具有任何字节顺序。如果未启用复制或缓冲,并且操作数数据不满足约束条件,则会引发错误。
连续约束仅适用于内部循环,连续的内循环可能具有任意的指针变化。
如果所请求的数据类型为非本机字节顺序,则 NBO 标志将覆盖它,并将所请求的数据类型转换为本机字节顺序。
代码语言:javascript复制NPY_ITER_ALLOCATE这将用于输出数组,并要求设置标志NPY_ITER_WRITEONLY或NPY_ITER_READWRITE。如果 op[i] 为 NULL,则创建一个具有最终广播维度和与迭代器的迭代顺序相匹配的布局的新数组。
当 op[i] 为 NULL 时,所请求的数据类型op_dtypes[i] 也可能为 NULL,这种情况下会自动生成它,其来自被标记为可读取的数组的数据类型。生成数据类型的规则与 UFuncs 相同。特别需要注意的是所选数据类型的字节序处理方法。如果只有一个输入,将直接使用输入的数据类型。否则,如果将多个输入数据类型组合在一起,则输出将采用本机字节序。
使用此标志分配后,调用者可以通过调用NpyIter_GetOperandArray并获取返回的 C 数组中的第 i 个对象来检索新数组。调用者必须调用 Py_INCREF 来声明对数组的引用。
NPY_ITER_NO_SUBTYPE用于NPY_ITER_ALLOCATE,此标志禁用为输出分配数组子类型,强制其为一维 ndarray。
TODO:也许引入一个函数NpyIter_GetWrappedOutput并删除此标志会更好?
NPY_ITER_NO_BROADCAST确保输入或输出与迭代维度完全匹配。
代码语言:javascript复制NPY_ITER_ARRAYMASK自版本 1.7 开始。
指示这个操作数是用于在写入应用了NPY_ITER_WRITEMASKED标志的操作数时使用的掩码。只有一个操作数可以应用NPY_ITER_ARRAYMASK标志。
具有此标志的操作数的数据类型应为NPY_BOOL,NPY_MASK或其字段都是有效掩码数据类型的结构数据类型。在后一种情况下,它必须与被 WRITEMASKED 的结构操作数匹配,因为它指定了该数组每个字段的掩码。
此标记仅影响从缓冲区回写到数组。这意味着,如果操作数还是NPY_ITER_READWRITE或NPY_ITER_WRITEONLY,则进行迭代的代码可以写入此操作数以控制哪些元素将不受影响,哪些将被修改。当掩码应该是输入掩码的组合时,这是有用的。
NPY_ITER_WRITEMASKED自版本 1.7 开始。
此数组是所有writemasked操作数的掩码。代码使用writemasked标志,指示只会写入选择的 ARRAYMASK 操作数为 True 的元素。一般来说,迭代器并不强制这一点,迭代的代码应该遵循这个承诺。
当使用writemasked标志并且此操作数被缓冲时,这会改变数据从缓冲区复制到数组的方式。会使用一个带掩码的复制例程,仅复制缓冲区中writemasked从相应的 ARRAYMASK 操作数返回 True 的元素。
NPY_ITER_OVERLAP_ASSUME_ELEMENTWISE在内存重叠检查中,假设启用了NPY_ITER_OVERLAP_ASSUME_ELEMENTWISE的操作数只能按照迭代器顺序访问。
这使得迭代器能够推理数据依赖性,可能避免不必要的复制。
仅当迭代器上启用了NPY_ITER_COPY_IF_OVERLAP时,此标志生效。
*NpyIter_AdvancedNew( nop, **op, flags, order, casting, *op_flags, **op_dtypes, int oa_ndim, int **op_axes, const *itershape, buffersize)通过提供几个高级选项,扩展了NpyIter_MultiNew ,提供了更多对广播和缓冲的控制。
如果将-1/NULL 值传递给oa_ndim、op_axes、itershape和buffersize,则相当于NpyIter_MultiNew。
当参数oa_ndim不为零或-1 时,指定将使用定制广播迭代的维度数量。如果提供了op_axes,则必须提供itershape。op_axes参数允许您详细控制操作数数组的轴如何匹配在一起并进行迭代。在op_axes中,必须提供nop指针数组,指向大小为oa_ndim的npy_intp类型数组。如果op_axes中的条目为 NULL,则将应用正常的广播规则。op_axes[j][i]中存储了op[j]的有效轴,或者是-1 表示newaxis。在每个op_axes[j]数组中,轴不能重复。以下示例是正常广播应用到 3D 数组、2D 数组、1D 数组和标量的情况。
注意:在 NumPy 1.8 之前,oa_ndim == 0用于表示``op_axes和itershape未使用。这已经不推荐使用,应替换为-1。最好使用NpyIter_MultiNew`来实现更好的向后兼容性。
int oa_ndim = 3; /* # iteration axes */
int op0_axes[] = {0, 1, 2}; /* 3-D operand */
int op1_axes[] = {-1, 0, 1}; /* 2-D operand */
int op2_axes[] = {-1, -1, 0}; /* 1-D operand */
int op3_axes[] = {-1, -1, -1} /* 0-D (scalar) operand */
int* op_axes[] = {op0_axes, op1_axes, op2_axes, op3_axes}; itershape参数允许您强制迭代器具有特定的迭代形状。它的长度为oa_ndim。当一个条目是负数时,它的值将根据操作数确定。此参数允许自动分配的输出获得与任何输入维度不匹配的附加维度。
如果buffersize为零,则使用默认缓冲区大小,否则它指定要使用多大的缓冲区。建议使用 2 的幂大小的缓冲区,例如 4096 或 8192。
如果存在错误则返回 NULL,否则返回分配的迭代器。
代码语言:javascript复制*NpyIter_Copy( *iter)复制给定迭代器。此函数主要用于使数据多线程迭代。
TODO:将此内容移到关于多线程迭代的部分。
多线程迭代的推荐方法是首先使用标记NPY_ITER_EXTERNAL_LOOP、NPY_ITER_RANGED、NPY_ITER_BUFFERED、NPY_ITER_DELAY_BUFALLOC,可能还有NPY_ITER_GROWINNER创建一个迭代器。为每个线程创建此迭代器的副本(第一个迭代器减去一)。然后,将迭代索引范围0,NpyIter_GetIterSize(iter))分割成任务,例如使用 TBB parallel_for 循环。当一个线程获得要执行的任务时,它将使用其迭代器的副本,通过调用[NpyIter_ResetToIterIndexRange并迭代整个范围。
在多线程代码或不持有 Python GIL 的代码中使用迭代器时,必须小心地只调用在该上下文中安全的函数。不能在没有 Python GIL 的情况下安全调用 NpyIter_Copy,因为它会增加 Python 引用。Reset* 和一些其他函数可以安全调用,通过传递非 NULL 的 errmsg 参数,这样函数将通过它传回错误,而不是设置 Python 异常。
必须为每个副本调用 NpyIter_Deallocate。
int NpyIter_RemoveAxis( *iter, int axis)从迭代中移除一个轴。这要求在迭代器创建时设置了 NPY_ITER_MULTI_INDEX,并且在启用缓冲或正在跟踪索引时无法工作。此函数还将迭代器重置回初始状态。
例如用于设置累积循环很有用。迭代器可以首先使用所有维度创建,包括累积轴,以便正确创建输出。然后,可以移除累积轴,并采用嵌套的方式进行计算。
警告:此函数可能会更改迭代器的内部内存布局。必须重新获取迭代器的任何缓存功能或指针!迭代范围也将被重置。
返回 NPY_SUCCEED 或 NPY_FAIL。
int NpyIter_RemoveMultiIndex( *iter)如果迭代器正在跟踪多重索引,则会取消对它们的支持,并对迭代器进行可能的进一步优化,如果不需要多重索引的话。此函数还将迭代器重置回初始状态。
警告:此函数可能会更改迭代器的内部内存布局。必须重新获取迭代器的任何缓存功能或指针!
调用此函数后,NpyIter_HasMultiIndex(iter) 将返回 false。
返回 NPY_SUCCEED 或 NPY_FAIL。
int NpyIter_EnableExternalLoop( *iter)如果调用了 NpyIter_RemoveMultiIndex,您可能希望启用标志 NPY_ITER_EXTERNAL_LOOP。这个标志与 NPY_ITER_MULTI_INDEX 不允许一起使用,因此在调用了 NpyIter_RemoveMultiIndex 之后提供此函数以启用该功能。此函数还将迭代器重置回初始状态。
警告:此函数更改了迭代器的内部逻辑。必须重新获取迭代器的任何缓存功能或指针!
返回 NPY_SUCCEED 或 NPY_FAIL。
int NpyIter_Deallocate( *iter)释放迭代器对象并解决任何需要的写回。
返回 NPY_SUCCEED 或 NPY_FAIL。
int NpyIter_Reset( *iter, char **errmsg)将迭代器重置回初始状态,即迭代范围的开始处。
返回NPY_SUCCEED或NPY_FAIL。如果 errmsg 非空,则当返回NPY_FAIL时不会设置任何 Python 异常。相反,errmsg 会被设置为错误消息。当 errmsg 非空时,可以在不持有 Python GIL 的情况下安全地调用该函数。
int NpyIter_ResetToIterIndexRange( *iter, istart, iend, char **errmsg)重置迭代器并将其限制在iterindex范围istart, iend)。有关如何在多线程迭代中使用此功能的解释,请参阅[NpyIter_Copy。这要求在迭代器构造函数中传递了标志NPY_ITER_RANGED。
如果要同时重置iterindex范围和基本指针,可以执行以下操作以避免额外的缓冲区复制(在复制此代码时,请务必添加返回码错误检查)。
/* Set to a trivial empty range */
NpyIter_ResetToIterIndexRange(iter, 0, 0);
/* Set the base pointers */
NpyIter_ResetBasePointers(iter, baseptrs);
/* Set to the desired range */
NpyIter_ResetToIterIndexRange(iter, istart, iend); 返回NPY_SUCCEED或NPY_FAIL。如果 errmsg 非空,则当返回NPY_FAIL时不会设置任何 Python 异常。相反,errmsg 会被设置为错误消息。当 errmsg 非空时,可以在不持有 Python GIL 的情况下安全地调用该函数。
int NpyIter_ResetBasePointers( *iter, char **baseptrs, char **errmsg)将迭代器重置回初始状态,但使用baseptrs中的值作为数据,而不是被迭代的数组中的指针。这个函数有意与 op_axes 参数一起被嵌套迭代代码使用,其中有两个或更多个迭代器。
返回NPY_SUCCEED或NPY_FAIL。如果 errmsg 非空,则当返回NPY_FAIL时不会设置任何 Python 异常。相反,errmsg 会被设置为错误消息。当 errmsg 非空时,可以在不持有 Python GIL 的情况下安全地调用该函数。
TODO:将以下内容移入关于嵌套迭代器的特殊部分中。
创建嵌套迭代的迭代器需要一些注意。所有的迭代器操作数必须完全匹配,否则调用NpyIter_ResetBasePointers 将无效。这意味着不应该随意使用自动复制和输出分配。仍然可以通过启用所有转换参数创建迭代器之一,然后用NpyIter_GetOperandArray函数获取分配的操作数,并将它们传递到其余迭代器的构造函数中来使用迭代器的自动数据转换和转换功能。
警告:在创建用于嵌套迭代的迭代器时,代码不能在不同的迭代器中多次使用相同的维度。如果这样做,嵌套迭代将在迭代期间产生越界指针。
警告:在创建用于嵌套迭代的迭代器时,缓冲区只能应用于最内部的迭代器。如果一个带有缓冲区的迭代器作为baseptrs的源,则它将指向一个小缓冲区,而不是数组,内部迭代将无效。
使用嵌套迭代器的模式如下。
代码语言:javascript复制NpyIter *iter1, *iter1;
NpyIter_IterNextFunc *iternext1, *iternext2;
char **dataptrs1;
/*
* With the exact same operands, no copies allowed, and
* no axis in op_axes used both in iter1 and iter2.
* Buffering may be enabled for iter2, but not for iter1.
*/
iter1 = ...; iter2 = ...;
iternext1 = NpyIter_GetIterNext(iter1);
iternext2 = NpyIter_GetIterNext(iter2);
dataptrs1 = NpyIter_GetDataPtrArray(iter1);
do {
NpyIter_ResetBasePointers(iter2, dataptrs1);
do {
/* Use the iter2 values */
} while (iternext2(iter2));
} while (iternext1(iter1)); 代码语言:javascript复制int NpyIter_GotoMultiIndex( *iter, const *multi_index)将迭代器调整到由multi_index指向的ndim索引。如果未正在追踪多索引,索引超出范围,或内部循环迭代被禁用,则返回错误。
返回NPY_SUCCEED或NPY_FAIL。
int NpyIter_GotoIndex( *iter, index)将迭代器调整到指定的index。如果迭代器是使用标记NPY_ITER_C_INDEX构造的,则index是 C 顺序索引;如果迭代器是使用标记NPY_ITER_F_INDEX构造的,则index是 Fortran 顺序索引。如果没有正在追踪的索引,索引超出范围,或者内部循环迭代被禁用,则返回错误。
返回NPY_SUCCEED或NPY_FAIL。
NpyIter_GetIterSize( *iter)返回正在迭代的元素数量。这是形状中所有维度的乘积。当正在追踪多个索引(并且可能调用NpyIter_RemoveAxis)时,大小可能为-1,表示迭代器过大。这样的迭代器无效,但在调用NpyIter_RemoveAxis后可能变为有效。不必检查这种情况。
NpyIter_GetIterIndex( *iter)获取迭代器的iterindex,即与迭代器的迭代顺序匹配的索引。
void NpyIter_GetIterIndexRange( *iter, *istart, *iend)获取正在迭代的iterindex子范围。如果未指定标记NPY_ITER_RANGED,则始终返回范围0, NpyIter_IterSize(iter))。
int NpyIter_GotoIterIndex( *iter, iterindex)调整迭代器以指向指定的iterindex。IterIndex 是将迭代器的迭代顺序匹配的索引。如果iterindex超出范围,启用了缓冲,或内部循环迭代被禁用,则返回错误。
返回NPY_SUCCEED或NPY_FAIL。
NpyIter_HasDelayedBufAlloc( *iter)如果标记[NPY_ITER_DELAY_BUFALLOC被传递给迭代器构造函数,并且还没有调用其中一个 Reset 函数,则返回 1;否则返回 0。
NpyIter_HasExternalLoop( *iter)根据构造函数标记NPY_ITER_EXTERNAL_LOOP或NpyIter_EnableExternalLoop,返回 1 表示调用者需要处理最内层的一维循环,返回 0 表示迭代器处理所有循环。
NpyIter_HasMultiIndex( *iter)如果迭代器是使用标记NPY_ITER_MULTI_INDEX创建的,则返回 1,否则返回 0。
NpyIter_HasIndex( *iter)如果迭代器是使用标记NPY_ITER_C_INDEX或NPY_ITER_F_INDEX创建的,则返回 1,否则返回 0。
NpyIter_RequiresBuffering( *iter)如果迭代器需要缓冲,则返回 1,即当操作数需要转换或对齐时无法直接使用。
代码语言:javascript复制NpyIter_IsBuffered( *iter)如果迭代器是使用标记NPY_ITER_BUFFERED创建的,则返回 1,否则返回 0。
NpyIter_IsGrowInner( *iter)如果迭代器是使用标记NPY_ITER_GROWINNER创建的,则返回 1,否则返回 0。
NpyIter_GetBufferSize( *iter)如果迭代器已缓冲,则返回正在使用的缓冲区的大小,否则返回 0。
代码语言:javascript复制int NpyIter_GetNDim( *iter)返回正在迭代的维数。如果在迭代器构造函数中未请求多索引,则此值可能小于原始对象中的维数。
代码语言:javascript复制int NpyIter_GetNOp( *iter)返回迭代器中的操作数数量。
代码语言:javascript复制*NpyIter_GetAxisStrideArray( *iter, int axis)获取指定轴的步幅数组。要求迭代器跟踪多索引,并且缓冲未启用。
在您希望以某种方式匹配操作数轴,然后使用 NpyIter_RemoveAxis 手动处理它们时,可以使用此函数。在移除轴之前调用此函数,可以获取手动处理的步幅。
错误时返回NULL。
int NpyIter_GetShape( *iter, *outshape)在outshape中返回迭代器的广播形状。只能在跟踪多索引的迭代器上调用此函数。
返回NPY_SUCCEED或NPY_FAIL。
**NpyIter_GetDescrArray( *iter)这会返回正在迭代的对象的nop数据类型 Descrs 的指针。结果指向iter,因此调用者不会获得任何对 Descrs 的引用。
在迭代循环之前,此指针可以被缓存,调用iternext不会更改它。
**NpyIter_GetOperandArray( *iter)这会将指针返回到正在迭代的nop操作数 PyObjects。结果指向iter,因此调用者不会获得任何对 PyObjects 的引用。
*NpyIter_GetIterView( *iter, i)将返回一个新的 ndarray 视图的引用,该视图是数组NpyIter_GetOperandArray中第 i 个对象的视图,其维度和步幅与内部优化的迭代模式相匹配。此视图的 C 顺序迭代等同于迭代器的迭代顺序。
例如,如果使用单个数组作为输入创建了迭代器,并且可能对其所有轴进行重排,然后将其合并为一个单一的跨度迭代,这将返回一个视图,即一维数组。
代码语言:javascript复制void NpyIter_GetReadFlags( *iter, char *outreadflags)填充nop标志。如果可以从op[i]读取,则将outreadflags[i]设置为 1,否则设置为 0。
void NpyIter_GetWriteFlags( *iter, char *outwriteflags)填充nop标志。如果可以向op[i]写入,则将outwriteflags[i]设置为 1,否则设置为 0。
int NpyIter_CreateCompatibleStrides( *iter, itemsize, *outstrides)构建与使用 NPY_ITER_ALLOCATE 标志创建的输出数组的步幅相同的步幅集,其中为 op_axes 传递了 NULL。这用于连续打包的数据,但不一定按照 C 或 Fortran 顺序。这应该与传递到构造函数的标志 NPY_ITER_MULTI_INDEX 一起使用,以及 NpyIter_GetShape 和 NpyIter_GetNDim。
此函数的用例是匹配迭代器的形状和布局,并附加一个或多个维度。例如,为了为数值梯度每个输入值生成一个向量,您需要传入 ndim*itemsize 作为 itemsize,然后在末尾添加另一个大小为 ndim,步幅为 itemsize 的维度。对于 Hessian 矩阵,做同样的事情,但是增加两个维度,或者利用对称性并将其打包到一个特定编码的维度中。
只有在迭代器正在跟踪多索引并且使用了NPY_ITER_DONT_NEGATE_STRIDES以防止轴逆序迭代时,才能调用此函数。
如果使用此方法创建数组,则仅需添加每次迭代的‘itemsize’即可遍历与迭代器匹配的新数组。
返回NPY_SUCCEED或NPY_FAIL。
NpyIter_IsFirstVisit( *iter, int iop)版本 1.7 中的新内容。
检查是否是迭代器指向的指定减少操作数的元素第一次被看到。该函数对于减少操作数和禁用缓冲的情况会返回一个合理的答案。对于缓冲的非减少操作数,答案可能是不正确的。
此函数仅用于 EXTERNAL_LOOP 模式,并且在未启用该模式时将产生一些错误的答案。
如果此函数返回 true,则调用者还应检查操作数的内循环步幅,因为如果该步幅为 0,则仅首次访问最内层外部循环的第一个元素。
警告:出于性能原因,’iop’没有进行边界检查,没有确认‘iop’实际上是减少操作数,也没有确认是否启用了 EXTERNAL_LOOP 模式。这些检查是调用者的责任,并应在任何内部循环之外完成。
迭代函数
代码语言:javascript复制*NpyIter_GetIterNext( *iter, char **errmsg)返回一个用于迭代的函数指针。这个函数计算了函数指针的专门版本,而不是将其存储在迭代器结构中。因此,为了获得良好的性能,必须将函数指针保存在变量中,而不是在每次循环迭代中获取它。
如果有错误则返回 NULL。如果 errmsg 非 NULL,在返回NPY_FAIL时不会设置 Python 异常。相反,*errmsg 会被设置为错误消息。当 errmsg 非 NULL 时,可以在不持有 Python GIL 的情况下安全地调用该函数。
典型的循环结构如下。
代码语言:javascript复制NpyIter_IterNextFunc *iternext = NpyIter_GetIterNext(iter, NULL);
char** dataptr = NpyIter_GetDataPtrArray(iter);
do {
/* use the addresses dataptr[0], ... dataptr[nop-1] */
} while(iternext(iter)); 当指定NPY_ITER_EXTERNAL_LOOP时,典型的内部循环结构如下。
NpyIter_IterNextFunc *iternext = NpyIter_GetIterNext(iter, NULL);
char** dataptr = NpyIter_GetDataPtrArray(iter);
npy_intp* stride = NpyIter_GetInnerStrideArray(iter);
npy_intp* size_ptr = NpyIter_GetInnerLoopSizePtr(iter), size;
npy_intp iop, nop = NpyIter_GetNOp(iter);
do {
size = *size_ptr;
while (size--) {
/* use the addresses dataptr[0], ... dataptr[nop-1] */
for (iop = 0; iop < nop; iop) {
dataptr[iop] = stride[iop];
}
}
} while (iternext()); 请注意,我们在迭代器内部使用了 dataptr 数组,而不是将值复制到局部临时变量。这是因为当调用iternext()时,这些指针将被覆盖为新值,而不是逐渐更新。
如果正在使用编译时固定缓冲区(标志NPY_ITER_BUFFERED和NPY_ITER_EXTERNAL_LOOP),则内部大小也可以用作信号。当iternext()返回 false 时,保证大小将变为零,从而启用以下循环结构。请注意,如果使用此结构,不应将NPY_ITER_GROWINNER作为标志传递,因为在某些情况下会导致较大的大小。
/* The constructor should have buffersize passed as this value */
#define FIXED_BUFFER_SIZE 1024
NpyIter_IterNextFunc *iternext = NpyIter_GetIterNext(iter, NULL);
char **dataptr = NpyIter_GetDataPtrArray(iter);
npy_intp *stride = NpyIter_GetInnerStrideArray(iter);
npy_intp *size_ptr = NpyIter_GetInnerLoopSizePtr(iter), size;
npy_intp i, iop, nop = NpyIter_GetNOp(iter);
/* One loop with a fixed inner size */
size = *size_ptr;
while (size == FIXED_BUFFER_SIZE) {
/*
* This loop could be manually unrolled by a factor
* which divides into FIXED_BUFFER_SIZE
*/
for (i = 0; i < FIXED_BUFFER_SIZE; i) {
/* use the addresses dataptr[0], ... dataptr[nop-1] */
for (iop = 0; iop < nop; iop) {
dataptr[iop] = stride[iop];
}
}
iternext();
size = *size_ptr;
}
/* Finish-up loop with variable inner size */
if (size > 0) do {
size = *size_ptr;
while (size--) {
/* use the addresses dataptr[0], ... dataptr[nop-1] */
for (iop = 0; iop < nop; iop) {
dataptr[iop] = stride[iop];
}
}
} while (iternext()); 代码语言:javascript复制*NpyIter_GetGetMultiIndex( *iter, char **errmsg)返回获取迭代器当前多索引的函数指针。如果迭代器未跟踪多索引,则返回 NULL。建议在迭代循环之前将此函数指针缓存到一个本地变量中。
如果发生错误,则返回 NULL。如果NPY_FAIL返回,则不设置 Python 异常。相反,errmsg 被设置为错误消息。当errmsg非 NULL 时,可以安全地调用该函数,而无需持有 Python GIL。
char **NpyIter_GetDataPtrArray( *iter)这将返回一个指向nop数据指针的指针。如果未指定NPY_ITER_EXTERNAL_LOOP,则每个数据指针指向迭代器的当前数据项。如果未指定内部迭代,则指向内部循环的第一个数据项。
此指针可能在迭代循环之前被缓存,调用iternext不会改变它。此函数可在不持有 Python GIL 的情况下安全调用。
char **NpyIter_GetInitialDataPtrArray( *iter)直接获取数据指针数组进入数组(永远不会进入缓冲区),对应于迭代索引 0。
这些指针与NpyIter_ResetBasePointers接受的指针不同,因为某些轴上的方向可能已被反转。
此函数可在不持有 Python GIL 的情况下安全调用。
代码语言:javascript复制*NpyIter_GetIndexPtr( *iter)这将返回一个指针,指向正在跟踪的索引,如果没有跟踪索引,则返回 NULL。仅当在构造期间指定了标志NPY_ITER_C_INDEX或NPY_ITER_F_INDEX之一时才可用。
当使用标志NPY_ITER_EXTERNAL_LOOP时,代码需要知道执行内部循环的参数。这些函数提供了该信息。
*NpyIter_GetInnerStrideArray( *iter)返回指向nop步长数组的指针,每个迭代对象一个,用于内部循环使用。
此指针可能在迭代循环之前被缓存,调用iternext不会改变它。此函数可在不持有 Python GIL 的情况下安全调用。
警告:虽然指针可能被缓存,但如果迭代器被缓冲,其值可能会发生变化。
代码语言:javascript复制*NpyIter_GetInnerLoopSizePtr( *iter)返回指针,指向内部循环应执行的迭代次数。
此地址可以在迭代循环之前进行缓存,调用iternext不会更改它。值本身可能会在迭代过程中更改,特别是如果启用了缓冲。可以在没有持有 Python GIL 的情况下安全调用此函数。
void NpyIter_GetInnerFixedStrideArray( *iter, *out_strides)获取一个在整个迭代过程中是固定或不会更改的步长数组。对于可能会更改的步长,会将值 NPY_MAX_INTP 放入步长中。
一旦迭代器准备好进行迭代(如果使用了NPY_ITER_DELAY_BUFALLOC后的重置),调用此函数以获取可用于选择快速内循环函数的步长。例如,如果步长为 0,这意味着内循环总是可以将其值加载到变量中一次,然后在整个循环中使用该变量,或者如果步长等于项目大小,则该操作数的连续版本可能会被使用。
可以在没有持有 Python GIL 的情况下安全调用此函数。
从先前的 NumPy 迭代器进行转换
旧的迭代器 API 包括函数如 PyArrayIter_Check,PyArray_Iter和 PyArray_ITER_。多迭代器数组包括 PyArray_MultiIter*,PyArray_Broadcast 和 PyArray_RemoveSmallest。新的迭代器设计将所有这些功能及其相关 API 替换为单个对象。新 API 的一个目标是,现有迭代器的所有用法都应该可以在不费力的情况下用新迭代器替代。在 1.6 中,对此的主要例外是邻域迭代器,在这个迭代器中没有相应的功能。
以下是用于新迭代器的函数使用转换表:
迭代器函数 | |
|---|---|
PyArray_IterNew | NpyIter_New |
PyArray_IterAllButAxis | NpyIter_New axes 参数或迭代器标志NPY_ITER_EXTERNAL_LOOP |
PyArray_BroadcastToShape | 不支持(请改用多个操作数的支持) |
PyArrayIter_Check | 需要在 Python 公开中添加此内容 |
PyArray_ITER_RESET | NpyIter_Reset |
PyArray_ITER_NEXT | 从NpyIter_GetIterNext得到的函数指针 |
PyArray_ITER_DATA | NpyIter_GetDataPtrArray |
PyArray_ITER_GOTO | NpyIter_GotoMultiIndex |
PyArray_ITER_GOTO1D | NpyIter_GotoIndex 或 NpyIter_GotoIterIndex |
PyArray_ITER_NOTDONE | iternext 函数指针的返回值 |
多迭代器函数 | |
PyArray_MultiIterNew | NpyIter_MultiNew |
PyArray_MultiIter_RESET | NpyIter_Reset |
PyArray_MultiIter_NEXT | 来自 NpyIter_GetIterNext 的函数指针 |
PyArray_MultiIter_DATA | NpyIter_GetDataPtrArray |
PyArray_MultiIter_NEXTi | 不支持(总是锁步迭代) |
PyArray_MultiIter_GOTO | NpyIter_GotoMultiIndex |
PyArray_MultiIter_GOTO1D | NpyIter_GotoIndex 或 NpyIter_GotoIterIndex |
PyArray_MultiIter_NOTDONE | iternext 函数指针的返回值 |
PyArray_Broadcast | 由 NpyIter_MultiNew 处理 |
PyArray_RemoveSmallest | 迭代器标记 NPY_ITER_EXTERNAL_LOOP |
其他功能 | |
PyArray_ConvertToCommonType | 迭代器标记 NPY_ITER_COMMON_DTYPE |


