stdout으로 인쇄하는 것이 왜 그렇게 느립니까? 속도를 올릴 수 있습니까?


166

인쇄 문으로 터미널에 간단히 출력하는 데 걸리는 시간이 항상 놀랐습니다. 최근의 고통스럽게 느린 로깅 후 나는 그것을 조사하기로 결정했고 거의 모든 시간이 터미널이 결과를 처리하기를 기다리고 있음을 알게되어 매우 놀랐 습니다.

어떻게 든 stdout에 글을 쓸 수 있습니까?

print_timer.py100k 줄을 stdout, 파일 및 stdout으로 리디렉션 할 때의 타이밍을 비교 하는 스크립트 ( 이 질문의 맨 아래에 ' ')를 작성했습니다 /dev/null. 타이밍 결과는 다음과 같습니다.

$ python print_timer.py
this is a test
this is a test
<snipped 99997 lines>
this is a test
-----
timing summary (100k lines each)
-----
print                         :11.950 s
write to file (+ fsync)       : 0.122 s
print with stdout = /dev/null : 0.050 s

와. 파이썬이 stdout을 / dev / null 또는 다른 것으로 재 지정했음을 인식하는 것과 같이 무대 뒤에서 무언가를하지 않도록하기 위해 스크립트 외부에서 리디렉션을 수행했습니다 ...

$ python print_timer.py > /dev/null
-----
timing summary (100k lines each)
-----
print                         : 0.053 s
write to file (+fsync)        : 0.108 s
print with stdout = /dev/null : 0.045 s

그래서 그것은 파이썬 트릭이 아니며 터미널 일뿐입니다. 나는 항상 출력을 / dev / null 속도로 덤프하는 것을 알고 있었지만 그것이 그렇게 중요하다는 것을 결코 알지 못했습니다!

tty가 얼마나 느린 지 놀랍습니다. 물리 디스크에 쓰는 것이 "스크린"(아마도 모든 RAM op)에 쓰는 것보다 더 빠르며 / dev / null을 사용하여 가비지에 덤프하는 것만 큼 효과적입니까?

이 링크 는 터미널이 I / O를 차단하는 방법에 대해 설명하여 "[입력] 구문 분석, 프레임 버퍼 업데이트, X 서버와 통신하여 창을 스크롤하는 등"을 수행 할 수 있지만 ... 완전히 가져옵니다. 너무 오래 걸릴 수있는 것은 무엇입니까?

나는 더 빠른 tty 구현이 부족한 방법이 없을 것으로 예상하지만 어쨌든 묻습니다.


업데이트 : 일부 의견을 읽은 후 화면 크기가 실제로 인쇄 시간에 얼마나 큰 영향을 미치는지 궁금해했으며 약간의 중요성이 있습니다. 위의 정말 느린 숫자는 내 Gnome 터미널이 1920x1200으로 날아간 것입니다. 아주 작게 줄이면 ...

-----
timing summary (100k lines each)
-----
print                         : 2.920 s
write to file (+fsync)        : 0.121 s
print with stdout = /dev/null : 0.048 s

확실히 나아지지만 (~ 4 배) 내 질문은 바뀌지 않습니다. 그것은 단지 추가 터미널 화면 렌더링이 표준 출력에 쓰는 응용 프로그램을 느리게해야하는 이유를 이해하지 않는 내 질문에. 프로그램에서 화면 렌더링이 계속되기를 기다려야하는 이유는 무엇입니까?

모든 터미널 / tty 앱이 동일하게 생성되지 않습니까? 아직 실험하지 않았습니다. 터미널이 들어오는 모든 데이터를 버퍼링하고, 보이지 않게 파싱 / 렌더링하고, 현재 화면 구성에서 볼 수있는 가장 최근의 청크 만 적절한 프레임 속도로 렌더링 할 수 있어야합니다. 따라서 ~ 0.1 초 안에 디스크에 쓰거나 fsync 할 수 있다면, 터미널은 그 순서에 따라 동일한 작업을 완료 할 수 있어야합니다.

나는 여전히 프로그래머 에게이 행동을 더 잘하기 위해 응용 프로그램 측에서 변경할 수있는 tty 설정이 있기를 바라고 있습니다. 이것이 엄격하게 터미널 응용 프로그램 문제인 경우 StackOverflow에 속하지 않을 수도 있습니다.

내가 무엇을 놓치고 있습니까?


