2016-02-08 6 views
3

Я написал небольшой тест, где я сравниваю различные методы конкатенации строк для ZOCache.tempfile.TemporaryFile vs. 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") 

EDIT 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") 

Это правда для каждой платформы? И если да, то почему?

EDIT: Результаты с фиксированным эталоном (и фиксированным кодом):

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: Nice catch. заметил, как подозрительно было время, но я просто предположил, что моя архаичная система не делает временные файлы эффективно. :-) Я включил это в свой ответ (и сделал его основной проблемой, потому что это так). – 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 (unicode в Python 2), которые ваши тесты подтверждают с использованием разных литералов и преобразования в unicode вместо bytes. Это добавляет много накладных расходов на кодирование и декодирование, а также использует намного больше памяти (unicode использует 2-4x память str для тех же данных, что означает больший расход распределителя, больше накладных расходов на носитель и т. Д.).

Другое важное отличие заключается в том, что вы устанавливаете поистине огромный bufsize для TemporaryFile; потребуется несколько системных вызовов, и большинство записей просто присоединяются к смежной памяти в буфере. В отличие от этого, io.StringIO хранит отдельные значения, записанные, и только соединяет их вместе, когда вы запрашиваете их с getvalue().

Кроме того, вы считаете, что используете совместимость с помощью конструктора bytes, но это не так; в Python 2 bytes является псевдонимом для str, так bytes(10) возвращает '10', но в Python 3, bytes это совершенно разные вещи, и передавая целое к нему возвращается ноль инициализируется bytes объект такого размера, bytes(10) возвращает 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, используя магию %timeit IPython, в этот рейтинг:

  1. io.BytesIO ~ 48 мкс на петле
  2. io.StringIO ~ 54 мкс на петле (так unicode накладные расходы не добавили много)
  3. cStringIO.StringIO ~ 83 мкс на петле
  4. TemporaryFile ~ 2,8 мс на петле (примечание блока s; ms на 1000 раз больше, чем μs)

И это не возвращается к размерам буфера по умолчанию (я сохранил явные bufsize из ваших тестов). Я подозреваю, что поведение TemporaryFile будет меняться намного больше (в зависимости от ОС и того, как обрабатываются временные файлы, некоторые системы могут просто хранить в памяти, другие могут хранить в /tmp, но, конечно же, /tmp может быть просто RAMdisk).

Что-то подсказывает, что у вас может быть установка, где TemporaryFile в основном представляет собой простой буфер памяти, который никогда не попадает в файловую систему, где моя может в конечном итоге заканчиваться на постоянном хранении (хотя бы на короткие периоды); события, происходящие в памяти, предсказуемы, но когда вы включаете файловую систему (которая может быть в зависимости от ОС, настроек ядра и т. д.), поведение будет сильно отличаться между системами.

+0

Не могли бы вы дать код ваших тестов? Ванн посмотрю, что я делаю иначе/неправильно. – pcdummy

+0

@pcdummy: Я сомневаюсь, что ваши тесты ошибочны, но я предполагаю, что у нас очень разные поведения под капотом для 'TemporaryFile'; вы, вероятно, на ядре Linux 3.11 или выше (где 'O_TMPFILE' доступен и делает что-то значимое, чтобы свести к минимуму фактический дисковый ввод-вывод); Я нахожусь на более старом ядре без этой функции, поэтому я создаю реальный файл в '/ tmp'. Мой код почти идентичен вашему (я просто уронил тесты для BufferedRandom и SpooledTemporaryFile), которые были определены в интерактивном приглашении и протестированы с помощью магии '% timeit' от' ipython', например. '% timeit -r5 bench_temporaryfile()'. – ShadowRanger

+0

См. Страницу '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' ''' '' '' '' '' '' '' '' '' наших ОС. – ShadowRanger