Numpy的`str`数据类型及其操作并未经过优化,因此在使用numpy处理字符串时,最好还是坚持使用`object`数据类型。
`str`比`object`占用更多内存
根据固定长度字符串的长度和数组的大小,比例会有所不同,但只要数组中最长的字符串长度超过2个字符,`str`就会占用更多内存(当数组中最长的字符串长度为2个字符时,它们相等)。例如,在下面的示例中,`str`占用的内存几乎是`object`的8倍。
from pympler.asizeof import asizesof
ar1 = np.array(['this is a string', 'string']*1000, dtype=object)
ar2 = np.array(['this is a string', 'string']*1000, dtype=str)
asizeof(ar2) / asizeof(ar1)
str
比object
慢
Numpy的向量化字符串方法没有进行优化,因此在操作object
数组时通常更快。例如,在原始问题中,每个字符重复的示例中,简单的*
(也称为multiply()
)不仅更简洁,而且比char.multiply()
快10倍以上。
import timeit
setup = "import numpy as np; from __main__ import ar1, ar2"
t1 = min(timeit.repeat("ar1*2", setup, number=1000))
t2 = min(timeit.repeat("np.char.multiply(ar2, 2)", setup, number=1000))
t2 / t1
即使对于不能直接应用于数组的函数,与向量化的`char`方法相比,循环遍历`object`数组并处理Python字符串的速度更快。
例如,迭代`object`数组并在每个Python字符串上调用`str.count()`比在`str`数组上使用向量化的`char.count()`快3倍以上。
f1 = lambda: np.array([s.count('i') for s in ar1])
f2 = lambda: np.char.count(ar2, 'i')
setup = "import numpy as np; from __main__ import ar1, ar2, f1, f2, f3"
t3 = min(timeit.repeat("f1()", setup, number=1000))
t4 = min(timeit.repeat("f2()", setup, number=1000))
t4 / t3
顺便说一下,如果涉及到显式循环,遍历列表比遍历numpy数组要快。因此,在前面的例子中,通过遍历列表可以进一步提高性能。
f3 = lambda: np.array([s.count('i') for s in ar1.tolist()])
t5 = min(timeit.repeat("f3()", setup, number=1000))
t3 / t5