Установка меньшего размера буфера для 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-18 КБ. Есть ли лучший способ чтения в стандартном вводе или, возможно, способ уменьшить размер буфера до менее 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], )

person David    schedule 08.09.2010    source источник


Ответы (6)


Вы можете полностью удалить буферизацию из 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, полное удаление буферизации, должно работать с меньшим количеством проблем на всех платформах, если это может соответствовать вашим требованиям).

person Alex Martelli    schedule 08.09.2010
comment
спасибо, флаг -u для среды linux был победителем. Ранее я пробовал использовать os.fdopen и столкнулся с той же проблемой с буферизацией, даже если я установил размер буфера равным 10. - person David; 08.09.2010
comment
К сожалению, Python 3 упорно продолжает открывать stdin в текстовом режиме с буферизацией. Переключатель -u теперь влияет только на stdout и stderr. - person Martijn Pieters; 11.01.2013
comment
Какие-нибудь обходные пути для Python3? Возможно, это библиотека / опция, управляемая событиями? - person Brad Hein; 24.01.2014
comment
Я пробовал с gio_channels, и он заработал, но поведение точно такое же: нет вывода, пока не будет нажата enter - person jcoppens; 06.06.2015
comment
Это сработало для меня в Python 3.4.3: os.fdopen(sys.stdin.fileno(), 'rb', buffering=0) - person Denilson Sá Maia; 03.12.2015
comment
Хорошая идея, @ DenilsonSá! - person Alex Martelli; 04.12.2015
comment
@ DenilsonSáMaia: Нет необходимости открывать его самостоятельно. sys.stdin - это действительно три слоя; io.TextIOWrapper (для декодирования bytes в str), обертывающего io.BufferedReader (для буферизации bytes), обертывающего io.FileIO (фактический объект, который отправляет системные вызовы). И все они доступны как атрибуты; sys.stdin.buffer получает BufferedReader без декодирования текста, sys.stding.buffer.raw получает FileIO без буферизации. - person ShadowRanger; 20.11.2020
comment
В Python 2.6–2.7 я бы рекомендовал использовать io.open(sys.stdin.fileno(), 'rb', buffering=0, closefd=True) вместо os.fdopen; os.fdopen по-прежнему возвращает вам file объект, реализованный поверх C stdio. io.open - это стиль Python 3 в двоичном режиме с отключенной буферизацией, обходится как упаковка C stdio в пользу собственного ввода-вывода ОС, так и устраняется проблема с for line in sys.stdin: (которая не устраняет буферизацию из-за внутренней буферизации в file.__next__, которая в противном случае требует странные приемы, которые нужно обойти). - person ShadowRanger; 20.11.2020

Вы можете просто использовать 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.

person Søren Løvborg    schedule 14.08.2013
comment
Это не имеет отношения к вопросу, вы хотели сделать это в качестве комментария. - person David; 15.08.2013
comment
Как я понял, вопрос был в том, есть ли лучший способ читать в stdin [чтобы избежать проблем с входным буфером при использовании скрипта Python в конвейере], и мой ответ (с опозданием на три года): Да, вместо этого используйте readline из __iter__. Но, возможно, мой ответ зависит от платформы, и у вас все еще есть проблемы с буфером, если вы попробуете приведенный выше код? - person Søren Løvborg; 16.08.2013
comment
Ага, я понимаю. Я имел в виду НАМНОГО меньшего размера буфера (например, 80 байт или меньше) для буферизации стандартного ввода. Для 2.7 вы не можете повлиять на эти размеры буфера без флага -U, о котором Алекс упоминает в своем ответе. - person David; 20.08.2013
comment
Интересно, что Алекс этого не заметил, certik / python-2.7 / blob / вы правы, что строка чтения, вероятно, быстрее, поскольку она использует getc постепенно, в то время как file_internext буферизует 8192, как определено в источнике. - person David; 20.08.2013
comment
Это очень важно - я вижу, что программы недостаточно интерактивны из-за буферизации stdin (вместо немедленной реакции). Раньше я этого не знал. - person dan3; 25.10.2013

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 самостоятельно).

