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扩展或生成器,具体取决于数据的使用方式。您需要在真实数据上进行测试,删除重复需要时间,因此可能不会更快。是的,但我可以在一个初步的步骤;正如我试图解释的,只有值会改变,所以我可以在之前准备提取。我知道在哪里可以得到结果的每个元素。这就是为什么我希望有一个更快的解决方案,利用我想要从中提取的所有字典只在值上不同,而在键上不同这一事实。你需要将数据保持在相同的结构中吗?我补充道