Python 尽可能快地从字典中提取数据

Python 尽可能快地从字典中提取数据,python,python-2.7,performance,dictionary,Python,Python 2.7,Performance,Dictionary,我有一本字典d,大约有500个主键(name1,name2,等等)。每个值本身就是一个小字典,有5个键,分别称为ppty1、ppty2,等等),相应的值被浮点转换为字符串 我想比现在更快地提取数据,基于['name1'、'ppty3'、'ppty4']格式的列表(name1可以由任何其他nameX和ppty3和ppty4可以是任何其他pptyX) 在我的应用程序中,我有许多字典,但它们的不同之处仅在于字段ppty1,…,ppty5的值。所有的钥匙都是“静态的”。我不在乎是否有一些初步操作,我只希

我有一本字典
d
,大约有500个主键(
name1
name2
,等等)。每个值本身就是一个小字典,有5个键,分别称为
ppty1
ppty2
,等等),相应的值被浮点转换为字符串

我想比现在更快地提取数据,基于
['name1'、'ppty3'、'ppty4']
格式的列表(
name1
可以由任何其他
nameX
ppty3
ppty4
可以是任何其他
pptyX

在我的应用程序中,我有许多字典,但它们的不同之处仅在于字段
ppty1
,…,
ppty5
的值。所有的钥匙都是“静态的”。我不在乎是否有一些初步操作,我只希望一本词典的处理时间理想情况下比现在快得多。我糟糕的实现,包括在每个字段上循环大约需要3毫秒

下面是生成
d
字段的代码;这只是为了模拟虚拟数据,不需要改进:

import random
random.seed(314)

# build dictionary
def make_small_dict():
    d = {}
    for i in range(5):
        key = "ppty" + str(i)
        d[key] = str(random.random())
    return d

d = {}
for i in range(100):
    d["name" + str(i)] = make_small_dict()

# build fields
def make_row():
    line = ['name' + str(random.randint(0,100))]
    [line.append('ppty' + str(random.randint(0,5))) for i in range(2)]
    return line

fields = [0]*300
for i in range(300):
    fields[i] = [make_row() for j in range(3)]
例如,
字段[0]
返回

[['name420', 'ppty1', 'ppty1'],
 ['name206', 'ppty1', 'ppty2'],
 ['name21', 'ppty2', 'ppty4']]
所以输出的第一行应该是

[[d['name420']['ppty1'], d['name420']['ppty1'],
 [d['name206']['ppty1'], d['name206']['ppty2']],
 [d['name21']['ppty2'], d['name21']['ppty4']]]]
我的解决方案:

start = time.time()
data = [0] * len(fields)
i = 0
for field in fields:
    data2 = [0] * 3
    j = 0
    for row in field:
        lst = [d[row[0]][key] for key in [row[1], row[2]]]
        data2[j] = lst
        j += 1
    data[i] = data2
    i += 1
print time.time() - start
我的主要问题是,如何改进我的代码?还有几个问题:

  • 稍后,我需要执行一些操作,例如列提取,对
    数据的一些条目执行基本操作:您是否建议将提取的值直接存储在np.array中
  • 如何避免多次提取相同的值(
    字段
    有一些冗余行,例如
    ['name1','ppty3','ppty4']
  • 我读到像
    I+=1
    这样的事情需要一点时间,我怎样才能避免呢

这很难理解,所以我开始将位分解成函数。然后,我可以用一个列表来测试这是否有效。它已经更快了,与10000次运行时间相比,它显示此代码运行时间约为原始代码的64%

在本例中,我将所有内容都保存在列表中,以强制执行,因此可以直接比较,但您可以使用生成器或映射,这会将计算推回到实际使用数据的时间

def row_lookup(name, key1, key2):
     return (d[name][key1], d[name][key2]) # Tuple is faster to construct than list

def field_lookup(field):
    return [row_lookup(*row) for row in field]

start = time.time()
result = [field_lookup(field) for field in fields]
print(time.time() - start)
print(data == result)

# without dupes in fields
from itertools import groupby
result = [field_lookup(field) for field, _ in groupby(fields)]
仅将结果分配行更改为:

result = map(field_lookup, fields)
运行时变得可以忽略不计,因为map是一个生成器,所以它实际上不会计算数据,除非您要求它提供结果。这不是一个公平的比较,但是如果您不打算使用所有的数据,您可以节省时间。将函数中的列表理解更改为Generator,您也会从中获得同样的好处。在这种情况下,多处理和异步IO并没有提高性能时间

如果可以更改结构,则可以将字段预处理为仅包含行的列表
[[['namex','pptyx','pptyx']]]
。在这种情况下,您可以将其更改为单列表理解,这样可以将其降低到原始运行时的29%左右,而忽略细化字段的预处理

from itertools import groupby, chain
slim_fields = [row for row, _ in groupby(chain.from_iterable(fields))]
results = [(d[name][key1], d[name][key2]) for name, key1, key2 in slim_fields]

在本例中,结果只是一个包含值的元组列表:
[(value1,value2)]

您真的想“尽快”完成吗?有什么好的理由吗?如果是这样,您可能应该编写一个通过C API访问dict的C扩展。根据dict的负载,手动迭代表存储实际上可能更快(它不是通过公共API公开的,但您可以通过转换指针来实现)。此外,您可能希望将表拆分为块,并在不同的核心上处理每个块。或者你真的不想尽可能快地去做,只是改进你的算法,让它足够快,变得合理?听起来像是过早的优化。口述很快:O(1)快。您没有显示性能测试代码。你为什么需要搜索?你不知道钥匙吗?你在测试中打印出什么了吗?如果这是一个主要的性能杀手-控制台打印需要很长时间。@abarnert所说的“尽可能快”,我的意思是,“使用合理的常用方法尽可能快”,那么你是对的,如果它的速度超过,比如说1ms,那么我会很高兴。我将编辑以澄清这一点。@PatrickArtner“搜索”的意思是提取值。我将很快添加我的测试代码(它不包括打印)。因为您已经在做一些非常有问题的微优化,比如在listcomp中执行
append
调用以产生副作用,一个使代码变得更好的小微优化正在用字符串格式替换所有那些
'name'+str(I)
。例如,在我的笔记本电脑上进行的一次快速测试中,您的版本比
'name%d%%(i,)
长了几个数量级,节省的成本几乎是使用listcomp代替For语句的5倍。(尽管我怀疑其中任何一个在这里都很重要)那么你认为不可能更快吗?通过获取每一行一次(字段有重复的行),并使用以前的“对应表”重新生成结果,必须能够加快一点速度。我不知道怎么做,我会试试。你需要在真实数据上测试,删除重复需要时间,所以可能不会更快。我已经包括了一个删除重复的方法,在测试数据上运行大约70%的时间。要获得更快的速度,请查看Cython或C扩展或生成器,具体取决于数据的使用方式。您需要在真实数据上进行测试,删除重复需要时间,因此可能不会更快。是的,但我可以在一个初步的步骤;正如我试图解释的,只有值会改变,所以我可以在之前准备提取。我知道在哪里可以得到
结果的每个元素。这就是为什么我希望有一个更快的解决方案,利用我想要从中提取的所有字典只在值上不同,而在键上不同这一事实。你需要将数据保持在相同的结构中吗?我补充道