person Antti Haapala    schedule 07.03.2015
comment
Похоже, это было бы лучше в качестве комментария к ответу Сорена. Алекс Мартелли и Сорен предоставили ответы, хотя это, скорее, улучшение предложения Сорена. - person David; 10.03.2015
comment
То, что вы предлагаете здесь, - лучшее решение этой ужасной проблемы, которое я видел; Я собираюсь просмотреть весь свой код Python и заменить им строку в sys.stdin. Я вижу, что он действительно указан на упомянутой вами справочной странице. Мне все еще не ясно ... почему, черт возьми, for line в sys.stdin ведет себя иначе, чем for line в iter (sys.stdin.readline, '') :? Насколько я могу судить, они семантически идентичны, за исключением того, что поведение предыдущей версии выглядит, как мне кажется, неприятной ошибкой, поведением, которое никому и никогда не может понадобиться. Если у кого-то есть контрпример, я бы с удовольствием его увидел. - person Don Hatch; 29.01.2016
comment
@DonHatch при повторении на стандартном вводе я согласен, что поведение странное и похожее на ошибку, но когда файл не является стандартным вводом, чтение 8k сразу улучшит производительность. - person Sam Jacobson; 31.08.2016
comment
@SamJacobson Почему имеет значение, является ли рассматриваемый входной поток стандартным вводом или нет? (Возможно, вы хотите указать на некоторую разницу между терминалами, файлами и каналами? Но такие различия не зависят от того, является ли это стандартный ввод.) И когда вы говорите, что чтение 8k сразу улучшит производительность - улучшит производительность по сравнению с чем ?? Я не думаю, что предлагал или защищал какое-либо поведение, которое когда-либо могло бы читать меньше 8k одновременно, когда 8k доступно на входе. - person Don Hatch; 31.08.2016
comment
@SamJacobson Кстати, я недавно написал об этом bugs.python.org/issue26290: fileinput и 'for Строка в sys.stdin делает странное издевательство над буферизацией ввода. - person Don Hatch; 31.08.2016
comment
Это определенно правильное решение для Python 2; for line in sys.stdin: на Python 2 использует внутреннюю буферизацию пользовательского режима, которая блокируется, пока не заполнит блок перед созданием каких-либо строк, и нет никакого способа отключить ее, кроме как убедиться, что вы не используете file.__next__ (-u не помогает); единственными решениями являются использование file.readline, как показано здесь, или повторное обертывание с использованием модуля io для получения поведения Python 3 (которое не блокируется, пока блок не будет заполнен, даже если буферизация включена; это, по сути, единственный системный вызов, и он подходит для краткое чтение, если краткое чтение включает новую строку). - person ShadowRanger; 20.11.2020

Это сработало для меня в 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, чтобы указать размер в байтах буфера фрагментов фиксированного размера.

Другими словами:

  • Полностью небуферизованный стандартный ввод требует двоичного режима и передачи нуля в качестве размера буфера.
  • Буферизация строки требует текстового режима.
  • Кажется, что любой другой размер буфера работает как в двоичном, так и в текстовом режимах (согласно документации).
person Denilson Sá Maia    schedule 06.12.2015

Возможно, ваши проблемы связаны не с 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 при использовании в середине конвейера.

person EvertW    schedule 18.09.2019

Единственный способ сделать это с помощью python 2.7:

tty.setcbreak(sys.stdin.fileno())

из неблокирующего ввода консоли Python. Это полностью отключит буферизацию, а также подавит эхо.

РЕДАКТИРОВАТЬ: Что касается ответа Алекса, первое предложение (вызов python с -u) невозможно в моем случае (см. ограничение shebang).

Второе предложение (дублирование fd с меньшим буфером: os.fdopen(sys.stdin.fileno(), 'r', 100)) не работает, когда я использую буфер 0 или 1, как и для интерактивного ввода, и мне нужно, чтобы каждый нажатый символ обрабатывался немедленно.

person calandoa    schedule 03.02.2017
comment
Как ни странно, тогда у меня сработал ответ Алекса. Интересно, что-то изменило / сломало обновление бэкпорта - person David; 05.02.2017
comment
tty.setcbreak касается не буферизации Python, а ввода буферизации уровня tty ядра. Таким образом, это не относится к трубам. - person textshell; 10.02.2019