타이밍을 생성하는 데 사용되는 파이썬 프로그램은 다음과 같습니다.

import time, sys, tty
import os

lineCount = 100000
line = "this is a test"
summary = ""

cmd = "print"
startTime_s = time.time()
for x in range(lineCount):
    print line
t = time.time() - startTime_s
summary += "%-30s:%6.3f s\n" % (cmd, t)

#Add a newline to match line outputs above...
line += "\n"

cmd = "write to file (+fsync)"
fp = file("out.txt", "w")
startTime_s = time.time()
for x in range(lineCount):
    fp.write(line)
os.fsync(fp.fileno())
t = time.time() - startTime_s
summary += "%-30s:%6.3f s\n" % (cmd, t)

cmd = "print with stdout = /dev/null"
sys.stdout = file(os.devnull, "w")
startTime_s = time.time()
for x in range(lineCount):
    fp.write(line)
t = time.time() - startTime_s
summary += "%-30s:%6.3f s\n" % (cmd, t)

print >> sys.stderr, "-----"
print >> sys.stderr, "timing summary (100k lines each)"
print >> sys.stderr, "-----"
print >> sys.stderr, summary

9
stdout에 쓰는 전체 목적은 사람이 출력을 읽을 수 있도록하는 것입니다. 12 초 만에 1 만 줄의 텍스트를 읽을 수있는 사람은 없으니 stdout을 더 빨리 만드는 요점은 무엇입니까 ???
Seun Osewa

14
@Seun Osewa : 인쇄 문 디버깅 과 같은 일을 할 때의 예 입니다. 프로그램을 실행하고 결과가 발생하면 결과를 확인하려고합니다. 대부분의 줄은 볼 수없는만큼 날아갈 것이지만, 예외가 발생했을 때 (또는 신중하게 배치 한 조건부 getch / raw_input / sleep 문을 눌렀을 때) 인쇄 출력을 직접 보려고합니다. 지속적으로 파일보기를 열거 나 새로 고쳐야합니다.
Russ

3
인쇄 명령문 디버깅은 tty 장치 (예 : 터미널)가 기본적으로 블록 버퍼링 대신 라인 버퍼링을 사용하는 이유 중 하나입니다. 프로그램이 중단되고 디버그 출력의 마지막 몇 줄이 여전히 버퍼에 있으면 디버그 출력이 많이 사용되지 않습니다. 터미널로 플러시되는 대신.
Stephen C. Steel

@Stephen : 그렇기 때문에 한 댓글 작성자가 버퍼 크기를 높이는 것으로 큰 개선을 추구하면서 크게 신경 쓰지 않았습니다. 디버그 인쇄의 목적을 완전히 상실합니다! 조사하는 동안 조금 실험을 해 보았지만 아무런 개선이 없었습니다. 나는 여전히 불일치에 대해 궁금하지만 실제로는 그렇지 않습니다.
Russ

때로는 매우 오래 실행되는 프로그램의 경우 curses 앱에서 새로 고침 지연과 비슷한 n 초마다 현재 줄 stdout을 인쇄합니다. 완벽하지는 않지만 한 번에 내가 어디에 있는지에 대한 아이디어를 제공합니다.
rkulla

답변:


155

물리 디스크에 쓰는 것이 "스크린"(아마도 모든 RAM op)에 쓰는 것보다 더 빠르며 / dev / null을 사용하여 가비지에 덤프하는 것만 큼 효과적입니까?

축하합니다. I / O 버퍼링의 중요성을 알게되었습니다. :-)

디스크가 나타납니다 이 매우 버퍼링 때문에, 빠른 것으로 모든 파이썬의write() 아무것도 실제로 물리 디스크에 기록되기 전에 호출이 반환된다. (OS는 나중에 수천 개의 개별 쓰기를 크고 효율적인 덩어리로 결합하여이 작업을 수행합니다.)

반면에, 터미널은 버퍼링을 거의 또는 전혀하지 않습니다 : 각 개인 print/ 전체를write(line) 기다립니다 쓰기 (즉, 출력 장치로의 디스플레이)가 완료 될 때까지 .

비교를 공정하게하려면 파일 테스트가 터미널과 동일한 출력 버퍼링을 사용하도록해야합니다. 예를 다음과 같이 수정하여 수행 할 수 있습니다.

fp = file("out.txt", "w", 1)   # line-buffered, like stdout
[...]
for x in range(lineCount):
    fp.write(line)
    os.fsync(fp.fileno())      # wait for the write to actually complete

