减少巨大的列表生成的执行时间

时间:2016-06-17 20:50:12

标签: python multithreading python-3.5 execution-time

我对Python很新,我正在尝试编写一些巨大的列表(里面有随机字母)。实际上,我的机器需要大约75-80秒才能获得2,000,000行。

import timeit
import random, string

global_tab     = []
global_nb_loop = 2000000

print("Generate %d lines" % global_nb_loop)
global_tab = []
for x in range(global_nb_loop):
    global_tab.append(("".join( [random.choice(string.ascii_letters) for i in range(15)] ), "".join( [random.choice(string.digits) for i in range(2)])))
print("%d lines generated" % len(global_tab))

使用linux time命令的结果:

$ time python3 DEV/PyETL/generateList.py 
Generate 2000000 lines
2000000 lines generated

real    1m16.844s
user    1m16.609s
sys 0m0.203s

在监控只有1个核心为100%的系统资源时,我感到很惊讶,而不是像我在其上测试过的Windows机器上那样4个。

当然我已尝试应用一些线程,但我遇到了一个问题:它比在单核上运行需要更多时间。也许线程不是解决方案,或者我可能错误地使用它们。

这是新代码:

import random, string
import threading

global_tab         = []
global_nb_threads  = 4
global_nb_loop     = 2000000


threadLock         = threading.Lock()

class generateList(threading.Thread):
    def __init__(self, name):
        threading.Thread.__init__(self)
        self.name = name

    def run(self):
        global global_tab
        self.tab = []

        print("[%s] Generate %d lines" % (self.name, int(global_nb_loop/global_nb_threads)))
        # divide desirated lines with number of threads
        for x in range(int(global_nb_loop/global_nb_threads)):
            self.tab.append(("".join( [random.choice(string.ascii_letters) for i in range(15)] ), "".join( [random.choice(string.digits) for i in range(2)])))

        threadLock.acquire()
        global_tab += self.tab
        threadLock.release()
        del self.tab
        print("[%s] %d lines in list" % (self.name, len(global_tab)))


for i in range(global_nb_threads):
    # Create threads
    t = generateList("Thread-" + str(i))
    # Start
    t.start()

for i in range(global_nb_threads):
    # Wait for threads end
    t.join()

执行:

$ time python3 DEV/PyETL/generateListThreads.py 
[Thread-0] Generate 500000 lines
[Thread-1] Generate 500000 lines
[Thread-2] Generate 500000 lines
[Thread-3] Generate 500000 lines
[Thread-3] 500000 lines in list
[Thread-0] 1000000 lines in list
[Thread-2] 1500000 lines in list
[Thread-1] 2000000 lines in list    
real    1m40.858s
user    1m41.208s
sys 0m0.916s

超过1个核心32秒,100%,但监控显示8个核心同时负载20 - 40%。

由于所有线程同时工作,生成较少的行并仅为更新全局变量进行同步,因此执行时间不应低于单个核心吗?

3 个答案:

答案 0 :(得分:2)

我很确定你的锁是没有必要的,并且会减慢你的速度。 (编辑:实际上,我只是注意到在大部分工作完成之后使用了锁,因此并不真正相关。)

