import numba
from numba import prange
from numba.typed import Dict, List
@numba.njit(parallel=True)
def create_list_of_dicts(input: List[int]):
output = List()
for i in prange(len(input)):
output.append((Dict([(1, 2)]), input[i]))
return output
len(create_list_of_dicts([i for i in range(1000)]))
导致我的 Python 解释器崩溃
double free or corruption (!prev)
Aborted (core dumped)
我认为这是因为List.append
不是线程安全的。有解决方法吗?
预填充列表
output = [None] * len(input)
然后通过分配给条目
output[i] = ...
给出
No implementation of function Function(<built-in function setitem>) found for signature:
>>> setitem(list(none)<iv=None>, int64, Tuple(DictType[int64,int64]<iv=None>, int64))
Numba 不会检查代码是否可以安全地并行执行(这实际上很难做到,在某些情况下如果没有额外的限制根本不可能)。因此,这是您的责任。这里,由于列表上的共享访问而存在竞争条件。这会导致未定义的行为,并且实际上会导致崩溃。
没有有效的方法可以直接并行地将项目附加到列表中。通常的解决方案包括使用关键部分(这是非常低效的,并且会阻止任何并行性,因此它在这里毫无用处),或者构建本地列表,然后合并结果(这在这里显然是次优的。最好的解决方案确实是预分配当您知道其最终大小时的列表(此处是可能的)。
None
Numba 抱怨是因为您用 的项目填充了列表none
。由于Numba 列表始终是键入的,而本机类型不包含此 None 类型,因此会导致键入错误。理论上,您可以向 Numba 指定该类型可以是none
另一个特定的给定类型(带有该optional
类型),但这通常会使访问更加复杂,可能效率较低,这里不需要这样做。让我们首先尝试创建具有默认值的列表。构建一个没有可选类型的列表
我们需要直接使用正确的类型构建列表。事实上,
output = List()
它是无效的,因为目标列表没有与您想要的类型匹配的类型。话虽这么说,目标类型并不那么简单,而且有一个问题:使用正确的类型,创建具有预定义大小的列表会强制您创建/初始化所有项目,因此字典对象尽管它们都可以引用相同的对象对象以提高初始化性能。这是生成的代码:坏消息是代码并不更快。乍一看,似乎大部分时间还是花在了列表的创建上。然而,分析信息往往表明问题来自于输出从类型化列表到反射列表的转换。这个问题与上一篇文章类似。因此,只有当您在循环中进行一些昂贵的计算时,此解决方案才有用。
这个问题可以通过显式转换
output
为List
. 所以你只需要替换return output
为return nb.typed.List(output)
. 在这种情况下,列表的类型是自动推断的。一旦修复,此实现比最初使用的速度要快得多,append
因为append
速度很慢。使用并行循环并不会显着缩短执行时间。这是因为分配无法扩展,而且还因为与创建线程、分配工作等所需的时间相比,并行循环太快。如果输入范围是 10_000,那么我可以看到使用多个线程的好处(50 -60% 加速)尽管由于分配问题而很小,据我所知这里无法轻松解决。构建具有可选类型的列表
到目前为止,我尚未成功构建具有可选类型的项目列表。列表显然尚不支持。事实上,下面的代码表明:
以下是具有可选类型的列表报告的错误:
请注意,仅当该行不在函数中时才会报告此明显错误。否则,Numba 会抛出一个明显更复杂的错误,但想法是相同的:当前缺少一些要实现的功能。
理论上,也应该可以用代数类型实现可选类型,但看起来 Numba 不支持它。至少,我在文档中没有找到任何相关参考,并且我没有成功地使其适用于
Any
迄今为止的类型。讨论
对于像这样的高级用法,我认为您已经达到了 Numba 的极限,并且它不是正确的工具。事实上,这不仅仅是一个问题,而是几个问题:
也许像Awkward (支持 Numba)这样的模块可以帮助解决一些问题,这对您来说可能就足够了,但我建议您使用较低级的语言(例如 C++ 或 Rust)。
如果您确实需要将输出类型作为包含字典的元组列表返回,那么请注意,创建此数据结构将相当昂贵(无论使用什么方法来执行此操作)。您可以将其包装在不透明类型中,以避免转换,但读取/写入数据结构中的项目将更加麻烦且效率较低。
笔记
请注意,我使用过
int64
,但在您的情况下使用较小的类型可能会更好(较小的类型占用的内存较少,因此它们也有利于性能)。另请注意,如果 Numba 使用反射列表,则循环将无法使用 Numba 线程并行化操作,因为所有 CPython 对象都需要受 GIL 保护,并且 GIL 在 Numba 并行循环中被禁用,因此没有 Python必须在 Numba 并行循环中访问对象。即使这是可能的,GIL 也会阻止任何实际的并行性。更不用说由于进程间通信和酸洗,多处理在这里效率不高。
请注意,据我所知 Windows 和 Linux 使用不同的整数默认类型。Windows 默认情况下倾向于使用 32 位整数,而 Linux 上倾向于使用 64 位整数。这就是我在代码中指定类型的原因:以避免警告和类型转换错误。