在更新字典时加速嵌套的 Python 循环

Posted

技术标签:

【中文标题】在更新字典时加速嵌套的 Python 循环【英文标题】:Speed up a nested Python loop while updating a dictionary 【发布时间】:2021-08-10 07:33:50 【问题描述】:

我有以下 Python 嵌套循环并试图减少它的执行时间。我尝试了一些优化,但没有太大帮助。我想知道是否有人可以提供一些提示,或者是否有任何 Pythonic 方式等。

def(input_list, A, B, threshold):
   a_dict = 
   idx = 0
   for sc, nb in zip(A, B):
       b_dict = 
       for s, n in zip(sc, nb):
           if s >= threshold:
                b_dict.update(init_dict(n, s))
       a_dict[input_list[idx]] = b_dict
       idx += 1 
   return a_dict

A 和 B 都是numpy.ndarray

例如,我尝试的优化之一是避免对 init_dict(n,s) 的函数调用并直接更新 b_dict 而无需调用函数并在其中创建另一个字典,返回它然后更新 b_dict ,这有点帮助。但是是否需要更多优化来避免两个循环,例如使用多处理或线程?

A 是这样的:

 [[0.8921996  0.91602445 0.92908716 0.9417222  0.96200365]
  [0.4753568  0.6385271  0.6559716  0.67830306 0.7077361 ]
  [0.700236   0.75287104 0.7589616  0.7638799  0.77096677]
  ....
 ]

B 是:

 [[682506892 693571174 668887658 303551993  27694382]
  [ 15028940  14862639  54801234  14711873  15136693]
  [567664619 217092797 399261625 124879790 349055820]
  ....
 ]

返回值(a_dict)是这样的:

 
  '147840198': 
   '567664619': 0.7002360224723816, '217092797': 0.752871036529541, 
   '399261625': 0.7589616179466248, '124879790': 0.7638798952102661, 
   '349055820': 0.7709667682647705
   , 
  '485045174': 
   '627320584': 0.24876028299331665, '297801439': 0.3101433217525482, 
   '166126424': 0.3392677307128906, '579653715': 0.3781401515007019, 
   '880315906': 0.40654435753822327
   , 
  '39703998': 
   '273891679': 0.667972981929779, '972073794': 0.8249127864837646, 
   '17236820': 0.8573702573776245, '675493278': 0.8575121164321899, 
   '163042687': 0.8683345317840576
   , 
  '55375077': 
   '14914733': 0.7121858596801758, '28645587': 0.7306985259056091, 
   '14914719': 0.7347514629364014, '15991986': 0.7463902831077576, 
   '14914756': 0.7500130534172058
   ,
   .....
 
 

_init_dict(n,s) 是一个函数,它分别获取 n 和 s 作为键和值,并返回一个字典。正如我之前提到的,不需要这一步,我们可以直接使用 n 和 s 作为 b_dict 的键值对。

threshold 可以是介于 0 和 1 之间的数字,input_list 是字符串列表,如下所示:

 ['147840198', '485045174', '39703998', '55375077', ....]

【问题讨论】:

你想在这里做什么? 我有两个 2D numpy 数组,A 和 B;我想构建一个字典,它的键来自给定的输入列表(使用 idx),它的值是字典,它们的键和值来自 A 和 B 考虑到 A 中的值的阈值。我举了一个例子,对于 A 和问题中的 B 也是如此。 这不是一个好的解释。您应该向我们展示几个样本的输出结果。我怀疑你想要的东西可以使用一次在整个数组上工作的 numpy 操作来有效地完成,但这仍然很难说。 我将返回的响应 a_dict 放在解释中。这是字典的字典(地图)。 我可以看到一个微优化:您可以使用update,而不是使用b_dict[str(n)] = s,但这里可能没有显着的加速。 【参考方案1】:

