使用NumPy数据类型的Python字典查找速度
背景 我在NumPy数组中有很多数字消息代码,我需要快速将它们转换为字符串。我在表现上遇到了一些问题,我想了解为什么以及如何快速完成 一些基准 I-琐碎的方法使用NumPy数据类型的Python字典查找速度,python,python-2.7,numpy,Python,Python 2.7,Numpy,背景 我在NumPy数组中有很多数字消息代码,我需要快速将它们转换为字符串。我在表现上遇到了一些问题,我想了解为什么以及如何快速完成 一些基准 I-琐碎的方法 import numpy as np # dictionary to use as the lookup dictionary lookupdict = { 1: "val1", 2: "val2", 27: "val3", 35: "val4", 59: "val5" } # some t
import numpy as np
# dictionary to use as the lookup dictionary
lookupdict = {
1: "val1",
2: "val2",
27: "val3",
35: "val4",
59: "val5" }
# some test data
arr = np.random.choice(lookupdict.keys(), 1000000)
# create a list of words looked up
res = [ lookupdict[k] for k in arr ]
查字典占了我休息时间的大部分,758毫秒。(我也试过res=map(lookupdict.get,arr)
,但那更糟。)
II-不带NumPy
import random
# dictionary to use as the lookup dictionary
lookupdict = {
1: "val1",
2: "val2",
27: "val3",
35: "val4",
59: "val5" }
# some test data
arr = [ random.choice(lookupdict.keys()) for _ in range(1000000) ]
# create a list of words looked up
res = [ lookupdict[k] for k in arr ]
计时结果变化相当大,达到76毫秒
应该注意的是,我对查找时间感兴趣。随机生成只是为了创建一些测试数据。花不花很多时间都不有趣。这里给出的所有基准测试结果仅用于一百万次查找
III-将NumPy数组转换为列表
我的第一个猜测是,这与列表与数组问题有关。但是,通过修改NumPy版本以使用列表:
res = [ lookupdict[k] for k in list(arr) ]
给我778毫秒,其中大约110毫秒用于转换列表,570毫秒用于查找。因此,查找速度要快一点,但总时间是相同的
IV-从np.int32
到int
的类型转换
由于唯一的区别似乎是数据类型(np.int32
vs.int
),我尝试动态转换类型。这有点愚蠢,因为dict可能也这么做:
res = [ lookupdict[int(k)] for k in arr ]
然而,这似乎做了一些有趣的事情,因为时间降到了266毫秒。似乎几乎但不完全相同的数据类型在字典查找方面玩了一些令人讨厌的把戏,并且dict代码在转换方面不是非常有效
V-字典键转换为np.int32
为了测试这一点,我修改了NumPy版本,在dict键和查找中使用完全相同的数据类型:
import numpy as np
# dictionary to use as the lookup dictionary
lookupdict = {
np.int32(1): "val1",
np.int32(2): "val2",
np.int32(27): "val3",
np.int32(35): "val4",
np.int32(59): "val5" }
# some test data
arr = np.random.choice(lookupdict.keys(), 1000000)
# create a list of words looked up
res = [ lookupdict[k] for k in arr ]
这改善到177毫秒。这不是一个微不足道的改善,但与76毫秒相去甚远
VI-使用int
对象的数组转换
import numpy as np
# dictionary to use as the lookup dictionary
lookupdict = {
1: "val1",
2: "val2",
27: "val3",
35: "val4",
59: "val5" }
# some test data
arr = np.array([ random.choice(lookupdict.keys()) for _ in range(1000000) ],
dtype='object')
# create a list of words looked up
res = [ lookupdict[k] for k in arr ]
这就得到了86ms,这已经非常接近本机python76ms
结果摘要
import random
# dictionary to use as the lookup dictionary
lookupdict = {
1: "val1",
2: "val2",
27: "val3",
35: "val4",
59: "val5" }
# some test data
arr = [ random.choice(lookupdict.keys()) for _ in range(1000000) ]
# create a list of words looked up
res = [ lookupdict[k] for k in arr ]
int
,使用int
索引(本机Python):76毫秒int
,使用int
对象(NumPy)进行索引:86毫秒np.int32
,索引为np.int32
:177毫秒int
,用np.int32
索引:758毫秒为什么??我能做些什么使字典查找尽可能快?我的输入数据是一个NumPy数组,因此目前最好(最快但最难看)的方法是将dict键转换为
np.int32
。(不幸的是,dict键可能分布在广泛的数字范围内,因此逐数组索引不是一个可行的选择。尽管速度很快,10毫秒)。正如您所怀疑的,它是int32。\uuuuu hash\uuuu
的错,它的速度是x11,与int一样慢。\uuuu hash\uuu
:
%timeit hash(5)
10000000 loops, best of 3: 39.2 ns per loop
%timeit hash(np.int32(5))
1000000 loops, best of 3: 444 ns per loop
(int32的int32
类型是用C实现的。如果你真的很好奇,你可以深入源代码,找出它在那里做什么,这需要很长时间)
编辑: 第二个减慢速度的部分是dict查找的隐式比较:
a = np.int32(5)
b = np.int32(5)
%timeit a == b # comparing two int32's
10000000 loops, best of 3: 61.9 ns per loop
%timeit a == 5 # comparing int32 against int -- much slower
100000 loops, best of 3: 2.62 us per loop
这就解释了为什么V比I和IV快得多。当然,坚持使用all-int
解决方案会更快
在我看来,你有两个选择:
int
类型,或在dict查找之前转换为int散列
ing(编辑:您可能还想在这里进行试验)在我的计时中,您的
II-不带NumPy
比I
慢很多
In [11]: timeit [lookupdict[k] for k in np.random.choice(lookupdict.keys(),1000000)]
1 loops, best of 3: 658 ms per loop
In [12]: timeit [lookupdict[k] for k in [np.random.choice(lookupdict.keys()) for _ in range(1000000)]]
1 loops, best of 3: 8.04 s per loop
但是如果通过对值进行选择来跳过查找,您将获得更多的时间
In [34]: timeit np.random.choice(lookupdict.values(),1000000)
10 loops, best of 3: 85.3 ms per loop
好的,让我们关注查找:
In [26]: arr =np.random.choice(lookupdict.keys(),1000000)
In [27]: arrlist=arr.tolist()
In [28]: timeit res = [lookupdict[k] for k in arr]
1 loops, best of 3: 583 ms per loop
In [29]: timeit res = [lookupdict[k] for k in arrlist]
10 loops, best of 3: 120 ms per loop
In [30]: timeit res = [lookupdict[k] for k in list(arr)]
1 loops, best of 3: 675 ms per loop
In [31]: timeit res = [lookupdict[k] for k in arr.tolist()]
10 loops, best of 3: 156 ms per loop
In [32]: timeit res = [k for k in arr]
1 loops, best of 3: 215 ms per loop
In [33]: timeit res = [k for k in arrlist]
10 loops, best of 3: 51.4 ms per loop
In [42]: timeit arr.tolist()
10 loops, best of 3: 33.6 ms per loop
In [43]: timeit list(arr)
1 loops, best of 3: 264 ms per loop
第一次观察-在np.数组上的迭代比在等效列表上的迭代慢
第二个-list(arr)
比arr.tolist()慢。
<代码>列表()
似乎有两个问题。就其本身而言,速度较慢,项目为np.int32
这很有趣,我可能已经找到了我问题的答案
备选方案三是将数组转换为列表。如果采用正确的方法,这似乎可以提供非常好的结果。这:
时钟778毫秒
但这是:
res = [ lookupdict[k] for k in arr.tolist() ]
时钟86毫秒
这背后的技术解释是,arr.tolist
将数组转换为int
对象,而list(arr)
创建了np.int32
对象的列表。您在问题中没有考虑的一个选项,尽管在某些情况下是不可行的公认的有限选项,是将lookupdict转换为数组。在我的机器上,用一个像你这样的小dict,速度非常快
import numpy as np
# dictionary to use as the lookup dictionary
lookupdict = {
1: "val1",
2: "val2",
27: "val3",
35: "val4",
59: "val5" }
# some test data
arr = np.random.choice(lookupdict.keys(), 1000000)
table = np.empty(max(lookupdict.keys()) + 1, dtype='S4')
for key, value in lookupdict.items():
table[key] = value
res = table[arr]
这里有一个使用熊猫的解决方案,它有五个方面的改进:
import numpy as np
import pandas as pd
# dictionary to use as the lookup dictionary
lookupdict = {
1: "val1",
2: "val2",
27: "val3",
35: "val4",
59: "val5" }
# some test data
arr = np.random.choice(lookupdict.keys(), 1000000)
# create a list of words looked up
%timeit res = [ lookupdict[k] for k in arr ]
%timeit res_pd = pd.Series(lookupdict).reindex(arr).values
print all(res == res_pd)
10 loops, best of 3: 192 ms per loop
10 loops, best of 3: 35.3 ms per loop
True
这是每个元素平均35纳秒,因此在本机Python中不可能击败它。如果您不熟悉Pandas,则Series对象类似于OrderedDict或索引数组,可以从标准Python dict构建。方法reindex
提供非常快速的查找;我不知道该怎么做,因为我真的不知道在幕后发生了什么(我不是一个非常有经验的程序员),但它可能是用C或Cython编写的。也许你可以查一下来源,为你的问题想出一个更快的定制解决方案。最后,values属性只返回
import numpy as np
import pandas as pd
# dictionary to use as the lookup dictionary
lookupdict = {
1: "val1",
2: "val2",
27: "val3",
35: "val4",
59: "val5" }
# some test data
arr = np.random.choice(lookupdict.keys(), 1000000)
# create a list of words looked up
%timeit res = [ lookupdict[k] for k in arr ]
%timeit res_pd = pd.Series(lookupdict).reindex(arr).values
print all(res == res_pd)
10 loops, best of 3: 192 ms per loop
10 loops, best of 3: 35.3 ms per loop
True
keys = np.array(lookupdict.keys())
strings = np.array(lookupdict.values())
%timeit res_np = strings[(np.atleast_2d(arr).T == keys).argmax(axis=1)]
10 loops, best of 3: 44.6 ms per loop
print all(res == res_np)
True