내 컴퓨터에서 파일 쓰기 테스트를 실행했으며 버퍼링을 사용하면 100,000 줄 동안 0.05도 있습니다.

그러나 버퍼되지 않은 상태로 쓰기 위해 위의 수정을 사용하면 디스크에 1,000 줄만 쓰려면 40 초가 걸립니다. 10 만 줄을 쓸 때까지 포기했지만 이전 내용을 외삽 하면 한 시간 이 걸릴 것입니다. 입니다.

그것은 단말기의 11 초를 원근법에 넣지 않습니까?

따라서 원래의 질문에 대답하기 위해 실제로 터미널에 쓰는 것은 엄청나게 빠르며 모든 것을 고려할 때 훨씬 더 빠를 여지가 충분하지 않습니다 (그러나 개별 터미널은 작업량에 따라 다릅니다. 이에 대한 Russ의 의견 참조) 대답).

디스크 I / O와 같이 쓰기 버퍼링을 더 추가 할 수 있지만 버퍼가 플러시 될 때까지 터미널에 쓰여진 내용을 볼 수 없습니다. 상호 작용 대 벌크 효율성이라는 단점이 있습니다.


6
I / O 버퍼링을 얻습니다 ... 완료 시간을 실제로 비교하기 위해 fsync를해야한다는 것을 분명히 상기 시켰지만 (질문을 업데이트 할 것입니다), 한 줄 당 fsync 는 광기입니다. tty가 실제로 효과적으로해야합니까? 파일에 해당하는 터미널 / OS 측 버퍼링이 없습니까? 즉 : 응용 프로그램은 stdout에 쓰고 터미널이 화면에 렌더링되기 전에 반환하며 터미널 (또는 os)은 모두 버퍼링합니다. 그 후, 단말기는 가시적 인 프레임 속도로 테일을 스크린에 현명하게 렌더링 할 수있다. 모든 회선에서 효과적으로 차단하는 것은 어리석은 것처럼 보입니다. 아직도 뭔가 빠진 것 같아요.
Russ

와 같은 것을 사용하여 큰 버퍼로 직접 stdout 핸들을 열 수 있습니다 os.fdopen(sys.stdout.fileno(), 'w', BIGNUM). 그러나 이것은 거의 유용하지 않을 것입니다. 거의 모든 응용 프로그램은 사용자가 의도 한 각 출력 줄마다 명시 적으로 플러시해야합니다.
Pi Delport

1
이전에는 거대한 fp = os.fdopen(sys.__stdout__.fileno(), 'w', 10000000)파이썬 쪽 버퍼로 최대 10MB까지 실험했습니다 . 영향은 없었습니다. 즉 : 여전히 tty 지연이 길다. 이것은 파이썬의 버퍼가 마침내 플러시 될 때 tty가 여전히 반환하기 전에 스트림에서 동일한 총 처리량을 수행하는 것처럼 보입니다.
Russ

8
이 답변은 오도되고 잘못되었습니다 (죄송합니다). "11 초보다 더 빨라질 여유가 없다"고 말하는 것은 잘못이다. wterm 터미널이 동일한 11s 결과를 0.26s로 달성했음을 보여주는 질문에 대한 내 대답을 참조하십시오.
Russ

2
Russ : 피드백 주셔서 감사합니다! 내 측면에서 더 큰 fdopen버퍼 (2MB)는 분명히 큰 차이를 만들었습니다 : 파일 출력 (을 사용하여 gnome-terminal) 과 같이 인쇄 시간이 몇 초에서 0.05 초로 단축되었습니다 .
Pi Delport

88

모든 의견에 감사드립니다! 나는 당신의 도움으로 스스로 대답했습니다. 그래도 자신의 질문에 대답하는 것은 더럽습니다.

질문 1 : stdout 인쇄 속도가 느린 이유는 무엇입니까?

답변 : stdout으로 인쇄하는 것이 본질적으로 느리지 않습니다 . 작업하는 터미널이 느립니다. 그리고 애플리케이션 측에서의 I / O 버퍼링과는 거의 관련이 없습니다 (예 : python 파일 버퍼링). 아래를 참조하십시오.

질문 2 : 속도를 높일 수 있습니까?