好的,鉴于 A 中的子列表已排序,这很快就会崩溃。每当您在排序列表中寻找阈值时,循环都是一个BAD 的想法。二分搜索通常是首选武器。

以下是您的代码的几个(逐渐变好的)变体。 chopper3() 将其归结为具有字典理解的 1-liner

from bisect import bisect_left

def chopper(output_keys, A, B, threshold):
    a_dict = 
    for idx, (sc, nb) in enumerate(zip(A, B)):
        b_dict = 
        chop_idx = bisect_left(sc, threshold)
        a_dict[output_keys[idx]] = k:v for k,v in zip(nb[chop_idx:], sc[chop_idx:])
    return a_dict

def chopper2(output_keys, A, B, threshold):
    chop_idx = [bisect_left(a, threshold) for a in A]
    res = output_key: dict(zip(k[chop_idx:], v[chop_idx:])) for 
        output_key, v, k, chop_idx in zip(output_keys, A, B, chop_idx)
    return res
    
def chopper3(output_keys, A, B, threshold):
    return output_key: dict(zip(k[chop_idx:], v[chop_idx:])) 
            for output_key, v, k in zip(output_keys, A, B) 
            for chop_idx in (bisect_left(v, threshold),)


A = [   [0.50, 0.55, 0.70, 0.80],
        [0.61, 0.71, 0.81, 0.91],
        [0.40, 0.41, 0.42, 0.43]]

B = [   [123, 456, 789, 1011],
        [202, 505, 30, 400],
        [90, 80, 70, 600]]

output_keys = list('ABC')

print (chopper(output_keys, A, B, 0.55))
print (chopper2(output_keys, A, B, 0.55))
print (chopper3(output_keys, A, B, 0.55))

产量:

'A': 456: 0.55, 789: 0.7, 1011: 0.8, 'B': 202: 0.61, 505: 0.71, 30: 0.81, 400: 0.91, 'C': 
'A': 456: 0.55, 789: 0.7, 1011: 0.8, 'B': 202: 0.61, 505: 0.71, 30: 0.81, 400: 0.91, 'C': 
'A': 456: 0.55, 789: 0.7, 1011: 0.8, 'B': 202: 0.61, 505: 0.71, 30: 0.81, 400: 0.91, 'C': 
[Finished in 0.0s]

【讨论】:

谢谢!!这很棒。如果我想将内部字典中的键转换为 str 并将其值转换为浮动,我不能使用 chopper3 和 chopper2,但我可以在 chopper1 中完成。 str(k):float(v) for k,v ....您还认为可以在此代码上进行任何并行化吗?例如,将输入(A 和 B)划分到不同的卡盘,然后每个卡盘由不同的线程处理。多处理不会有太大帮助,因为它们都需要更新共享内存位置(输出字典)。你怎么看? 内部值已经是浮点型。如果你想创建内部键 str,我会使用列表推导作为函数的第一行来一次转换它们。 B = [str(b) for b in B]。如果这回答了您的问题,您可以接受答案。祝你好运! B 是一个 list 列表,也将它的每个元素转换为 str 它将处于嵌套循环中。我还必须将键转换为浮点数,因为它们是 float32。再次感谢您的回复,这有助于提高性能。但是我一直在寻找更高的速度,可能使用线程或其他 pythonic 方法。 哦,对了,在 B....我的错。您可以尝试对其进行多线程处理,但列表必须很大才能使其有价值。搏一搏。此外,通过将 B 转换为字符串,您将消耗更多的内存。

以上是关于在更新字典时加速嵌套的 Python 循环的主要内容,如果未能解决你的问题,请参考以下文章

开放式加速器 | Fortran 90:并行化嵌套 DO 循环的最佳方法是啥?

Python - 循环加速 - 大型数据集

如何加速R中的循环计算循环

在 Python 中使用 if 条件加速逐行循环

如何加速这个 Python 循环

如何使用CUDA并行化嵌套for循环以在2D数组上执行计算