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

Python 减少生成大型列表时的执行时间,python,multithreading,python-3.5,execution-time,Python,Multithreading,Python 3.5,Execution Time,我对Python相当陌生,我正在尝试编写一些巨大的列表(其中包含随机字母)。事实上,在我的机器上运行2000000行大约需要75-80秒 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): gl

我对Python相当陌生,我正在尝试编写一些巨大的列表(其中包含随机字母)。事实上,在我的机器上运行2000000行大约需要75-80秒

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
32秒,超过1个核心的100%,但监测显示,8个核心的20-40%的负载在同一时间


由于所有线程都在同一时间工作,生成的行数较少,并且仅为更新全局变量而同步,所以执行时间不应该低于单个内核吗?

我非常确定您的锁是不必要的,并且正在减慢您的速度。(编辑:事实上,我刚刚注意到锁是在大部分工作完成后使用的,因此与此无关。)

global\u tab+=self.tab
通过Python GIL是原子的。(实际上,仅声明
list.extend()
,因此请改用它。下面是另一个参考:

或者,我会尝试使用较大的chunksize进行
多处理。imap_unordered
。缺点是结果通过流发送,但您的随机字符串处理可能会掩盖这一点

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 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已经做到了这一点。)
  • 拆下螺纹锁固胶
  • 在全局计算机上使用
    extend()
(顺便说一句,我的结果与添加到
global_选项卡
,略去临时列表时大致相同。)

…但是,单线程在16秒时仍然稍快一些

如果我调整
多处理
,我可以将时间缩短到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
…所以我想这就是我的最终答案:使用
多处理

来自:

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

基本上,这意味着python中的线程不会提高性能,除非线程主要在等待某些事情发生。多处理在python中运行得很好,但由于进程不共享任何对象或全局状态,因此多处理的编程模型略有不同se多处理:

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)

multi-processing
模块有很多版本的
map
apply
,例如
imap
map\u async
等等。查看文档以找到最适合您的问题的版本。

由于您处理的数据量很大,我建议您查看numpy。通常numpy比列表,但内存效率更高,而且非常适合于许多向量化操作。即使使用numpy,您也可以始终使用多处理路径

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


在Python中,线程几乎从来都不是解决方案。谢谢你的回答,那么还有其他解决方案吗,或者我的第一次测试是我能得到的更好的执行吗?你分析过代码了吗?没有,因为我说我是Python新手,我会检查怎么做谢谢你的回答,我也想过使用多处理,但在问问题之前没有测试,我停在了fa上ct显示线程在100%负载下并不比单核好。我将进行测试并查看,但rrauenza的答案中有一个非常好的优化,它在第一次运行时在17秒内生成了我的列表。这在linux上非常有用,但在windows上测试时,我必须添加以下说明:if name='main':多处理。freeze_support()在一台比我更好的机器上,运行需要1m15秒,numpy运行不到4秒。今晚我会在家里做更好的测试,但我需要在所有平台上都高效的代码。Windows似乎会导致性能问题。真是太神奇了,你的第一个解决方案非常慢(3m8秒内2000000个)8核50%的负载,但您的多处理调整真的令人印象深刻:仅17秒就有2000000个,8核已满负载!我将再次进行一些测试,尝试各种映射并按照Bi Rico所说的应用,但我认为我不会比您减少更多的时间。多亏了您,您也可以(真见鬼)尝试使用线程的
multiprocessing.dummy
。我今晚将尝试,并进行比较。再次感谢
multiprocessing
(只需浏览一下源代码)我认为它使用pickle和sockets进行通信。这增加了一点开销。感谢您的回复,正如我在运行速度上看到的,运行速度比rrauenza给出的多处理解决方案快(14.3s vs 21.8s)。正如我所看到的,它在100%负载下仅使用1个内核,我将尝试使用numpty进行多处理,并查看是否获得更好的结果。我对生成的输出有一个小问题,输出包含一个包含二进制和字符串的元组。我期待一个包含字符串或字符串和int的元组。我将研究如何将二进制转换为stringYour welcome。我在给人的印象是,您需要在全局选项卡中显示如下内容:
('pkiwgbxjqfrwamf','26'),('ronabuyobwmnfb','39'),('ekharackatebpnz',',
$ time python r.py                                                                 
2000000

real    0m5.713s
user    0m35.594s
sys     0m0.546s
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)
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