如何在Python中并行化这个查询(就像PLINQ一样)?

时间:2018-04-08 11:05:46

标签: c# python multithreading python-multithreading plinq

我在使用Python编写的查询时遇到了一些问题(必须将它用于TensorFlow),由于输入数据集非常大,因此效果很好但速度太慢。查询完成可能需要5分钟以上,检查任务管理器我可以确认它确实在单核上运行。

以下是代码:

# Assume words is a list of strings
for i, pair in enumerate(sorted(
    ((word, words.count(word))          # Map each word to itself and its count
        for word in set(words)),        # Set of unique words (remove duplicates)
    key=lambda p: p[1],                 # Order by the frequency of each word
    reverse=True)):                     # Descending order - less frequent words last

    # Do stuff with each sorted pair

我在这里做的只是获取输入列表words,删除重复项,然后根据输入文本中的频率按降序对单词进行排序。

如果我使用PLINQ在C#中写这个,我会做这样的事情:

var query = words.AsParallel().Distinct()
            .OrderByDescending(w => words.Count(s => s.Equals(w)))
            .Select((w, i) => (w, i));

我找不到使用可能的内置库重写Python中的并行实现的简单方法。我看到了一些关于Pool扩展名的指南,但看起来它只是并行Select操作的等价物,所以我仍然想念如何实现Distinct和{{1 Python中的操作,并行。

是否可以使用内置库执行此操作,或者是否有常用的第三方库来执行此操作?

谢谢!

1 个答案:

答案 0 :(得分:0)

您当前方法的问题主要基于words.count(word)循环内的for。这意味着您为set(words)中的每个唯一单词迭代整个列表,并且只计算一个单词...相反,您可以使用Counter并对列表进行单次传递。 Counter对象是一个字典,您可以将其用于排序中的键,频率查找为O(1)。即使在我的例子中有1000个“单词”,加速也是戏剧性的...对于更长的输入,我感到无聊等待timeit完成:)

import string
from collections import Counter
import numpy as np # Just to create fake data

# Create some fake data
letters = list(string.ascii_lowercase)
new_words = [''.join(list(np.random.choice(letters, 3, replace=True))) 
             for x in range(1000)]


def original(search_list):
    """ Your current approach """
    for i, pair in enumerate(sorted(
    ((word, search_list.count(word)) 
        for word in set(search_list)),
    key=lambda p: p[1],
    reverse=True)):    
        pass


def new_approach(search_list):
    freq = Counter(search_list)
    search_list = sorted(search_list, key=lambda x: freq[x], reverse=True)
    new_list = []
    checked = set()
    for item in search_list:
        if item not in checked:
            new_list.append(item)
            checked.add(item)

获取1000个“单词”的列表:

%timeit original(new_words)
26.6 ms ± 289 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)


%timeit new_approach(new_words)
833 µs ± 30 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

在尝试使用multiprocessing之类的内容之前,您应该先了解这种新方法是否适合您的需求,因为这可能会增加额外的代码复杂性,而这在修复时间复杂度问题后是不必要的。

编辑:

正如OP所指出的,我们可以跳过中间列表并通过简单地对Counter对象进行排序来设置:

def new_approach(search_list):
    freq = Counter(search_list)
    search_list = enumerate(sorted(freq, key=lambda x: freq[x], reverse=True))

新时间:

%timeit new_approach(new_words)
438 µs ± 6.31 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)