인쇄 문으로 터미널에 간단히 출력하는 데 걸리는 시간이 항상 놀랐습니다. 최근의 고통스럽게 느린 로깅 후 나는 그것을 조사하기로 결정했고 거의 모든 시간이 터미널이 결과를 처리하기를 기다리고 있음을 알게되어 매우 놀랐 습니다.
어떻게 든 stdout에 글을 쓸 수 있습니까?
print_timer.py
100k 줄을 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 터미널이 1920×1200으로 날아간 것입니다. 아주 작게 줄이면 …
-----
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
답변
물리 디스크에 쓰는 것이 “스크린”(아마도 모든 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와 같이 쓰기 버퍼링을 더 추가 할 수 있지만 버퍼가 플러시 될 때까지 터미널에 쓰여진 내용을 볼 수 없습니다. 상호 작용 대 벌크 효율성이라는 단점이 있습니다.
답변
모든 의견에 감사드립니다! 나는 당신의 도움으로 스스로 대답했습니다. 그래도 자신의 질문에 대답하는 것은 더럽습니다.
질문 1 : stdout 인쇄 속도가 느린 이유는 무엇입니까?
답변 : stdout으로 인쇄하는 것이 본질적으로 느리지 않습니다 . 작업하는 터미널이 느립니다. 그리고 애플리케이션 측에서의 I / O 버퍼링과는 거의 관련이 없습니다 (예 : python 파일 버퍼링). 아래를 참조하십시오.
질문 2 : 속도를 높일 수 있습니까?
답 : 예, 가능하지만 프로그램 쪽 (표준 인쇄를 위해 ‘인쇄’를하는 쪽)에서는 보이지 않습니다. 속도를 높이려면 더 빠른 다른 터미널 에뮬레이터를 사용하십시오.
설명…
나는 자체 설명 된 ‘경량’터미널 프로그램을 시도하고 훨씬 더 나은 결과를 wterm
얻었 습니다 . 아래는 wterm
기본 인쇄 옵션이 gnome-terminal을 사용하여 12 초가 걸리는 동일한 시스템에서 1920×1200으로 실행될 때 테스트 스크립트 (질문 하단)의 출력입니다 .
----- 타이밍 요약 (각 100k 라인) ----- 인쇄 : 0.261s 파일에 쓰기 (+ fsync) : 0.110 초 stdout = / dev / null로 인쇄 : 0.050 초
0.26s는 12s보다 훨씬 낫습니다! 내가 wterm
제안하는 방식에 따라 화면을 렌더링하는 방법에 대해 더 지능적 인지 (합리적인 프레임 속도로 ‘보이는’꼬리를 렌더링하는 것) 또는 “보다 작게”하는지 여부를 모르겠습니다 gnome-terminal
. 내 질문의 목적을 위해 나는 대답을 얻었습니다. gnome-terminal
느리다.
그래서-오래 실행되는 스크립트가 느리고 느리게 많은 양의 텍스트가 표준 출력으로 퍼져 있다면 다른 터미널을 사용 해보고 더 나은지 확인하십시오!
wterm
우분투 / 데비안 리포지토리에서 거의 무작위로 뽑았습니다 . 이 링크 는 동일한 터미널 일 수 있지만 확실하지 않습니다. 다른 터미널 에뮬레이터를 테스트하지 않았습니다.
업데이트 : 가려움증을 긁어 야했기 때문에 동일한 스크립트와 전체 화면 (1920×1200)으로 다른 터미널 에뮬레이터 전체를 테스트했습니다. 수동으로 수집 한 통계는 다음과 같습니다.
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.
보너스로, 다양한 터미널 에뮬레이터 중 일부를 흥미롭게 둘러 보았습니다. 나는 첫 번째 ‘대체’테스트가 가장 좋은 것으로 나타났습니다.
답변
프로그램이 출력 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
답변
기술적 인 세부 사항에 대해 잘 모르기 때문에 말할 수는 없지만 놀랍지는 않습니다. 터미널은 이와 같은 많은 데이터를 인쇄하도록 설계되지 않았습니다. 실제로, 인쇄 할 때마다 수행해야하는 많은 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):
...
답변
터미널로의 인쇄 속도가 느려집니다. 불행히도 새로운 터미널 구현을 작성하는 데 부족한 점은 실제로 어떻게 이것을 가속화했는지 알 수 없습니다.
답변
출력은 아마도 라인 버퍼 모드로 기본 설정되는 것 외에도 터미널로의 출력으로 인해 데이터가 최대 처리량을 가진 터미널 및 직렬 라인으로 흐르거나 의사 터미널 및 디스플레이를 처리하는 별도의 프로세스가 발생합니다 이벤트 루프, 일부 글꼴에서 문자 렌더링, 스크롤 표시를 구현하기 위해 표시 비트 이동. 후자의 시나리오는 여러 프로세스 (예 : 텔넷 서버 / 클라이언트, 터미널 앱, X11 디스플레이 서버)에 분산되어 있으므로 컨텍스트 전환 및 대기 시간 문제도 있습니다.