Python,使用多进程比不使用它慢

发布于 2021-01-29 15:25:33

在花了很多时间试图将注意力集中在多处理上之后,我想到了以下代码,这是一个基准测试:

范例1:

from multiprocessing  import Process

class Alter(Process):
    def __init__(self, word):
        Process.__init__(self)
        self.word = word
        self.word2 = ''

    def run(self):
        # Alter string + test processing speed
        for i in range(80000):
            self.word2 = self.word2 + self.word

if __name__=='__main__':
    # Send a string to be altered
    thread1 = Alter('foo')
    thread2 = Alter('bar')
    thread1.start()
    thread2.start()

    # wait for both to finish

    thread1.join()
    thread2.join()

    print(thread1.word2)
    print(thread2.word2)

这将在2秒内完成(多线程时间的一半)。出于好奇,我决定接下来运行此命令:

范例2:

word2 = 'foo'
word3 = 'bar'

word = 'foo'
for i in range(80000):
    word2 = word2 + word

word  = 'bar'
for i in range(80000):
    word3 = word3 + word

print(word2)
print(word3)

令我震惊的是,不到半秒就完成了!

这里发生了什么?我期望多处理运行得更快-鉴于示例1被拆分为两个进程,示例2的时间是否应该完成一半?

更新:

考虑了Chris的反馈后,我包括了消耗最多处理时间的“实际”代码,并引导我考虑进行多处理:

self.ListVar = [[13379+ strings],[13379+ strings],
                [13379+ strings],[13379+ strings]]

for b in range(len(self.ListVar)):
    self.list1 = []
    self.temp = []
    for n in range(len(self.ListVar[b])):
        if not self.ListVar[b][n] in self.temp:
            self.list1.insert(n, self.ListVar[b][n] + '(' + 
                              str(self.ListVar[b].count(self.ListVar[b][n])) +
                              ')')
           self.temp.insert(0, self.ListVar[b][n])

   self.ListVar[b] = list(self.list1)
关注者
0
被浏览
141
1 个回答
  • 面试哥
    面试哥 2021-01-29
    为面试而生,有面试问题,就找面试哥。

    预计到达时间:现在您已经发布了代码,我可以告诉您有一种简单的方法可以更快地完成您正在做的事情(快100倍以上)。

    我看到您正在执行的操作是在字符串列表中的每个项目的括号中添加一个频率。不必每次都计算所有元素(您可以使用cProfile确认这是迄今为止代码中最大的瓶颈),您只需创建一个字典即可将每个元素映射到其频率。这样,您只需要遍历该列表两次-
    一次创建频率字典,一次使用它添加频率。

    在这里,我将展示我的新方法,对其进行计时,并使用生成的测试用例将其与旧方法进行比较。测试用例甚至表明新结果与旧结果 完全相同注意:
    下面您真正需要注意的就是new_method。

    import random
    import time
    import collections
    import cProfile
    
    LIST_LEN = 14000
    
    def timefunc(f):
        t = time.time()
        f()
        return time.time() - t
    
    
    def random_string(length=3):
        """Return a random string of given length"""
        return "".join([chr(random.randint(65, 90)) for i in range(length)])
    
    
    class Profiler:
        def __init__(self):
            self.original = [[random_string() for i in range(LIST_LEN)]
                                for j in range(4)]
    
        def old_method(self):
            self.ListVar = self.original[:]
            for b in range(len(self.ListVar)):
                self.list1 = []
                self.temp = []
                for n in range(len(self.ListVar[b])):
                    if not self.ListVar[b][n] in self.temp:
                        self.list1.insert(n, self.ListVar[b][n] + '(' +    str(self.ListVar[b].count(self.ListVar[b][n])) + ')')
                        self.temp.insert(0, self.ListVar[b][n])
    
                self.ListVar[b] = list(self.list1)
            return self.ListVar
    
        def new_method(self):
            self.ListVar = self.original[:]
            for i, inner_lst in enumerate(self.ListVar):
                freq_dict = collections.defaultdict(int)
                # create frequency dictionary
                for e in inner_lst:
                    freq_dict[e] += 1
                temp = set()
                ret = []
                for e in inner_lst:
                    if e not in temp:
                        ret.append(e + '(' + str(freq_dict[e]) + ')')
                        temp.add(e)
                self.ListVar[i] = ret
            return self.ListVar
    
        def time_and_confirm(self):
            """
            Time the old and new methods, and confirm they return the same value
            """
            time_a = time.time()
            l1 = self.old_method()
            time_b = time.time()
            l2 = self.new_method()
            time_c = time.time()
    
            # confirm that the two are the same
            assert l1 == l2, "The old and new methods don't return the same value"
    
            return time_b - time_a, time_c - time_b
    
    p = Profiler()
    print p.time_and_confirm()
    

    当我运行此命令时,它得到的时间为(15.963812112808228,0.05961179733276367),这意味着它快了250倍,尽管这一优势取决于列表的时长和每个列表中的频率分布。我相信您会同意,凭借这种速度优势,您可能不需要使用多处理功能:)

    (我的原始答案留在后头,以供后代参考)

    ETA:顺便说一句,值得注意的是,该算法在列表长度上大致是线性的,而您使用的代码是二次的。这意味着,元素数量越多,它的优势就越明显。例如,如果将每个列表的长度增加到1000000,则只需要5秒钟即可运行。根据推断,旧代码将花费一天的时间:)


    这取决于您正在执行的操作。例如:

    import time
    NUM_RANGE = 100000000
    
    from multiprocessing  import Process
    
    def timefunc(f):
        t = time.time()
        f()
        return time.time() - t
    
    def multi():
        class MultiProcess(Process):
            def __init__(self):
                Process.__init__(self)
    
            def run(self):
                # Alter string + test processing speed
                for i in xrange(NUM_RANGE):
                    a = 20 * 20
    
        thread1 = MultiProcess()
        thread2 = MultiProcess()
        thread1.start()
        thread2.start()
        thread1.join()
        thread2.join()
    
    def single():
        for i in xrange(NUM_RANGE):
            a = 20 * 20
    
        for i in xrange(NUM_RANGE):
            a = 20 * 20
    
    print timefunc(multi) / timefunc(single)
    

    在我的机器上,多进程操作仅占单线程操作时间的60%。



知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看