【问题标题】:Parallelize this nested for loop in python在python中并行化这个嵌套的for循环
【发布时间】:2015-03-24 00:38:41
【问题描述】:

我再次努力改善这段代码的执行时间。由于计算非常耗时,我认为最好的解决方案是并行化代码。

我首先按照this 问题中的说明使用地图,但后来我尝试了一种更简单的方法,认为我可以找到更好的解决方案。但是我还没有想出任何东西,所以由于这是一个不同的问题,我决定将它作为一个新问题发布。

我正在使用 Python 3.4 在 Windows 平台上工作。

代码如下:

similarity_matrix = [[0 for x in range(word_count)] for x in range(word_count)]
for i in range(0, word_count):
    for j in range(0, word_count):
        if i > j:
            similarity = calculate_similarity(t_matrix[i], t_matrix[j])
            similarity_matrix[i][j] = similarity
            similarity_matrix[j][i] = similarity

这是calculate_similarity 函数:

def calculate_similarity(array_word1, array_word2):
      denominator = sum([array_word1[i] + array_word2[i] for i in range(word_count)])
      if denominator == 0:
          return 0
      numerator = sum([2 * min(array_word1[i], array_word2[i]) for i in range(word_count)])
      return numerator / denominator

以及对代码的解释:

  • word_count 是列表中存储的唯一单词的总数
  • t_matrix 是一个矩阵,其中包含每对单词的值
  • 输出应为similarity_matrix,其维度为word_count x word_count,还包含每对单词的相似度值
  • 可以将两个矩阵都保存在内存中
  • 经过这些计算后,我可以轻松找到每个单词最相似的单词(或前三个相似的单词,根据任务可能需要)
  • calculate_similarity 接受两个浮点列表,每个列表代表一个单独的单词(每个都是 t_matrix 中的一行)

我使用 13k 个单词的列表,如果我计算正确,我的系统上的执行时间将是几天。所以,任何能在一天内完成这项工作的东西都会很棒!

也许只在calculate_similarity 中并行化numeratordenominator 的计算会带来显着的改进。

【问题讨论】:

  • 作为风格问题,您可以通过将第二个循环中的范围更改为第一个循环的i 来迭代“三角形”而不是“正方形”。这样你不会得到太多的性能提升,但你会减少一层嵌套.. :)
  • 你能用不同的语言吗?也许是 c++ 和 openmp?
  • @wim 你的意思是for j in range(i, word_count): ?我已经尝试过了,但它几乎没有改变。 @Coder Hacker 如果我真的没有其他选择,我会翻译我们到目前为止编写的代码。你认为用 C++ 做这件事会很容易吗?
  • @jmunsch:您的代码更快,因为它实际上并没有分配尽可能多的列表。如果您向其中一个内部列表添加一个值,您也会在所有副本中看到它,这可能是不可取的。
  • 看看@Blckknght 的解决方案,它比我的有很大的改进。此外,您应该从calculate_similarity 的总和中删除方括号。例如denominator = sum(array_word1[i] + array_word2[i] for i in range(word_count))。在此处使用 generator 而不是列表推导式,可以避免您构建列表并存储大量值来总结它们并丢弃列表。

标签: python parallel-processing multiprocessing python-multithreading python-multiprocessing


【解决方案1】:

这是与Matt's answer 中相同的通用算​​法的另一种实现,只是使用multiprocessing.Pool 而不是concurrent.futures.ProcessPoolExecutor。它可能比他的代码更高效,因为输入 (t_matrix) 的值只被序列化一次并传递给每个工作进程中的initializer 函数。

import multiprocessing
import itertools

def worker_init(matrix):
    global worker_matrix
    worker_matrix = matrix

def worker(i, j):
    similarity = calculate_similarity(worker_matrix[i], worker_matrix[j])
    return i, j, similarity

def main(matrix):
    size = len(matrix)
    result = [[0]*size for _ in range(size)]
    with multiprocessing.Pool(initializer=worker_init, initargs=(matrix,)) as pool:
        for i, j, val in pool.starmap(worker, itertools.combinations(range(size), 2)):
            result[i][j] = result[j][i] = val
    return result

if __name__ == "__main__":
    # get t_matrix from somewhere
    main(t_matrix)

【讨论】:

  • result = [[0]*size for _ in size] 应该是result = [[0]*size for _ in range(size)]
  • @Matt:谢谢,你是对的。我在编码过程的后期排除了len(matrix) 计算,最终在那个地方削减了太多。
  • 此方案在计算结果时会导致内存错误。我不确定内存有什么不同。
【解决方案2】:
from concurrent.futures import ProcessPoolExecutor, Future, wait
from itertools import combinations
from functools import partial

similarity_matrix = [[0]*word_count for _ in range(word_count)]

def callback(i, j, future):
    similarity_matrix[i][j] = future.result()
    similarity_matrix[j][i] = future.result()

with ProcessPoolExecutor(max_workers=4) as executer:
    fs = []
    for i, j in combinations(range(wordcount), 2):
        future = excuter.submit(
                    calculate_similarity, 
                    t_matrix[i], 
                    t_matrix[j])

        future.add_done_callback(partial(callback, i, j))
        fs.append(future)

    wait(fs)

【讨论】:

  • 代码运行良好,但我再次用 350 字测试它,但执行时间从 18 秒增加到 40 秒。我也尝试过使用 ThreadPoolExecutor,但正如我在任务管理器中看到的那样,它似乎不是并行运行的,执行时间约为 25 秒。
  • 我猜想减速是由于重复序列化 t_matrix[i]t_matrix[j] 项目的开销。我知道使用multiprocessing.Pool,您可以编写一个函数来为工作进程进行一次性设置,但我不确定您是否可以为ProcessPoolExecutor 做同样的事情。
  • @Blckknght concurrent.futures.ProcessPoolExecutor 目前不支持 initializer 关键字参数。有 a bug 对此提出异议,并提供了一个工作补丁来增加对它的支持,但它仍在等待审查。
【解决方案3】:

对于如此大量的数据,您使用了许多列表推导。我强烈推荐numpy 模块。 如果这是一个选项,您可以这样做:

import numpy as np
import itertools

t = np.array(t_matrix)

s = np.sum(t,axis=1)

denom = s[:,None] + s[None,:]
num = np.zeros((word_count,word_count))

for i,j in itertools.product(range(word_count),repeat=2):
    num[i,j] = np.where(t[i] <= t[j], t[i], t[j]).sum()

similarity_matrix = np.where(denom != 0.0, 2.*num/denom, 0 )

【讨论】:

  • 仍有改进的方法......但首先我会感兴趣它是否给出与您的代码相同的结果以及它有多快
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-20
  • 1970-01-01
相关资源
最近更新 更多