【发布时间】:2026-02-03 22:10:01
【问题描述】:
这是那些“主要是出于纯粹的好奇心(可能是徒劳的希望我能学到一些东西)”的问题。
我正在研究在大量字符串的操作上节省内存的方法,对于一些场景,string operations in numpy 似乎很有用。然而,我得到了一些令人惊讶的结果:
import random
import string
milstr = [''.join(random.choices(string.ascii_letters, k=10)) for _ in range(1000000)]
npmstr = np.array(milstr, dtype=np.dtype(np.unicode_, 1000000))
使用memory_profiler的内存消耗:
%memit [x.upper() for x in milstr]
peak memory: 420.96 MiB, increment: 61.02 MiB
%memit np.core.defchararray.upper(npmstr)
peak memory: 391.48 MiB, increment: 31.52 MiB
到目前为止,一切都很好;但是,计时结果让我感到惊讶:
%timeit [x.upper() for x in milstr]
129 ms ± 926 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit np.core.defchararray.upper(npmstr)
373 ms ± 2.36 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
这是为什么呢?我预计,由于 Numpy 对其数组使用连续的内存块,并且它的操作是矢量化的(如上面的 numpy 文档页面所述),并且 numpy 字符串数组显然使用更少的内存,因此对它们的操作至少应该是更多的 CPU 缓存-友好,字符串数组的性能至少与纯 Python 中的性能相似?
环境:
Python 3.6.3 x64,Linux
numpy==1.14.1
【问题讨论】:
-
评论:我通常怀疑在测试和证明之前操作被“矢量化”的说法。 2 个示例:
np.vectorize(比map替换稍多)和pd.Series.str(源代码只是一个循环的lambda)。 -
看看source code(函数
_vec_string,_vec_string_no_args,...)似乎没有矢量化魔法,只是循环(不确定你可以矢量化字符串操作反正)。我不认为字符串连续存储在数组中,因为它们可以有不同的大小,我猜它们的引用是。我想列表推导的开销比创建 NumPy 数组的开销要小,而且字符串操作本身也需要大约相同的时间。 -
时间表现似乎取决于 Python 的版本。这可能是针对特定版本的优化。
-
字符串存储在连续的数组数据缓冲区中,如果需要,可以使用填充。但是 numpy 使用 Python 字符串方法来处理它们。它没有实现自己编译的字符代码,也就是我们通常所说的真向量化。
defchararray对于已经是字符串 dtype 的数组来说是一种便利,而不是处理字符串列表的替代品。从文档中,“所有这些都基于 Python 标准库中的字符串方法” -
@hpaulj - 好像是答案 ;)
标签: python numpy benchmarking