global_tab += self.tab是(我认为)原子通过Python GIL。 (实际上,this只声明了list.extend(),所以请改用它。这是另一个参考:Are lists thread safe?

或者,我会尝试multiprocessing.imap_unordered一个大的chunksize。缺点是结果是按流发送的,但是随机字符串处理可能会掩盖这一点。

import multiprocessing
import random
import string

def randomword(x):
    return ''.join(random.choice(string.ascii_letters) for i in range(15))

pool = multiprocessing.Pool(8)
results = pool.imap_unordered(randomword, range(100))
print([r for r in results])

对于200万个字符串(我更改了它以打印长度):

$ time python r.py                                                                 
2000000

real    0m38.305s
user    1m31.717s
sys     0m25.853s

我也试过清理你的版本并获得:

$ time python rr.py 
[Thread-0] Generate 250000 lines
[Thread-1] Generate 250000 lines
[Thread-2] Generate 250000 lines
[Thread-3] Generate 250000 lines
[Thread-4] Generate 250000 lines
[Thread-5] Generate 250000 lines
[Thread-6] Generate 250000 lines
[Thread-7] Generate 250000 lines
[Thread-4] 250000 lines in list
[Thread-1] 500000 lines in list
[Thread-7] 750000 lines in list
[Thread-0] 1000000 lines in list
[Thread-6] 1250000 lines in list
[Thread-2] 1500000 lines in list
[Thread-3] 1750000 lines in list
[Thread-5] 2000000 lines in list

real    0m22.113s
user    0m24.969s
sys     0m5.537s

一些重大变化:

  • 在大范围内使用xrange()(啊,python3已经这样做了。)
  • 删除threadlock
  • 在全球范围内使用extend()

(我的结果只是附加到global_tab,顺便说一下,然后省略了临时列表。)

import random, string
import threading

global_tab         = []
global_nb_threads  = 8
global_nb_loop     = 2000000

class generateList(threading.Thread):
    def __init__(self, name):
        threading.Thread.__init__(self)
        self.name = name

    def run(self):
        global global_tab
        self.tab = []

        print("[%s] Generate %d lines" % (self.name, int(global_nb_loop/global_nb_threads)))
        for x in range(int(global_nb_loop/global_nb_threads)):
            self.tab.append(("".join( [random.choice(string.ascii_letters) for i in range(15)] ), "".join( [random.choice(string.digits) for i in range(2)])))

        global_tab.extend(self.tab)
        print("[%s] %d lines in list" % (self.name, len(global_tab)))


for i in range(global_nb_threads):
    t = generateList("Thread-" + str(i))
    t.start()

for i in range(global_nb_threads):
    t.join()

...但是,单线程在16秒时仍然略快。

如果我调整multiprocessing,我可以将其降低到6秒:

size = 2000000
processes = 8
pool = multiprocessing.Pool(processes)
results = [r for r in pool.imap_unordered(randomword, range(size), chunksize=int(size/processes))]
print(len(results))

输出:

$ time python r.py                                                                 
2000000

real    0m5.713s
user    0m35.594s
sys     0m0.546s

...所以我认为这是我的最终答案:使用multiprocessing

答案 1 :(得分:2)

来自python threading docs

  

CPython实现细节:在CPython中,由于Global   解释器锁,只有一个线程可以一次执行Python代码   (即使某些面向性能的库可能会克服   这个限制)。如果您希望您的应用程序更好地使用   建议您使用多核机器的计算资源   使用多处理。但是,线程仍然是一个合适的模型   如果你想同时运行多个I / O绑定任务。

基本上这意味着python中的线程不会提高性能,除非线程主要等待某些事情发生。多处理在python中运行良好,但由于进程不共享任何对象或全局状态,编程模型对于多处理有一点不同。以下是如何使用多处理的示例:

import multiprocessing
import random
import string

def randomData(i):
    data = ("".join(random.sample(string.ascii_letters, 15)),
            "".join(random.sample(string.digits, 2)))
    return data

global_nb_loop = 2000000
pool = multiprocessing.Pool(8)
results = pool.imap(randomData, xrange(global_nb_loop))
global_tab = list(results)
print len(global_tab)

multiprocessing模块有很多版本mapapply,即imapmap_async等等。查看文档,找到最适合您问题的文档。

答案 2 :(得分:1)

由于您正在处理大量数据,我建议您查看numpy。通常numpy比列表慢,但内存效率更高,并且对于许多矢量化操作非常有用。您总是可以沿着多处理路线前进,即使是numpy。

这个版本运行速度比原始问题快3倍(作为参考,原始版本在我的机器上运行30.3秒)。

import numpy as np


def numpy_test(N=2000000):
    global_nb_loop = N 
    global_tab     = []
    asc_list = list('abcdefghijklmnopqrstuvwxyz')

    print("Generate %d lines" % global_nb_loop)
    global_tab = [(u.tostring(),str(v)) for u,v in zip( np.random.choice(asc_list, (N, 15)), np.random.randint(10, 100, N) )]
    print("%d lines generated" % len(global_tab))


In [306]: %timeit numpy_test()
Generate 2000000 lines
2000000 lines generated
Generate 2000000 lines
2000000 lines generated
Generate 2000000 lines
2000000 lines generated
Generate 2000000 lines
2000000 lines generated
1 loop, best of 3: 11.1 s per loop