避免软实时分配的优点 NumPy/CPython

Merits of avoiding allocations for soft realtime NumPy/CPython

我读到(软)实时程序经常避免堆分配,部分原因是时间不可预测,尤其是当使用停止世界 (STW) 垃圾收集 (GC) 来释放内存时。我想知道避免堆分配是否有助于减少使用 NumPy 和 CPython 的主循环(例如 100 Hz)中的延迟。我的问题:

CPython uses reference counting for the most part and a STW GC for cyclic references. Does that mean the STW part would never trigger if I don't use any objects with cyclic references? For example, scalars and NumPy arrays don't seem to have cyclic references, and most of them would not go beyond the function in which they are allocated.

只要 Numpy 数组包含原生标量(例如 np.float64np.int32 等),通常就可以了。但是如果 Numpy 数组包含纯 CPython 对象,那么 GC 可能是一个问题(尽管这种情况很少见,因为循环很少见并且 Python 使用分代 GC)。

实际上,在这两种情况下,GC 都可以 运行 一个集合(尤其是当一个新的 CPython 对象是 created/deleted,包括 Numpy 数组时)。但是,对于使用本机类型的 Numpy 数组的程序来说,GC 的开销可以忽略不计,因为引用的数量很小(在这种情况下,垃圾收集器看不到数组的单元格,而不是 Numpy 数组包含的情况纯 CPython 个对象)。

请注意,对于包含纯 CPython 对象的 Numpy 数组,引用循环在理论上是可能的,因为两个数组可以包含彼此的引用:

a = np.empty(1, dtype=object)
b = np.empty(1, dtype=object)
a[0] = b
b[0] = a

请注意,您可以在 Python documentation 中所述的目标用例中禁用 GC。然而,在大多数情况下,它应该不会产生显着差异。

Would reducing array allocations (preallocate, in-place, etc) make a significant difference?

毫无疑问,是的!当你处理许多非常小的 Numpy 数组时,创建一个数组是相当昂贵的(在我的机器上超过 400 ns)。 and 是显示分配 Numpy 数组成本的有趣示例。但是,在大代码中大量应用就地优化之前,您应该检查这是实际问题,因为它明显使代码更难阅读和维护(因此降低了以后应用进一步高级优化的能力)。

Typical NumPy expressions allocate a temporary array for every operation; what are some good ways to get around this?

你可以使用 Numpy 函数的 out 参数不分配新数组(如前面 SO post link 中所见)。请注意,这并不总是可能的。

Only thing that comes to mind now is very tedious Numba rewrites, and even then I'm not sure if non-ufuncs can avoid allocating a temporary array e.g. output[:] = not_a_ufunc(input)

使用 output[:] = numpy_function(...) 之类的指令可能不会有太大帮助,因为它可能会创建一个新的临时数组并执行复制。该副本在大数组上通常很昂贵,但在小数组上通常很便宜(由于 CPU 缓存)。

据我所知,Numba 几乎没有优化分配(除非变量未使用或这是一个微不足道的代码)。但是,Numba 有助于避免创建许多临时数组。更不用说临时数组的创建并不是小型 Numpy 数组的唯一问题:Numpy 调用也非常昂贵(由于许多内部检查和解释器中的 C/Python 上下文切换)并且减少了 Numpy 的数量通话很快就会变得乏味或棘手。