답 : 예, 가능하지만 프로그램 쪽 (표준 인쇄를 위해 '인쇄'를하는 쪽)에서는 보이지 않습니다. 속도를 높이려면 더 빠른 다른 터미널 에뮬레이터를 사용하십시오.

설명...

나는 자체 설명 된 '경량'터미널 프로그램을 시도하고 훨씬 더 나은 결과를 wterm얻었 습니다 . 아래는 wterm기본 인쇄 옵션이 gnome-terminal을 사용하여 12 초가 걸리는 동일한 시스템에서 1920x1200으로 실행될 때 테스트 스크립트 (질문 하단)의 출력입니다 .

-----
타이밍 요약 (각 100k 라인)
-----
인쇄 : 0.261s
파일에 쓰기 (+ fsync) : 0.110 초
stdout = / dev / null로 인쇄 : 0.050 초

0.26s는 12s보다 훨씬 낫습니다! 내가 wterm제안하는 방식에 따라 화면을 렌더링하는 방법에 대해 더 지능적 인지 (합리적인 프레임 속도로 '보이는'꼬리를 렌더링하는 것) 또는 "보다 작게"하는지 여부를 모르겠습니다 gnome-terminal. 내 질문의 목적을 위해 나는 대답을 얻었습니다. gnome-terminal느리다.

그래서-오래 실행되는 스크립트가 느리고 느리게 많은 양의 텍스트가 표준 출력으로 퍼져 있다면 다른 터미널을 사용 해보고 더 나은지 확인하십시오!

wterm우분투 / 데비안 리포지토리에서 거의 무작위로 뽑았습니다 . 이 링크 는 동일한 터미널 일 수 있지만 확실하지 않습니다. 다른 터미널 에뮬레이터를 테스트하지 않았습니다.


업데이트 : 가려움증을 긁어 야했기 때문에 동일한 스크립트와 전체 화면 (1920x1200)으로 다른 터미널 에뮬레이터 전체를 테스트했습니다. 수동으로 수집 한 통계는 다음과 같습니다.

wterm 0.3 초
aterm 0.3s
rxvt 0.3s
mrxvt 0.4s
곤솔 0.6s
야 쿠아 케 0.7s
lxterminal 7s
xterm 9s
그놈 터미널 12
xfce4 터미널 12
발라 터미널 18
xvt 48s

기록 된 시간은 수동으로 수집되지만 꽤 일관성이있었습니다. 나는 가장 좋은 값을 기록했다. 분명히 YMMV.

보너스로, 다양한 터미널 에뮬레이터 중 일부를 흥미롭게 둘러 보았습니다. 나는 첫 번째 '대체'테스트가 가장 좋은 것으로 나타났습니다.


1
당신은 또한 aterm을 시도 할 수 있습니다. 다음은 스크립트를 사용한 테스트 결과입니다. Aterm-인쇄 : 0.491s, 파일에 쓰기 (+ fsync) : 0.110s, stdout으로 인쇄 = / dev / null : 0.087s wterm-print : 0.521s, 파일에 쓰기 (+ fsync) : 0.105s, stdout으로 인쇄 = / dev / null : 0.085 s
frogstarr78

1
urxvt는 rxvt와 어떻게 비교됩니까?
Daenyth

3
또한, screen(프로그램)이 목록에 포함되어야합니다! (또는 개선 byobu된 래퍼 인 screen)이 유틸리티를 사용하면 X 터미널의 탭과 같이 여러 터미널을 가질 수 있습니다. 현재 screen터미널에 인쇄하는 것이 일반 터미널에 인쇄하는 것과 동일 하다고 생각 하지만 screen터미널 중 하나에 인쇄 한 다음 아무런 작업없이 다른 터미널로 전환하는 것은 어떻습니까?
Armando Pérez Marqués

1
이상하게도 얼마 전에 속도 측면에서 다른 터미널을 비교하고 그놈 터미널은 심각한 테스트에서 가장 잘 나타 났지만 xterm은 가장 느 렸습니다. 아마도 그들은 그 이후 버퍼링에 열심히 노력했을 것입니다. 또한 유니 코드 지원은 큰 차이를 만들 수 있습니다.
Tomas Pruzina

2
OSX의 iTerm2가 나에게 주었다 print: 0.587 s, write to file (+fsync): 0.034 s, print with stdout = /dev/null : 0.041 s. iTerm2에서 'screen'이 실행되는 경우 :print: 1.286 s, write to file (+fsync): 0.043 s, print with stdout = /dev/null : 0.033 s
rkulla

