使用NumPy数据类型的Python字典查找速度

时间:2014-07-11 20:02:51

标签: python python-2.7 numpy

背景

我在NumPy数组中有很多数字消息代码,我需要快速将它们转换为字符串。我在性能方面遇到了一些问题,想了解为什么以及如何快速完成。

某些基准

我 - 琐碎的方法

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.int32int的转换类型

由于唯一的其他区别似乎是数据类型(np.int32int),我尝试即时转换类型。这有点愚蠢,因为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 ]

这给出了86毫秒,这已经非常接近本机Python 76毫秒。

结果摘要

  1. dict keys int,使用int(原生Python)编制索引:76 ms
  2. dict keys int,使用int个对象编号(NumPy):86 ms
  3. dict键np.int32,使用np.int32建立索引:177 ms
  4. dict键int,使用np.int32建立索引:758 ms
  5. 问题(S)

    为什么呢?我该怎么做才能尽快使字典查找?我的输入数据是NumPy数组,因此最好(最快但很难看)将dict键转换为np.int32。 (不幸的是,dict键可能分布在很多数字上,因此逐个数组索引不是一个可行的替代方案。但是快10分钟。)

6 个答案:

答案 0 :(得分:5)

正如您所怀疑的那样,int32.__hash__在此处出错,x11与int.__hash__一样慢:

%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类型在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快得多。当然,坚持使用全部int解决方案会更快。


所以我看到它,你有两个选择:

  1. 坚持使用纯int类型,或在dict-lookup
  2. 之前转换为int
  3. 如果最大的代码值不是太大,和/或内存不是问题,你可以用dict-lookup进行列表索引,不需要hash
  4. E.g:

    lookuplist = [None] * (max(lookupdict.keys()) + 1)
    for k,v in lookupdict.items():
        lookuplist[k] = v
    
    res = [ lookuplist[k] for k in arr ] # using list indexing
    

    (编辑:你可能还想在这里试验np.choose

答案 1 :(得分:3)

在我的时间安排中,II - Without NumPyI

慢得多
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

但如果通过在值上设置choice来跳过查找,则会获得更多时间

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.array的迭代比等效列表上的迭代慢

第二 - list(arr)arr.tolist()慢。 list()似乎有2个问题。它本身速度较慢,而且项目为np.int32

答案 2 :(得分:3)

这很有趣,我可能已经找到了我的问题的答案。

备选方案III是将数组转换为列表。如果以正确的方式,这似乎提供了非常好的结果。这样:

res = [ lookupdict[k] for k in list(arr) ]

时钟778毫秒。

但是这个:

res = [ lookupdict[k] for k in arr.tolist() ]

时钟86毫秒。

这背后的技术解释是arr.tolist将数组转换为int个对象,而list(arr)创建np.int32个对象的列表。

答案 3 :(得分:1)

这是使用Pandas的解决方案,它提供了五倍的改进:

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

这是每个元素平均35ns,因此必须在原生Python中无法击败。如果你不熟悉Pandas,那么Series对象就像一个OrderedDict或索引数组,它可以用标准的Python dict构造。 reindex方法提供非常快速的查找;我不知道我怎么不知道幕后发生了什么(我不是一个非常有经验的程序员)但它可能用C或Cython编写。也许您可以查看源代码并为您的问题提出更快速的定制解决方案。最后,values属性只返回Series下面的数组。

编辑: 这是一个纯粹的numpy解决方案,几乎与熊猫一样好:

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

答案 4 :(得分:0)

您在问题中没有考虑过的一个选项,即在某些情况下无法使用的公认有限的选项,是将您的lookupdict转换为数组。在我的机器上,像你的例子这样的小字典很快。

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]

答案 5 :(得分:0)

当您的键是整数时,这似乎是最快的方法。只需做数组索引即可。

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)
lookup_array=np.array([None if i not in lookupdict else lookupdict[i] for i in range(60)])
%timeit lookup_array[arr]
<块引用>

5.3 ms ± 73.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)