这是那些"大多数是出于纯粹的好奇心(可能徒劳地希望我会学到一些东西)"的问题。
我正在调查在大量字符串操作上节省内存的方法,而对于某些场景,似乎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 doc页面所说)和numpy字符串数组显然使用较少的内存,因此对它们进行操作至少可能是更多的CPU缓存 - 友好,字符串数组的性能至少与纯Python相似?
环境:
Python 3.6.3 x64,Linux
numpy的== 1.14.1
答案 0 :(得分:4)
在讨论numpy
时,Vectorized以两种方式使用,而且并不总是清楚它是什么意思。
第二点是使矢量化操作比python中的for循环快得多,而多线程部分使它们比列表理解更快。 当这里的评论者说明矢量化代码更快时,他们也指的是第二种情况。 但是,在numpy文档中,vectorized仅指第一种情况。 这意味着您可以直接在数组上使用函数,而无需遍历所有元素并在每个元素上调用它。 从这个意义上说,它使代码更简洁,但不一定更快。 一些矢量化操作会调用多线程代码,但据我所知,这仅限于线性代数例程。 就个人而言,我更喜欢使用矢量化操作系统,因为我认为它比列表推导更具可读性,即使性能相同。
现在,对于有问题的代码,np.char
的文档(np.core.defchararray
的别名),状态
chararray
类的存在是为了向后兼容 Numarray,不建议用于新开发。从numpy开始 1.4,如果需要字符串数组,建议使用数组dtype
object_
,string_
或unicode_
,并使用免费功能 在numpy.char
模块中进行快速矢量化字符串操作。
因此有四种方法(一种不推荐)来处理numpy中的字符串。 有些测试是有序的,因为每种方式肯定会有不同的优点和缺点。 使用如下定义的数组:
npob = np.array(milstr, dtype=np.object_)
npuni = np.array(milstr, dtype=np.unicode_)
npstr = np.array(milstr, dtype=np.string_)
npchar = npstr.view(np.chararray)
npcharU = npuni.view(np.chararray)
这将使用以下数据类型创建数组(或最后两个的chararrays):
In [68]: npob.dtype
Out[68]: dtype('O')
In [69]: npuni.dtype
Out[69]: dtype('<U10')
In [70]: npstr.dtype
Out[70]: dtype('S10')
In [71]: npchar.dtype
Out[71]: dtype('S10')
In [72]: npcharU.dtype
Out[72]: dtype('<U10')
基准测试在这些数据类型中提供了相当大的性能范围:
%timeit [x.upper() for x in test]
%timeit np.char.upper(test)
# test = milstr
103 ms ± 1.42 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
377 ms ± 3.67 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
# test = npob
110 ms ± 659 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
<error on second test, vectorized operations don't work with object arrays>
# test = npuni
295 ms ± 1.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
323 ms ± 1.05 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
# test = npstr
125 ms ± 2.52 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
125 ms ± 483 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
# test = npchar
663 ms ± 4.94 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
127 ms ± 1.27 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
# test = npcharU
887 ms ± 8.13 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
325 ms ± 3.23 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
令人惊讶的是,使用普通的旧字符串列表仍然是最快的。
当数据类型为string_
或object_
时,Numpy具有竞争力,但一旦包含unicode,性能就会变得更糟。
chararray
是迄今为止最慢,最慢的处理unicode。
应该清楚为什么不建议使用它。
使用unicode字符串会显着降低性能。 docs表示以下这些类型之间的差异
为了向后兼容Python 2,
S
和a
类型字符串保持零终止字节,np.string_继续映射到np.bytes_。要在Python 3中使用实际字符串,请使用U或np.unicode_。对于不需要零终止的有符号字节,可以使用b或i1。
在这种情况下,如果字符集不需要unicode,那么使用更快的string_
类型是有意义的。
如果需要unicode,如果需要其他numpy功能,则可以通过使用列表或类型为object_
的numpy数组来获得更好的性能。
列表可能更好的另一个好例子是appending lots of data
所以,请注意: