Установка меньшего размера буфера для sys.stdin?
Я запускаю memcached с помощью следующего шаблона команды bash:
memcached -vv 2>&1 | tee memkeywatch2010098.log 2>&1 | ~/bin/memtracer.py | tee memkeywatchCounts20100908.log
попытаться отследить непревзойденный доступ к наборам для ключей всей платформы.
Скрипт memtracer приведен ниже и работает по желанию, с одной незначительной проблемой. Наблюдая за размером промежуточного файла журнала, memtracer.py не начинает получать ввод, пока размер файла memkeywatchYMD.log не достигнет 15-18K. Есть ли лучший способ чтения в stdin или, возможно, способ сократить размер буфера до 1 КБ для более быстрого времени отклика?
#!/usr/bin/python
import sys
from collections import defaultdict
if __name__ == "__main__":
keys = defaultdict(int)
GET = 1
SET = 2
CLIENT = 1
SERVER = 2
#if <
for line in sys.stdin:
key = None
components = line.strip().split(" ")
#newConn = components[0][1:3]
direction = CLIENT if components[0].startswith("<") else SERVER
#if lastConn != newConn:
# lastConn = newConn
if direction == CLIENT:
command = SET if components[1] == "set" else GET
key = components[2]
if command == SET:
keys[key] -= 1
elif direction == SERVER:
command = components[1]
if command == "sending":
key = components[3]
keys[key] += 1
if key != None:
print "%s:%s" % ( key, keys[key], )
5 ответов
Вы можете полностью удалить буферизацию из stdin/stdout, используя python -u
флаг:
-u : unbuffered binary stdout and stderr (also PYTHONUNBUFFERED=x)
see man page for details on internal buffering relating to '-u'
и страница руководства разъясняет:
-u Force stdin, stdout and stderr to be totally unbuffered. On
systems where it matters, also put stdin, stdout and stderr in
binary mode. Note that there is internal buffering in xread-
lines(), readlines() and file-object iterators ("for line in
sys.stdin") which is not influenced by this option. To work
around this, you will want to use "sys.stdin.readline()" inside
a "while 1:" loop.
Помимо этого, изменение буферизации для существующего файла не поддерживается, но вы можете создать новый файловый объект с тем же базовым дескриптором файла, что и существующий, и, возможно, с другой буферизацией, используя os.fdopen. То есть,
import os
import sys
newin = os.fdopen(sys.stdin.fileno(), 'r', 100)
должен связать newin
к имени файлового объекта, который читает тот же FD, что и стандартный ввод, но буферизуется только около 100 байтами за раз (и вы можете продолжить с sys.stdin = newin
использовать новый объект файла в качестве стандартного ввода с этого момента). Я говорю "должен", потому что в этой области раньше было несколько ошибок и проблем на некоторых платформах (довольно сложно обеспечить кроссплатформенность с полной общностью) - я не уверен, каково ее состояние сейчас, но я ' Я определенно рекомендую провести тщательное тестирование на всех платформах, чтобы убедиться, что все идет гладко. (-u
Полное удаление буферизации должно работать с меньшим количеством проблем на всех платформах, если это может удовлетворить ваши требования).
Вы можете просто использовать sys.stdin.readline()
вместо sys.stdin.__iter__()
:
import sys
while True:
line = sys.stdin.readline()
if not line: break # EOF
sys.stdout.write('> ' + line.upper())
Это дает мне линейное буферизованное чтение с использованием Python 2.7.4 и Python 3.3.1 в Ubuntu 13.04.
sys.stdin.__iter__
все еще будучи буферизованным строкой, можно иметь итератор, который ведет себя в основном одинаково (останавливается на EOF, тогда как stdin.__iter__
не будет) с помощью формы с двумя аргументамиiter
сделать итератор sys.stdin.readline
:
import sys
for line in iter(sys.stdin.readline, ''):
sys.stdout.write('> ' + line.upper())
Или предоставить None
в качестве сторожа (но учтите, что тогда вам нужно самостоятельно обработать условие EOF).
Это сработало для меня в Python 3.4.3:
import os
import sys
unbuffered_stdin = os.fdopen(sys.stdin.fileno(), 'rb', buffering=0)
Документация для fdopen()
говорит, что это просто псевдоним для open()
,
open()
имеет дополнительный buffering
параметр:
buffering - необязательное целое число, используемое для установки политики буферизации. Пропустите 0, чтобы выключить буферизацию (разрешено только в двоичном режиме), 1, чтобы выбрать буферизацию строки (можно использовать только в текстовом режиме), и целое число> 1, чтобы указать размер в байтах буфера фрагмента фиксированного размера.
Другими словами:
- Полностью небуферизованный стандартный ввод требует двоичного режима и передачи нуля в качестве размера буфера.
- Буферизация строки требует текстового режима.
- Кажется, что любой другой размер буфера работает как в двоичном, так и в текстовом режимах (согласно документации).
Возможно, ваши проблемы связаны не с Python, а с буферизацией, которую оболочка Linux вводит при связывании команд с конвейерами. Когда это проблема, входной сигнал буферизуется не по строкам, а по блокам 4K.
Чтобы остановить эту буферизацию, перед цепочкой команд поставьте unbuffer
команда из expect
пакет, например:
unbuffer memcached -vv 2>&1 | unbuffer -p tee memkeywatch2010098.log 2>&1 | unbuffer -p ~/bin/memtracer.py | tee memkeywatchCounts20100908.log
В unbuffer
команде нужен -p
вариант при использовании в середине трубопровода.
Единственный способ, которым я мог сделать это с Python 2.7, был:
tty.setcbreak(sys.stdin.fileno())
из Python неблокирующий консольный ввод. Это полностью отключает буферизацию и также подавляет эхо.
РЕДАКТИРОВАТЬ: Относительно ответа Алекса, первое предложение (вызов Python с -u
) в моем случае это невозможно (см. ограничение Шебанга).
Второе предложение (дублирование fd с меньшим буфером: os.fdopen(sys.stdin.fileno(), 'r', 100)
) не работает, когда я использую буфер 0 или 1, так как он используется для интерактивного ввода, и мне нужно, чтобы каждый символ был нажат для немедленной обработки.