13

프로그램이 출력 FD가 tty를 가리키는 지 여부를 결정할 수 있으므로 리디렉션은 아마도 아무것도하지 않습니다.

터미널을 가리킬 때 stdout이 라인 버퍼링 될 수 있습니다 (C의 stdout스트림 동작 과 동일 ).

재미있는 실험으로, 출력을 cat .


나는 내 자신의 재미있는 실험을 시도했으며 결과는 다음과 같습니다.

$ python test.py 2>foo
...
$ cat foo
-----
timing summary (100k lines each)
-----
print                         : 6.040 s
write to file                 : 0.122 s
print with stdout = /dev/null : 0.121 s

$ python test.py 2>foo |cat
...
$ cat foo
-----
timing summary (100k lines each)
-----
print                         : 1.024 s
write to file                 : 0.131 s
print with stdout = /dev/null : 0.122 s

파이썬이 출력 FS를 확인하는 것을 생각하지 않았습니다. 파이썬이 배후에서 트릭을 당기고 있는지 궁금합니다. 나는 기대하지 않지만 모른다.
Russ

버퍼링의 가장 중요한 차이점을 지적한 +1
Peter G.

@Russ 다음 -u옵션 세력 stdin, stdoutstderr블록 (인한 오버 헤드) 버퍼링되고 느릴 것이다 버퍼링 될
Hasturkun

4

기술적 인 세부 사항에 대해 잘 모르기 때문에 말할 수는 없지만 놀랍지는 않습니다. 터미널은 이와 같은 많은 데이터를 인쇄하도록 설계되지 않았습니다. 실제로, 인쇄 할 때마다 수행해야하는 많은 GUI 항목에 대한 링크를 제공하기도합니다. pythonw대신 스크립트를 호출하면 15 초가 걸리지 않습니다. 이것은 전적으로 GUI 문제입니다. stdout이를 피하려면 파일로 리디렉션 하십시오.

import contextlib, io
@contextlib.contextmanager
def redirect_stdout(stream):
    import sys
    sys.stdout = stream
    yield
    sys.stdout = sys.__stdout__

output = io.StringIO
with redirect_stdout(output):
    ...

3

터미널로의 인쇄 속도가 느려집니다. 불행히도 새로운 터미널 구현을 작성하는 데 부족한 점은 실제로 어떻게 이것을 가속화했는지 알 수 없습니다.


2

출력은 아마도 라인 버퍼 모드로 기본 설정되는 것 외에도 터미널로의 출력으로 인해 데이터가 최대 처리량을 가진 터미널 및 직렬 라인으로 흐르거나 의사 터미널 및 디스플레이를 처리하는 별도의 프로세스가 발생합니다 이벤트 루프, 일부 글꼴에서 문자 렌더링, 스크롤 표시를 구현하기 위해 표시 비트 이동. 후자의 시나리오는 여러 프로세스 (예 : 텔넷 서버 / 클라이언트, 터미널 앱, X11 디스플레이 서버)에 분산되어 있으므로 컨텍스트 전환 및 대기 시간 문제도 있습니다.


진실! 이로 인해 터미널 창 크기 (Gnome 단위)를 우스운 크기 (1920x1200)로 낮추려고했습니다. 물론 ... 2.8 초 인쇄 시간 대 11.5 초. 훨씬 나아지지만 여전히 ... 왜 멈추는가? stdout 버퍼 (hmm)가 100k 라인을 모두 처리 할 수 ​​있다고 생각할 것입니다. 터미널 디스플레이는 버퍼의 꼬리 끝에서 화면에 맞는 모든 것을 잡아서 한 번에 완료 할 수 있다고 생각합니다.
Russ

xterm (또는이 경우 gterm)은 다른 출력을 모두 표시해야한다고 생각하지 않으면 최종 화면을 더 빠르게 렌더링합니다. 이 경로를 사용하려고 시도하면 작은 화면 업데이트의 일반적인 경우가 덜 반응하는 것처럼 보일 수 있습니다. 이 유형의 소프트웨어를 작성할 때 때때로 다른 모드를 사용하고 소규모 작동 모드에서 대량 작동 모드로 /에서 이동해야하는시기를 감지하여 처리 할 수 ​​있습니다. 이 속도 향상을 위해 cat big_file | tail또는 cat big_file | tee big_file.cpy | tail매우 자주 사용할 수 있습니다 .
nategoose
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.