为啥 python 不能矢量化 map() 或列表推导

Posted

技术标签:

【中文标题】为啥 python 不能矢量化 map() 或列表推导【英文标题】:Why can't python vectorize map() or list comprehensions为什么 python 不能矢量化 map() 或列表推导 【发布时间】:2019-09-20 21:44:01 【问题描述】:

我对向量化了解不多,但我有兴趣了解为什么像 python 这样的语言不能通过库接口在可迭代对象上提供向量化,就像它提供线程支持一样。我知道许多 numpy 方法都是矢量化的,但是必须使用 numpy 进行通用计算可能会受到限制。

我目前的理解是,即使函数匹配“SIMD”模式,python 也无法向量化函数。例如,理论上不应该任何列表理解或map() 函数的使用是可矢量化的,因为它们输出一个列表,该列表是在输入列表的独立输入上运行相同函数的结果?

以我幼稚的理解,似乎无论何时我使用map(),理论上我都应该能够创建一个表示该功能的指令集;然后输入中的每个元素只需要通过编译的相同函数运行。设计一个提供simd_map(func, iterable) 的工具的技术挑战是什么,它试图“及时”编译func,然后从iterable 获取输入批次,并利用处理器的simd 功能通过@987654327 运行这些批次@?

谢谢!

【问题讨论】:

好的,列表可以包含任何类型的对象,并且它们可以是混合集合的事实 - 这是否考虑在内?列表的灵活性是有代价的——Python 必须依次检查每个对象。如果你有一个数组,它是一个固定的dtype,所以不需要检查(除非它的类型是object Base Python 提供了灵活性。 numpy 用于矢量化计算,numba 用于 JIT。 它可以,如果你可以保证func是一个纯函数。 @roganjosh 当然,但为什么类型管理不只是 func() 的另一部分?我确实知道它的 python 代码仍然比 C 慢,但我仍然没有看到进行 JIT 编译和 SIMD 处理的任何基本问题。 Numba 似乎大致提供了此功能,但调试 Numba 代码似乎也很痛苦,因为您可以在 func() 中为 numba 使用一些允许的调用和对象。他们给出的例子是在制作矢量化函数时不能使用内置的 pandas DF 如果您想要一种设计为与 Python 具有相似表现力但注重运行时性能的语言,我可以推荐 Julia 吗? Python 不是设计(CPython 也没有实现)用于数值计算的——它后来被硬塞进了这个角色,在被采用的原因与任何类型的设计关注点无关之后运行时性能——它仅适用于将第 3 方插件置于顶部的目的......这意味着您需要在代码中实际使用那些第 3 方插件才能获得它们的好处。 【参考方案1】:

map 应用的操作是任意 Python 代码。 CPython 是解释器,而不是 JIT 编译器。

CPython 可能可能有一些固定的 C 函数(提前编译为解释器的一部分)用于数组上的 SIMD 操作,但它不是 AFAIK。即便如此,它必须将提供的func 优化到它可以进行模式识别的东西,以注意到它是例如正在做a[i] = max(a[i], some_value)

但通常 CPython 不会 这样做;解释器开销是循环数组元素的一个大问题。 CPython 远没有接近原生标量循环的性能,因此即使没有自动矢量化,也有巨大的收益空间。 像 IIRC 慢 200 倍的因素。例如Why are bitwise operators slower than multiplication/division/modulo? 表明,某些操作甚至没有小整数的“快速路径”,而且这种开销足以使 & 比内部使用硬件除法指令的 // 慢。

此外,Python 列表不会存储为简单的连续数组 int32_tdouble,因此 CPU SIMD 无论如何都不是高效的。这就是 numpy 数组特别的原因:它们确实像原始类型的 C 数组一样存储值。


(警告:我几乎不知道 Python 并且不经常使用它。但我认为我知道的足够多,因此这个答案是正确的:它是一个用 C 编写的解释器,不会执行任何即时生成本机机器代码。唯一可以运行的本机循环是作为解释器的一部分或在 NumPy 库中预编译的循环。)

【讨论】:

【参考方案2】:

您希望矢量化或 JIT 编译使用 numba、pypy 或 cython,但请注意速度是以牺牲灵活性为代价的。

numba 是一个 python 模块,它会为你 jit 编译某些函数,但它不支持某些(许多)python 结构上的多种输入和 barfs。它在工作时非常快,但很难解决。它也非常适合使用 numpy 数组。

pypy 完全替代了作为 JIT 的 cpython 解释器。它支持整个 python 规范,但不能很好地与扩展集成,因此某些库将无法工作。

cython 是 python 的扩展,它编译成二进制文件,其行为类似于 python 模块。但是,它确实需要您使用特殊语法来利用速度增益,并且需要您将事物显式声明为 ctypes 才能真正获得任何优势。

我的建议是: 如果您是纯 python,请使用 pypy。 (如果它适合你,它基本上是毫不费力的) 如果您需要加速 numpy 没有好的方法的数字计算,请使用 numba。 如果您需要速度而其他两个不起作用,请使用 cython。

【讨论】:

“为什么 python 不能矢量化 map() 或列表推导”。这到底是怎么回答的? @roganjosh:这个答案基本上是在说“使用 CPython 以外的语言实现时可能可以”。该问题询问 Python(语言),并且仅隐含地询问 CPython(默认/标准实现)。被推荐为对我关于 CPython 的回答的有用补充,特别是对于任何对实际结果感兴趣的人。 @PeterCordes, ...作为一个确实相当了解 Python 的人,我认为这不是一个有用的答案。是的,它涉及允许 Python 更好的运行时性能的插件或替代运行时实现,但它并没有直接涉及 任何 向量化列表推导或 map() 函数输出的能力,这是问的具体问题。 @CharlesDuffy:好点。 PyPy 依赖于 JIT 编译,大多数 JIT 编译器没有时间寻找 SIMD 的自动向量化。例如Java 的 HotSpot JIT 需要数年时间才能获得任何 SIMD,然后仅用于简单的循环。 Do any JVM's JIT compilers generate code that uses vectorized floating point instructions? Ahead-of-time 编译器可以花更多时间进行优化。

以上是关于为啥 python 不能矢量化 map() 或列表推导的主要内容,如果未能解决你的问题,请参考以下文章

为什么内置函数abs()不能用于Python列表,但却能正确地用于NumPy数组和pandas数列(因为它会被向量化)?

为啥MFC只能用位图不能用png图?

为啥我不能在 python 中使用列表作为 dict 键?

简单的 Python 问题:为啥我不能将变量分配给排序列表(就地)? [复制]

Python 为啥list不能作为字典的key

python怎么构建hash map