线程&队列与串行性能

时间:2009-11-13 10:46:21

标签: python performance encryption multithreading queue

虽然查看线程和队列会很有趣,所以我编写了2个脚本,一个会打破文件并加密一个线程中的每个块,另一个会串行完成。我仍然是python的新手,并不知道为什么踩踏脚本需要这么长时间。

螺纹脚本:

#!/usr/bin/env python

from Crypto.Cipher import AES
from optparse import OptionParser
import os, base64, time, sys, hashlib, pickle, threading, timeit, Queue


BLOCK_SIZE = 32 #32 = 256-bit | 16 = 128-bit
TFILE = 'mytestfile.bin'
CHUNK_SIZE = 2048 * 2048
KEY = os.urandom(32)

class DataSplit():
    def __init__(self,fileObj, chunkSize):

        self.fileObj = fileObj
        self.chunkSize = chunkSize

    def split(self):
        while True:
            data = self.fileObj.read(self.chunkSize)
            if not data:
                break
            yield data

class encThread(threading.Thread):
    def __init__(self, seg_queue,result_queue, cipher):
        threading.Thread.__init__(self)
        self.seg_queue = seg_queue
        self.result_queue = result_queue
        self.cipher = cipher

    def run(self):
        while True:
            #Grab a data segment from the queue
            data = self.seg_queue.get()
            encSegment = []           
            for lines in data:
            encSegment.append(self.cipher.encrypt(lines))
            self.result_queue.put(encSegment)
            print "Segment Encrypted"
            self.seg_queue.task_done()

start = time.time()
def main():
    seg_queue = Queue.Queue()
    result_queue = Queue.Queue()
    estSegCount = (os.path.getsize(TFILE)/CHUNK_SIZE)+1
    cipher = AES.new(KEY, AES.MODE_CFB)
    #Spawn threads (one for each segment at the moment)
    for i in range(estSegCount):
        eT = encThread(seg_queue, result_queue, cipher)
        eT.setDaemon(True)
        eT.start()
        print ("thread spawned")

    fileObj = open(TFILE, "rb")
    splitter = DataSplit(fileObj, CHUNK_SIZE)
    for data in splitter.split():
        seg_queue.put(data)
        print ("Data sent to thread")

    seg_queue.join()
    #result_queue.join()
    print ("Seg Q: {0}".format(seg_queue.qsize()))
    print ("Res Q: {0}".format(result_queue.qsize()))



main()
print ("Elapsed Time: {0}".format(time.time()-start))

串行脚本:

#!/usr/bin/env python

from Crypto.Cipher import AES
from optparse import OptionParser
import os, base64, time, sys, hashlib, pickle, threading, timeit, Queue

TFILE = 'mytestfile.bin'
CHUNK_SIZE = 2048 * 2048

class EncSeries():
    def __init(self):
        pass

    def loadFile(self,path):
        openFile = open(path, "rb")
        #fileData = openFile.readlines()
        fileData = openFile.read(CHUNK_SIZE)
        openFile.close()
        return fileData

    def encryptData(self,key, data):
        cipher = AES.new(key, AES.MODE_CFB)
        newData = []
        for lines in data:
            newData.append(cipher.encrypt(lines))
        return newData


start = time.time()
def main():
    print ("Start")
    key = os.urandom(32)
    run = EncSeries()
    fileData = run.loadFile(TFILE)

    encFileData=run.encryptData(key, fileData)
    print("Finish")

main()
print ("Elapsed Time: {0}".format(time.time()-start))

使用readlines()而不是read也似乎在串行版本上大大加快了速度,但它已经比线程版本快得多。

5 个答案:

答案 0 :(得分:1)

线程不是加速程序的神奇方法 - 将工作分成线程通常会减慢速度,除非程序花费大量时间等待I / O.每个新线程在分割工作时会增加代码的开销,并且在线程之间切换时会增加OS中的开销。

理论上,如果你在多处理器CPU上运行,那么线程可以在不同的处理器上运行,因此工作是并行完成的,但即便如此,没有必要拥有比处理器更多的线程。

在实践中它是完全不同的,至少对于C版的Python来说。使用多个处理器时,GIL根本无法正常工作。请参阅David Beazley的presentation了解原因。 IronPython和Jython没有这个问题。

如果你真的想要并行化工作,那么最好产生多个进程并将工作分配给它们,但是传递大块数据的进程间通信开销可能会抵消任何好处并行性。

答案 1 :(得分:1)

  1. 看起来你的第二个版本只读取一个块,而第一个版本读取整个文件 - 这可以解释大的加速。 编辑:另一个问题:我刚刚注意到您无缘无故地运行for lines in data - 这实际上会单独加密字符,这要慢得多。相反,只需将数据直接传递给encrypt

  2. 启动比CPU更重的线程没有意义。

  3. 如果线程调用的是在运行时解锁GIL的扩展模块,则它们只能并行工作。我不认为PyCrypto这样做,所以你不会在这里完成任何并行工作。

  4. 如果瓶颈是磁盘性能,那么无论如何你都不会看到很多改进 - 在这种情况下,最好让一个线程执行磁盘I / O而另一个线程执行加密。 GIL不会成为问题,因为它在执行磁盘I / O时被释放。

答案 2 :(得分:1)

我观看了Dave Kirby链接到的演示文稿并尝试了一个示例计数器,这个计数器需要两倍的时间来运行两个线程:

import time
from threading import Thread

countmax=100000000

def count(n):
    while n>0:
        n-=1

def main1():
    count(countmax)
    count(countmax)

def main2():
    t1=Thread(target=count,args=(countmax,))
    t2=Thread(target=count,args=(countmax,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()

def timeit(func):
    start = time.time()
    func()
    end=time.time()-start
    print ("Elapsed Time: {0}".format(end))

if __name__ == '__main__':
    timeit(main1)
    timeit(main2)

输出:

Elapsed Time: 21.5470001698
Elapsed Time: 55.3279998302

但是,如果我更改Thread for Process:

from multiprocessing import Process

t1=Process(target ....

等。我得到了这个输出:

Elapsed Time: 20.5
Elapsed Time: 10.4059998989

现在好像我的Pentium CPU有两个内核,我打赌它是超线程。任何人都可以在他们的两个或四个核心机器上尝试这个并运行2或4个线程吗?

请参阅multiprocessing

的python 2.6.4文档

答案 3 :(得分:0)

线程有几种不同的用途:

  1. 他们只提供加速,如果它们允许您在问题的同时使多个硬件同时工作,无论该硬件是CPU核心还是磁盘头。

  2. 它们允许您跟踪多个I / O事件序列,如果没有它们会更加复杂,例如与多个用户同时进行对话。

  3. 后者不是为了提高性能,而是为了清晰的代码。

答案 4 :(得分:0)

更新这个帖子只是一个简单的注释:python 3.2有一个新的GIL实现,它减轻了与多线程相关的大量开销,但没有消除锁定。 (即它不允许您使用多个核心,但它允许您有效地在该核心上使用多个线程。)