tempfile.TemporaryFile 与 StringIO
tempfile.TemporaryFile vs. StringIO
我写了一个小基准,比较了 ZOCache.
的不同字符串连接方法
所以这里看起来 tempfile.TemporaryFile 比其他任何东西都快:
$ python src/ZOCache/tmp_benchmark.py
3.00407409668e-05 TemporaryFile
0.385630846024 SpooledTemporaryFile
0.299962997437 BufferedRandom
0.0849719047546 io.StringIO
0.113346099854 concat
我一直在使用的基准代码:
#!/usr/bin/python
from __future__ import print_function
import io
import timeit
import tempfile
class Error(Exception):
pass
def bench_temporaryfile():
with tempfile.TemporaryFile(bufsize=10*1024*1024) as out:
for i in range(0, 100):
out.write(b"Value = ")
out.write(bytes(i))
out.write(b" ")
# Get string.
out.seek(0)
contents = out.read()
out.close()
# Test first letter.
if contents[0:5] != b"Value":
raise Error
def bench_spooledtemporaryfile():
with tempfile.SpooledTemporaryFile(max_size=10*1024*1024) as out:
for i in range(0, 100):
out.write(b"Value = ")
out.write(bytes(i))
out.write(b" ")
# Get string.
out.seek(0)
contents = out.read()
out.close()
# Test first letter.
if contents[0:5] != b"Value":
raise Error
def bench_BufferedRandom():
# 1. BufferedRandom
with io.open('out.bin', mode='w+b') as fp:
with io.BufferedRandom(fp, buffer_size=10*1024*1024) as out:
for i in range(0, 100):
out.write(b"Value = ")
out.write(bytes(i))
out.write(b" ")
# Get string.
out.seek(0)
contents = out.read()
# Test first letter.
if contents[0:5] != b'Value':
raise Error
def bench_stringIO():
# 1. Use StringIO.
out = io.StringIO()
for i in range(0, 100):
out.write(u"Value = ")
out.write(unicode(i))
out.write(u" ")
# Get string.
contents = out.getvalue()
out.close()
# Test first letter.
if contents[0] != 'V':
raise Error
def bench_concat():
# 2. Use string appends.
data = ""
for i in range(0, 100):
data += u"Value = "
data += unicode(i)
data += u" "
# Test first letter.
if data[0] != u'V':
raise Error
if __name__ == '__main__':
print(str(timeit.timeit('bench_temporaryfile()', setup="from __main__ import bench_temporaryfile", number=1000)) + " TemporaryFile")
print(str(timeit.timeit('bench_spooledtemporaryfile()', setup="from __main__ import bench_spooledtemporaryfile", number=1000)) + " SpooledTemporaryFile")
print(str(timeit.timeit('bench_BufferedRandom()', setup="from __main__ import bench_BufferedRandom", number=1000)) + " BufferedRandom")
print(str(timeit.timeit("bench_stringIO()", setup="from __main__ import bench_stringIO", number=1000)) + " io.StringIO")
print(str(timeit.timeit("bench_concat()", setup="from __main__ import bench_concat", number=1000)) + " concat")
编辑Python3.4.3 + io.BytesIO
python3 ./src/ZOCache/tmp_benchmark.py
2.689500024644076e-05 TemporaryFile
0.30429405899985795 SpooledTemporaryFile
0.348170792000019 BufferedRandom
0.0764778530001422 io.BytesIO
0.05162201000030109 concat
新来源io.BytesIO:
#!/usr/bin/python3
from __future__ import print_function
import io
import timeit
import tempfile
class Error(Exception):
pass
def bench_temporaryfile():
with tempfile.TemporaryFile() as out:
for i in range(0, 100):
out.write(b"Value = ")
out.write(bytes(str(i), 'utf-8'))
out.write(b" ")
# Get string.
out.seek(0)
contents = out.read()
out.close()
# Test first letter.
if contents[0:5] != b"Value":
raise Error
def bench_spooledtemporaryfile():
with tempfile.SpooledTemporaryFile(max_size=10*1024*1024) as out:
for i in range(0, 100):
out.write(b"Value = ")
out.write(bytes(str(i), 'utf-8'))
out.write(b" ")
# Get string.
out.seek(0)
contents = out.read()
out.close()
# Test first letter.
if contents[0:5] != b"Value":
raise Error
def bench_BufferedRandom():
# 1. BufferedRandom
with io.open('out.bin', mode='w+b') as fp:
with io.BufferedRandom(fp, buffer_size=10*1024*1024) as out:
for i in range(0, 100):
out.write(b"Value = ")
out.write(bytes(i))
out.write(b" ")
# Get string.
out.seek(0)
contents = out.read()
# Test first letter.
if contents[0:5] != b'Value':
raise Error
def bench_BytesIO():
# 1. Use StringIO.
out = io.BytesIO()
for i in range(0, 100):
out.write(b"Value = ")
out.write(bytes(str(i), 'utf-8'))
out.write(b" ")
# Get string.
contents = out.getvalue()
out.close()
# Test first letter.
if contents[0:5] != b'Value':
raise Error
def bench_concat():
# 2. Use string appends.
data = ""
for i in range(0, 100):
data += "Value = "
data += str(i)
data += " "
# Test first letter.
if data[0] != 'V':
raise Error
if __name__ == '__main__':
print(str(timeit.timeit('bench_temporaryfile()', setup="from __main__ import bench_temporaryfile", number=1000)) + " TemporaryFile")
print(str(timeit.timeit('bench_spooledtemporaryfile()', setup="from __main__ import bench_spooledtemporaryfile", number=1000)) + " SpooledTemporaryFile")
print(str(timeit.timeit('bench_BufferedRandom()', setup="from __main__ import bench_BufferedRandom", number=1000)) + " BufferedRandom")
print(str(timeit.timeit("bench_BytesIO()", setup="from __main__ import bench_BytesIO", number=1000)) + " io.BytesIO")
print(str(timeit.timeit("bench_concat()", setup="from __main__ import bench_concat", number=1000)) + " concat")
每个平台都是这样吗?如果是,为什么?
编辑:具有固定基准(和固定代码)的结果:
0.2675984420002351 TemporaryFile
0.28104681999866443 SpooledTemporaryFile
0.3555715570000757 BufferedRandom
0.10379689100045653 io.BytesIO
0.05650951399911719 concat
你最大的问题:,你实际上从未运行 TemporaryFile
测试;您在 timeit
片段中省略了括号(并且仅针对该测试,其他实际上是 运行)。因此,您是在计算查找名称 bench_temporaryfile
所花费的时间,而不是实际调用它所花费的时间。变化:
print(str(timeit.timeit('bench_temporaryfile', setup="from __main__ import bench_temporaryfile", number=1000)) + " TemporaryFile")
至:
print(str(timeit.timeit('bench_temporaryfile()', setup="from __main__ import bench_temporaryfile", number=1000)) + " TemporaryFile")
(添加括号使其成为调用)修复。
其他一些问题:
io.StringIO
与您的其他测试用例根本不同。具体来说,您正在测试的所有其他类型都以二进制模式运行,读取和写入 str
,并避免行结束转换。 io.StringIO
使用 Python 3 种样式字符串(Python 2 中的 unicode
),您的测试通过使用不同的文字并转换为 unicode
而不是 [=20] 来确认=].这增加了大量的编码和解码开销,以及使用更多的内存(unicode
使用 2-4 倍的内存 str
用于相同的数据,这意味着更多的分配器开销,更多的复制开销等)。
另一个主要区别是您为 TemporaryFile
设置了一个真正巨大的 bufsize
;几乎不需要发生系统调用,而且大多数写入只是附加到缓冲区中的连续内存。相比之下,io.StringIO
存储写入的各个值,并且仅在您使用 getvalue()
.
请求它们时才将它们连接在一起
此外,最后,您认为使用 bytes
构造函数可以向前兼容,但事实并非如此;在Python2中bytes
是str
的别名,所以bytes(10)
returns'10'
,但在Python3中,bytes
是一个完全不同的东西,将一个整数传递给它 returns 一个零初始化的 bytes
那个大小的对象,bytes(10)
returns b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
.
如果你想要一个公平的测试用例,最起码换成cStringIO.StringIO
或io.BytesIO
而不是io.StringIO
并统一写bytes
。通常,您不会自己明确设置 TemporaryFile
等的缓冲区大小,因此您可能会考虑放弃它。
在我自己对 Linux x64 和 Python 2.7.10 的测试中,使用 ipython 的 %timeit
魔法,运行 国王是:
io.BytesIO
每个循环约 48 μs
io.StringIO
每个循环约 54 微秒(因此 unicode
开销并没有增加太多)
cStringIO.StringIO
每个循环约 83 微秒
TemporaryFile
~2.8 ms 每个循环(注意单位;ms 比 μs 长 1000 倍)
而且这还没有回到默认缓冲区大小(我在您的测试中保留了明确的 bufsize
)。我怀疑 TemporaryFile
的行为会有很大差异(取决于 OS 和临时文件的处理方式;一些系统可能只存储在内存中,其他系统可能存储在 /tmp
中,但是当然,/tmp
可能只是一个 RAMdisk。
有些东西告诉我你可能有一个设置,其中 TemporaryFile
基本上是一个永远不会进入文件系统的普通内存缓冲区,我的可能最终会在持久存储上结束(如果只是短期的话) );内存中发生的事情是可以预测的,但是当你涉及文件系统时(TemporaryFile
可以,取决于 OS、内核设置等),系统之间的行为会有很大差异。
我写了一个小基准,比较了 ZOCache.
的不同字符串连接方法所以这里看起来 tempfile.TemporaryFile 比其他任何东西都快:
$ python src/ZOCache/tmp_benchmark.py
3.00407409668e-05 TemporaryFile
0.385630846024 SpooledTemporaryFile
0.299962997437 BufferedRandom
0.0849719047546 io.StringIO
0.113346099854 concat
我一直在使用的基准代码:
#!/usr/bin/python
from __future__ import print_function
import io
import timeit
import tempfile
class Error(Exception):
pass
def bench_temporaryfile():
with tempfile.TemporaryFile(bufsize=10*1024*1024) as out:
for i in range(0, 100):
out.write(b"Value = ")
out.write(bytes(i))
out.write(b" ")
# Get string.
out.seek(0)
contents = out.read()
out.close()
# Test first letter.
if contents[0:5] != b"Value":
raise Error
def bench_spooledtemporaryfile():
with tempfile.SpooledTemporaryFile(max_size=10*1024*1024) as out:
for i in range(0, 100):
out.write(b"Value = ")
out.write(bytes(i))
out.write(b" ")
# Get string.
out.seek(0)
contents = out.read()
out.close()
# Test first letter.
if contents[0:5] != b"Value":
raise Error
def bench_BufferedRandom():
# 1. BufferedRandom
with io.open('out.bin', mode='w+b') as fp:
with io.BufferedRandom(fp, buffer_size=10*1024*1024) as out:
for i in range(0, 100):
out.write(b"Value = ")
out.write(bytes(i))
out.write(b" ")
# Get string.
out.seek(0)
contents = out.read()
# Test first letter.
if contents[0:5] != b'Value':
raise Error
def bench_stringIO():
# 1. Use StringIO.
out = io.StringIO()
for i in range(0, 100):
out.write(u"Value = ")
out.write(unicode(i))
out.write(u" ")
# Get string.
contents = out.getvalue()
out.close()
# Test first letter.
if contents[0] != 'V':
raise Error
def bench_concat():
# 2. Use string appends.
data = ""
for i in range(0, 100):
data += u"Value = "
data += unicode(i)
data += u" "
# Test first letter.
if data[0] != u'V':
raise Error
if __name__ == '__main__':
print(str(timeit.timeit('bench_temporaryfile()', setup="from __main__ import bench_temporaryfile", number=1000)) + " TemporaryFile")
print(str(timeit.timeit('bench_spooledtemporaryfile()', setup="from __main__ import bench_spooledtemporaryfile", number=1000)) + " SpooledTemporaryFile")
print(str(timeit.timeit('bench_BufferedRandom()', setup="from __main__ import bench_BufferedRandom", number=1000)) + " BufferedRandom")
print(str(timeit.timeit("bench_stringIO()", setup="from __main__ import bench_stringIO", number=1000)) + " io.StringIO")
print(str(timeit.timeit("bench_concat()", setup="from __main__ import bench_concat", number=1000)) + " concat")
编辑Python3.4.3 + io.BytesIO
python3 ./src/ZOCache/tmp_benchmark.py
2.689500024644076e-05 TemporaryFile
0.30429405899985795 SpooledTemporaryFile
0.348170792000019 BufferedRandom
0.0764778530001422 io.BytesIO
0.05162201000030109 concat
新来源io.BytesIO:
#!/usr/bin/python3
from __future__ import print_function
import io
import timeit
import tempfile
class Error(Exception):
pass
def bench_temporaryfile():
with tempfile.TemporaryFile() as out:
for i in range(0, 100):
out.write(b"Value = ")
out.write(bytes(str(i), 'utf-8'))
out.write(b" ")
# Get string.
out.seek(0)
contents = out.read()
out.close()
# Test first letter.
if contents[0:5] != b"Value":
raise Error
def bench_spooledtemporaryfile():
with tempfile.SpooledTemporaryFile(max_size=10*1024*1024) as out:
for i in range(0, 100):
out.write(b"Value = ")
out.write(bytes(str(i), 'utf-8'))
out.write(b" ")
# Get string.
out.seek(0)
contents = out.read()
out.close()
# Test first letter.
if contents[0:5] != b"Value":
raise Error
def bench_BufferedRandom():
# 1. BufferedRandom
with io.open('out.bin', mode='w+b') as fp:
with io.BufferedRandom(fp, buffer_size=10*1024*1024) as out:
for i in range(0, 100):
out.write(b"Value = ")
out.write(bytes(i))
out.write(b" ")
# Get string.
out.seek(0)
contents = out.read()
# Test first letter.
if contents[0:5] != b'Value':
raise Error
def bench_BytesIO():
# 1. Use StringIO.
out = io.BytesIO()
for i in range(0, 100):
out.write(b"Value = ")
out.write(bytes(str(i), 'utf-8'))
out.write(b" ")
# Get string.
contents = out.getvalue()
out.close()
# Test first letter.
if contents[0:5] != b'Value':
raise Error
def bench_concat():
# 2. Use string appends.
data = ""
for i in range(0, 100):
data += "Value = "
data += str(i)
data += " "
# Test first letter.
if data[0] != 'V':
raise Error
if __name__ == '__main__':
print(str(timeit.timeit('bench_temporaryfile()', setup="from __main__ import bench_temporaryfile", number=1000)) + " TemporaryFile")
print(str(timeit.timeit('bench_spooledtemporaryfile()', setup="from __main__ import bench_spooledtemporaryfile", number=1000)) + " SpooledTemporaryFile")
print(str(timeit.timeit('bench_BufferedRandom()', setup="from __main__ import bench_BufferedRandom", number=1000)) + " BufferedRandom")
print(str(timeit.timeit("bench_BytesIO()", setup="from __main__ import bench_BytesIO", number=1000)) + " io.BytesIO")
print(str(timeit.timeit("bench_concat()", setup="from __main__ import bench_concat", number=1000)) + " concat")
每个平台都是这样吗?如果是,为什么?
编辑:具有固定基准(和固定代码)的结果:
0.2675984420002351 TemporaryFile
0.28104681999866443 SpooledTemporaryFile
0.3555715570000757 BufferedRandom
0.10379689100045653 io.BytesIO
0.05650951399911719 concat
你最大的问题:TemporaryFile
测试;您在 timeit
片段中省略了括号(并且仅针对该测试,其他实际上是 运行)。因此,您是在计算查找名称 bench_temporaryfile
所花费的时间,而不是实际调用它所花费的时间。变化:
print(str(timeit.timeit('bench_temporaryfile', setup="from __main__ import bench_temporaryfile", number=1000)) + " TemporaryFile")
至:
print(str(timeit.timeit('bench_temporaryfile()', setup="from __main__ import bench_temporaryfile", number=1000)) + " TemporaryFile")
(添加括号使其成为调用)修复。
其他一些问题:
io.StringIO
与您的其他测试用例根本不同。具体来说,您正在测试的所有其他类型都以二进制模式运行,读取和写入 str
,并避免行结束转换。 io.StringIO
使用 Python 3 种样式字符串(Python 2 中的 unicode
),您的测试通过使用不同的文字并转换为 unicode
而不是 [=20] 来确认=].这增加了大量的编码和解码开销,以及使用更多的内存(unicode
使用 2-4 倍的内存 str
用于相同的数据,这意味着更多的分配器开销,更多的复制开销等)。
另一个主要区别是您为 TemporaryFile
设置了一个真正巨大的 bufsize
;几乎不需要发生系统调用,而且大多数写入只是附加到缓冲区中的连续内存。相比之下,io.StringIO
存储写入的各个值,并且仅在您使用 getvalue()
.
此外,最后,您认为使用 bytes
构造函数可以向前兼容,但事实并非如此;在Python2中bytes
是str
的别名,所以bytes(10)
returns'10'
,但在Python3中,bytes
是一个完全不同的东西,将一个整数传递给它 returns 一个零初始化的 bytes
那个大小的对象,bytes(10)
returns b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
.
如果你想要一个公平的测试用例,最起码换成cStringIO.StringIO
或io.BytesIO
而不是io.StringIO
并统一写bytes
。通常,您不会自己明确设置 TemporaryFile
等的缓冲区大小,因此您可能会考虑放弃它。
在我自己对 Linux x64 和 Python 2.7.10 的测试中,使用 ipython 的 %timeit
魔法,运行 国王是:
io.BytesIO
每个循环约 48 μsio.StringIO
每个循环约 54 微秒(因此unicode
开销并没有增加太多)cStringIO.StringIO
每个循环约 83 微秒TemporaryFile
~2.8 ms 每个循环(注意单位;ms 比 μs 长 1000 倍)
而且这还没有回到默认缓冲区大小(我在您的测试中保留了明确的 bufsize
)。我怀疑 TemporaryFile
的行为会有很大差异(取决于 OS 和临时文件的处理方式;一些系统可能只存储在内存中,其他系统可能存储在 /tmp
中,但是当然,/tmp
可能只是一个 RAMdisk。
有些东西告诉我你可能有一个设置,其中 TemporaryFile
基本上是一个永远不会进入文件系统的普通内存缓冲区,我的可能最终会在持久存储上结束(如果只是短期的话) );内存中发生的事情是可以预测的,但是当你涉及文件系统时(TemporaryFile
可以,取决于 OS、内核设置等),系统之间的行为会有很大差异。