2016-02-08 24 views
3

我寫了一個基準,我比較了不同字符串連接方法ZOCachetempfile.TemporaryFile與StringIO

因此,它看起來像這裏是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 
+1

'GROANMODE = 1'你實際上並沒有運行臨時文件測試,它應該是'timeit('bench_temporaryfile()''(用parens調用函數) – tdelaney

+0

@tdelaney:很好的接收。注意到時間有多可疑,但我只是假定我的古老系統沒有有效地做臨時文件。:-)我將它併入我的答案中(並將它作爲主要問題,因爲它是)。 – ShadowRanger

+0

謝謝@tdelaney和ShadowRanger。 – pcdummy

回答

4

你最大的問題:Per tdelaney,你從來沒有真正跑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而不是bytes來確認。這增加了大量的編碼和解碼開銷,並且使用了更多的內存(unicodestr使用相同數據的2-4x內存,這意味着更多的分配器開銷,更多的複製開銷等)。

另一個主要區別是,你設置了一個真正巨大的bufsizeTemporaryFile;幾乎不需要系統調用,大多數寫操作只是追加到緩衝區中的連續內存中。相反,io.StringIO正在存儲寫入的各個值,並且只有在您使用getvalue()請求時纔將它們連接在一起。另外,最後,你認爲你通過使用bytes構造函數向前兼容,但你不是;在Python 2 bytesstr的別名,所以bytes(10)返回'10',但在Python 3,bytes是一個完全不同的事,並傳遞給它的整數返回零初始化該大小的bytes對象,bytes(10)返回b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

如果你想要一個公平的測試情況下,至少是開關cStringIO.StringIOio.BytesIO代替io.StringIObytes均勻。通常,你不會自己明確地設置TemporaryFile等的緩衝區大小,所以你可以考慮放棄。

在我自己在Linux的x64測試與Python 2.7.10,使用IPython中的%timeit魔術,排名是:每個環路

  1. io.BytesIO〜48微秒每循環
  2. io.StringIO〜54微秒(所以unicode開銷沒有每循環增加多少)
  3. cStringIO.StringIO〜83微秒
  4. TemporaryFile〜2.8 毫秒每個循環(注單元s; ms是比μs長1000倍)

而且這不會回到默認緩衝區大小(我保留從您的測試顯式bufsize)。我懷疑TemporaryFile的行爲將會有很大的變化(取決於操作系統以及如何處理臨時文件;有些系統可能只是存儲在內存中,其他可能存儲在/tmp中,但當然,/tmp也可能只是RAMdisk)。

有些東西告訴我你可能有一個設置,其中TemporaryFile基本上是一個普通的內存緩衝區,永遠不會進入文件系統,在那裏我的最終可能會持久存儲(如果只是短暫的)。在內存中發生的事情是可以預測的,但是當涉及文件系統時(這取決於操作系統,內核設置等),這種行爲在系統之間會有很大差異。

+0

你可以給你的測試代碼嗎?想看看我在做什麼不同/錯誤。 – pcdummy

+0

@pcdummy:我懷疑你的測試是錯誤的,但我猜測我們對於'TemporaryFile'有非常不同的底層行爲;你可能在Linux內核3.11或更高版本上(其中'O_TMPFILE'可用並且做了一些有意義的事情來最小化實際的磁盤I/O);我在一個沒有這個功能的舊內核中,所以我在'/ tmp'中創建了一個真實的文件。我的代碼與您的代碼幾乎完全相同(我只是放棄了對'BufferedRandom'和'SpooledTemporaryFile'的測試),並在交互式提示符處定義並使用來自'ipython'的'%timeit'魔法進行測試。 '%timeit -r5 bench_temporaryfile()'。 – ShadowRanger

+0

請參閱['open'手冊頁](http://man7.org/linux/man-pages/man2/open.2.html)並閱讀關於'O_TMPFILE'的信息,這可能會在性能上帶來巨大差異我們的操作系統。 – ShadowRanger