繁体   English   中英

为什么python zipfile 模块比C 快?

[英]Why is the python zipfile module faster than C?

我正在编写一个需要能够非常快地处理大量 zip 文件的模块。 因此,我将使用 C 语言实现的东西,而不是 Python(我将从 Python 中调用提取器)。 为了尝试测试哪种方法最快,我编写了一个测试脚本,将 linux 的“unzip”命令与 czipfile python 模块(围绕 ac zip 提取器的包装器)进行比较。 作为控件,我使用了原生的 python zipfile 模块。

该脚本创建一个 zipfile,大约 100MB 的 100 ~1MB 文件。 它着眼于 3 个场景。 A) 这些文件都是随机字节串。 B)文件只是随机的十六进制字符 C)文件是带有换行符的统一随机句子。

在所有情况下,zipfile(在 python 中实现)的性能与在 c 中实现的两个提取器相当或明显更好。

任何想法为什么会发生这种情况? 附上脚本。 需要 czipfile 和 shell 中可用的“解压缩”命令。

from datetime import datetime
import zipfile
import czipfile
import os, binascii, random

class ZipTestError(Exception):
    pass



class ZipTest:

    procs = ['zipfile', 'czipfile', 'os']
    type_map = {'r':'Random', 'h':'Random Hex', 's':'Sentences'}

    # three types. t=='r' is random noise files directly out of urandom. t=='h' is urandom noise converted to ascii characters. t=='s' are randomly constructed sentences with line breaks.
    def __init__(self):
        print """Testing Random Byte Files:
"""
        self.test('r')
        self.test('h')
        self.test('s')




    @staticmethod
    def rand_name():
        return binascii.b2a_hex(os.urandom(10))

    def make_file(self, t): 
        f_name = self.rand_name()
        f = open(f_name, 'w')
        if t == 'r':
            f.write(os.urandom(1048576))
        elif t == 'h':
            f.write(binascii.b2a_hex(os.urandom(1048576)))
        elif t == 's':
            for i in range(76260):
                ops = ['dog', 'cat', 'rat']
                ops2 = ['meat', 'wood', 'fish']
                n1 = int(random.random()*10) % 3
                n2 = int(random.random()*10) % 3
                sentence = """The {0} eats {1}
    """.format(ops[n1], ops2[n2])
                f.write(sentence)
        else:
            raise ZipTestError('Invalid Type')
        f.close()
        return f_name

    #create a ~100MB zip file to test extraction on.
    def create_zip_test(self, t):
        self.file_names = []
        self.output_names = []
        for i in range(100):
            self.file_names.append(self.make_file(t))
        self.zip_name = self.rand_name()
        output = zipfile.ZipFile(self.zip_name, 'w', zipfile.ZIP_DEFLATED)
        for f in self.file_names:
            output.write(f)
        output.close()


    def clean_up(self, rem_zip = False):
        for f in self.file_names:
            os.remove(f)
        self.file_names = []
        for f in self.output_names:
            os.remove(f)
        self.output_names = []
        if rem_zip:
            if getattr(self, 'zip_name', False):
                os.remove(self.zip_name)
            self.zip_name = False

    def display_res(self, res, t):
        print """
{0} results:
""".format(self.type_map[t])
        for p in self.procs:
            print"""
{0} = {1} milliseconds""".format(p, str(res[p]))


    def test(self, t):
        self.create_zip_test(t)
        res = self.unzip()
        self.display_res(res, t)
        self.clean_up(rem_zip = True)


    def unzip(self):
        res = dict()
        for p in self.procs:
            self.clean_up()
            res[p] = getattr(self, "unzip_with_{0}".format(p))()
        return res

    def unzip_with_zipfile(self):
        return self.unzip_with_python(zipfile)

    def unzip_with_czipfile(self):
        return self.unzip_with_python(czipfile)

    def unzip_with_python(self, mod):
        f = open(self.zip_name)
        zf = mod.ZipFile(f)
        start = datetime.now()
        op = './'
        for name in zf.namelist():
            zf.extract(name,op)
            self.output_names.append(name)
        end = datetime.now()
        total = end-start
        ms = total.microseconds
        ms += (total.seconds) * 1000000
        return ms /1000

    def unzip_with_os(self):
        f = open(self.zip_name)
        start = datetime.now()
        zf = zipfile.ZipFile(f)
        for name in zf.namelist():
            self.output_names.append(name)   
        os.system("unzip -qq {0}".format(f.name))
        end = datetime.now()
        total = end-start
        ms = total.microseconds
        ms += (total.seconds) * 1000000
        return ms /1000






if __name__ == '__main__':
    ZipTest()

如上所述,解密是在python中完成的,而不是解压。 所以 zipfile 只是像其他两个一样使用 ac 实现。

即使 C 通常比解释语言快,假设算法相同,不同的缓冲策略也会有所不同。 这里有一些证据:

我对您的脚本进行了一些更改。 差异如下。

我在os.system之前启动了秒表。 这种变化并不明显,因为从中央目录读取条目的速度很快。 因此,我保存了 zip 文件并使用 Python 之外的内置time shell 测量了解压缩时间。 结果表明,启动新进程的开销并不那么重要。

一个更有趣的变化是添加了libarchive 我得到的结果是这样的(毫秒):

             Random     Hex  Sentences
zipfile         368    1909        604
czipfile        241    1600       2313
os              707    2225        784
shell-measured  797    2272        737
libarchive      248    1513        451
         EXTRACTION METHOD

请注意,结果每次都会有几毫秒的变化。 shell 测量realusersys时间(请参阅time(1) 的输出中的“real”、“user”和“sys”是什么意思? )。 为了与其他测量结果保持一致,以上数字反映了实时情况。

可以通过strace -c -w更好地分析系统调用 unzip 问题。 它显示了Hex的读取峰值:

             Random     Hex  Sentences
read            805   14597      12816
write          2600    3200       1600
         SYSTEM CALLS ISSUED BY unzip

现在的差异(它假定最初的剧本名为ziptest.py在运行相同的目录patch < _diff_ ,看到

--- ziptest.py.orig 2017-05-25 10:36:03.106994889 +0200
+++ ziptest.py  2017-05-25 11:30:42.032598259 +0200
@@ -2,6 +2,7 @@
 import zipfile
 import czipfile
 import os, binascii, random
+import libarchive.public

 class ZipTestError(Exception):
     pass
@@ -10,7 +11,7 @@

 class ZipTest:

-    procs = ['zipfile', 'czipfile', 'os']
+    procs = ['zipfile', 'czipfile', 'os', 'libarchive']
     type_map = {'r':'Random', 'h':'Random Hex', 's':'Sentences'}

     # three types. t=='r' is random noise files directly out of urandom. t=='h' is urandom noise converted to ascii characters. t=='s' are randomly constructed sentences with line breaks.
@@ -119,10 +120,10 @@

     def unzip_with_os(self):
         f = open(self.zip_name)
-        start = datetime.now()
         zf = zipfile.ZipFile(f)
         for name in zf.namelist():
             self.output_names.append(name)   
+        start = datetime.now()
         os.system("unzip -qq {0}".format(f.name))
         end = datetime.now()
         total = end-start
@@ -130,7 +131,15 @@
         ms += (total.seconds) * 1000000
         return ms /1000

-
+    def unzip_with_libarchive(self):
+        start = datetime.now()
+        for entry in libarchive.public.file_pour(self.zip_name):
+            self.output_names.append(str(entry))
+        end = datetime.now()
+        total = end-start
+        ms = total.microseconds
+        ms += (total.seconds) * 1000000
+        return ms /1000

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM