【问题标题】:How to parallelize this query in Python (just like PLINQ)?如何在 Python 中并行化这个查询(就像 PLINQ 一样)?
【发布时间】:2018-04-08 11:05:46
【问题描述】:

我在 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 操作的等价物,所以我仍然想念如何在Python 中并行实现DistinctOrderByDescending 操作.

是否可以使用内置库来做到这一点,或者是否有常用的 3rd 方库来做到这一点?

谢谢!

【问题讨论】:

    标签: c# python multithreading python-multithreading plinq


    【解决方案1】:

    您当前方法的问题主要基于for 循环内的words.count(word)。这意味着您为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)
    

    【讨论】:

    • 谢谢,Counter 课程非常适合解决这个问题!我不明白您发布的代码,我的意思是,您为什么在包含重复项的原始 search_list 上调用 sorted?在freq 对象上调用sorted 不是更好吗(因为可以迭代字典),然后在该排序列表上调用enumerate?毕竟,我只需要按降序迭代这些独特的单词,我不确定你为什么要创建这两个额外的 new_listchecked 变量。
    • @Sergio0694 好问题!首先看到this...然后我研究了有序集的实现,它增加了很多我认为没有必要的复杂性。最新版本的 Python 保留了作为实现点的顺序,而不是依赖的东西......现在(3.6 / 3.7)版本中正在发生变化
    • 我不是在谈论字典的排序顺序(作为字典,根据定义,没有特定的键顺序),我的意思是:你为什么不直接打电话给@ 987654341@ 而不是迭代原始列表,然后稍后创建这两个额外的列表/集? sorted返回的排序后的列表应该已经是我要找的了(不分字典的具体顺序,看你说的具体实现了)吧?
    • @Sergio0694 我认为您可能是正确的,我现在无法在 2.7 中进行测试。在我可以确定之前,我仍然需要从字典中生成一个键列表,然后对其进行排序,所以我不确定你会获得多少。在我可以测试之前,我不愿意更新答案。
    • @Sergio0694 实际上我看不出它在 P 2.7 中会有什么不同,所以我会用时间更新答案。感谢您指出。
    猜你喜欢
    • 1970-01-01
    • 2016-11-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多