Tại sao in ấn đến thiết bị xuất chuẩn quá chậm? Nó có thể được tăng tốc?


166

Tôi luôn luôn ngạc nhiên / thất vọng với việc mất bao lâu để đơn giản xuất ra thiết bị đầu cuối với một câu lệnh in. Sau một số lần đăng nhập chậm chạp gần đây, tôi quyết định xem xét nó và khá ngạc nhiên khi thấy rằng gần như toàn bộ thời gian đang chờ đợi thiết bị đầu cuối xử lý kết quả.

Có thể viết để stdout được tăng tốc bằng cách nào đó?

Tôi đã viết một tập lệnh (' print_timer.py' ở cuối câu hỏi này) để so sánh thời gian khi viết các dòng 100k vào thiết bị xuất chuẩn, vào tệp và với thiết bị xuất chuẩn được chuyển hướng đến /dev/null. Đây là kết quả thời gian:

$ 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

Ồ Để chắc chắn rằng trăn không làm gì đó đằng sau hậu trường như nhận ra rằng tôi đã gán lại thiết bị xuất chuẩn thành / dev / null hoặc thứ gì đó, tôi đã chuyển hướng bên ngoài kịch bản ...

$ 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

Vì vậy, nó không phải là một trò lừa trăn, nó chỉ là thiết bị đầu cuối. Tôi luôn biết đầu ra bán phá giá cho / dev / null tăng tốc mọi thứ, nhưng không bao giờ nghĩ rằng nó là đáng kể!

Nó làm tôi ngạc nhiên khi tty chậm như thế nào. Làm thế nào có thể ghi vào đĩa vật lý là CÁCH nhanh hơn ghi vào "màn hình" (có lẽ là op toàn bộ RAM), và có hiệu quả nhanh như chỉ đơn giản là đổ rác vào / dev / null?

Liên kết này nói về cách thiết bị đầu cuối sẽ chặn I / O để nó có thể "phân tích [đầu vào], cập nhật bộ đệm khung, giao tiếp với máy chủ X để cuộn cửa sổ, v.v." nhưng tôi không hoàn toàn có được nó Điều gì có thể mất nhiều thời gian?

Tôi hy vọng không có lối thoát nào (thiếu việc triển khai nhanh hơn?) Nhưng dù sao tôi cũng sẽ hỏi.


CẬP NHẬT: sau khi đọc một số bình luận, tôi tự hỏi kích thước màn hình của tôi thực sự ảnh hưởng đến thời gian in như thế nào và nó có một số ý nghĩa quan trọng. Những con số thực sự chậm ở trên là với thiết bị đầu cuối Gnome của tôi được thổi lên tới 1920x1200. Nếu tôi giảm nó rất nhỏ, tôi sẽ ...

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

Điều đó chắc chắn tốt hơn (~ 4x), nhưng không thay đổi câu hỏi của tôi. Nó chỉ thêm vào câu hỏi của tôi vì tôi không hiểu tại sao kết xuất màn hình đầu cuối sẽ làm chậm ứng dụng ghi vào thiết bị xuất chuẩn. Tại sao chương trình của tôi cần đợi kết xuất màn hình tiếp tục?

Có phải tất cả các ứng dụng đầu cuối / tty không được tạo ra bằng nhau? Tôi vẫn chưa thử nghiệm. Tôi thực sự có vẻ như một thiết bị đầu cuối có thể đệm tất cả dữ liệu đến, phân tích / kết xuất nó một cách vô hình và chỉ hiển thị đoạn dữ liệu gần đây nhất có thể nhìn thấy trong cấu hình màn hình hiện tại ở tốc độ khung hình hợp lý. Vì vậy, nếu tôi có thể ghi + fsync vào đĩa trong ~ 0,1 giây, một thiết bị đầu cuối sẽ có thể hoàn thành thao tác tương tự theo thứ tự đó (có thể là một vài cập nhật màn hình trong khi nó đã thực hiện).

Tôi vẫn hy vọng có một cài đặt tty có thể được thay đổi từ phía ứng dụng để làm cho hành vi này tốt hơn cho lập trình viên. Nếu đây thực sự là một sự cố ứng dụng đầu cuối, thì điều này thậm chí có thể không thuộc về StackOverflow?

Tôi đang thiếu gì?


Đây là chương trình python được sử dụng để tạo thời gian:

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
Toàn bộ mục đích của việc viết lên thiết bị xuất chuẩn là để con người có thể đọc đầu ra. Không có con người nào trên thế giới có thể đọc 10.000 dòng văn bản trong 12 giây, vậy đâu là điểm khiến cho thiết bị xuất chuẩn nhanh hơn ???
Seun Osewa

14
@Seun Osewa: Một ví dụ (điều đó dẫn đến câu hỏi của tôi) là khi làm những việc như gỡ lỗi câu lệnh in . Bạn muốn chạy chương trình của bạn và xem kết quả khi chúng xảy ra. Bạn rõ ràng đúng rằng hầu hết các dòng sẽ bay theo mà bạn không thể nhìn thấy, nhưng khi có ngoại lệ xảy ra (hoặc bạn nhấn câu lệnh getch / raw_input / ngủ có điều kiện bạn đặt cẩn thận), bạn muốn xem trực tiếp đầu ra in hơn là liên tục phải mở hoặc làm mới một tập tin xem.
Nga

3
Gỡ lỗi câu lệnh in là một trong những lý do tại sao các thiết bị tty (tức là thiết bị đầu cuối) mặc định là bộ đệm dòng thay vì bộ đệm khối: đầu ra gỡ lỗi không được sử dụng nhiều nếu chương trình bị treo và một vài dòng đầu ra gỡ lỗi cuối cùng vẫn nằm trong bộ đệm thay vì xả vào thiết bị đầu cuối.
Stephen C. Steel

@Stephen: Đây là lý do tại sao tôi không bận tâm nhiều đến việc theo đuổi những cải tiến to lớn mà một nhà bình luận đã tuyên bố bằng cách tăng kích thước bộ đệm. Nó hoàn toàn đánh bại mục đích của việc in gỡ lỗi! Tôi đã thử nghiệm một chút trong khi điều tra, nhưng không thấy cải thiện mạng. Tôi vẫn tò mò về sự khác biệt, nhưng thực sự không phải vậy.
Nga

Đôi khi, đối với các chương trình đang chạy rất lâu, tôi sẽ chỉ in dòng tiêu chuẩn hiện tại cứ sau n giây - tương tự như có độ trễ làm mới trong ứng dụng nguyền rủa. Nó không hoàn hảo, nhưng đưa ra một ý tưởng về nơi mà tôi cùng một lúc.
rkulla

Câu trả lời:


155

Làm thế nào có thể ghi vào đĩa vật lý là CÁCH nhanh hơn ghi vào "màn hình" (có lẽ là op toàn bộ RAM), và có hiệu quả nhanh như chỉ đơn giản là đổ rác vào / dev / null?

Xin chúc mừng, bạn vừa phát hiện ra tầm quan trọng của bộ đệm I / O. :-)

Đĩa có vẻ nhanh hơn, vì nó được đệm rất cao: tất cả Pythonwrite() cuộc gọi đều quay trở lại trước khi mọi thứ thực sự được ghi vào đĩa vật lý. (HĐH thực hiện việc này sau, kết hợp nhiều nghìn cá nhân viết thành một khối lớn, hiệu quả.)

Mặt khác, thiết bị đầu cuối không có hoặc không có bộ đệm: mỗi cá nhân print/ write(line)chờ đợi đầy đủ ghi (nghĩa là hiển thị cho thiết bị đầu ra) để hoàn thành.

Để làm cho phép so sánh công bằng, bạn phải làm cho kiểm tra tệp sử dụng bộ đệm đầu ra giống như thiết bị đầu cuối, điều mà bạn có thể làm bằng cách sửa đổi ví dụ của mình thành:

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

Tôi đã chạy thử nghiệm viết tệp của bạn trên máy của mình và với bộ đệm, nó cũng có 0,05 giây ở đây cho 100.000 dòng.

Tuy nhiên, với các sửa đổi ở trên để ghi không có bộ đệm, phải mất 40 giây để chỉ ghi 1.000 dòng vào đĩa. Tôi đã từ bỏ chờ đợi 100.000 dòng để viết, nhưng ngoại suy từ trước đó, sẽ mất hơn một giờ .

Điều đó đặt 11 giây của thiết bị đầu cuối vào viễn cảnh, phải không?

Vì vậy, để trả lời câu hỏi ban đầu của bạn, việc viết thư cho một thiết bị đầu cuối thực sự rất nhanh, tất cả mọi thứ được xem xét và không có nhiều chỗ để làm cho nó nhanh hơn nhiều (nhưng các thiết bị đầu cuối riêng lẻ làm thay đổi bao nhiêu công việc; câu trả lời).

(Bạn có thể thêm bộ đệm ghi, như với I / O của đĩa, nhưng sau đó bạn sẽ không thấy những gì được ghi vào thiết bị đầu cuối của mình cho đến khi bộ đệm bị xóa. Đó là một sự đánh đổi: tương tác so với hiệu quả hàng loạt.)


6
Tôi nhận được bộ đệm I / O ... bạn chắc chắn đã nhắc nhở tôi rằng tôi nên có fsync'd để so sánh đúng về thời gian hoàn thành (tôi sẽ cập nhật câu hỏi), nhưng fsync trên mỗi dòng là sự điên rồ. Có một tty thực sự cần phải làm điều đó một cách hiệu quả? Không có bộ đệm thiết bị đầu cuối / os tương đương với các tập tin? tức là: Các ứng dụng ghi vào thiết bị xuất chuẩn và trả về trước khi thiết bị đầu cuối hiển thị trên màn hình, với thiết bị đầu cuối (hoặc os) đệm tất cả. Thiết bị đầu cuối sau đó có thể hiển thị rõ ràng phần đuôi để sàng lọc ở tốc độ khung hình có thể nhìn thấy. Hiệu quả chặn trên mỗi dòng có vẻ ngớ ngẩn. Tôi cảm thấy mình vẫn còn thiếu một cái gì đó.
Nga

Bạn có thể chỉ cần mở một tay cầm để thiết bị xuất chuẩn với bộ đệm lớn, sử dụng cái gì đó như os.fdopen(sys.stdout.fileno(), 'w', BIGNUM). Điều này gần như sẽ không bao giờ hữu ích, mặc dù: hầu như tất cả các ứng dụng sẽ phải nhớ rõ ràng sau mỗi dòng đầu ra của người dùng.
Pi Delport

1
Tôi đã thử nghiệm trước đó với fp = os.fdopen(sys.__stdout__.fileno(), 'w', 10000000)bộ đệm phía trăn khổng lồ (tối đa 10 MB với ). Tác động là con số không. tức là: vẫn còn chậm trễ tty. Điều này khiến tôi suy nghĩ / nhận ra rằng bạn vừa hoãn lại vấn đề tty chậm ... khi bộ đệm của python cuối cùng đã xóa, tty dường như vẫn thực hiện cùng một lượng xử lý trên luồng trước khi quay lại.
Nga

8
Lưu ý rằng câu trả lời này là sai lệch và sai (xin lỗi!). Cụ thể là sai khi nói "không có nhiều chỗ để làm cho nó nhanh hơn [hơn 11 giây]". Xin vui lòng xem câu trả lời của riêng tôi cho câu hỏi mà tôi chỉ ra rằng thiết bị đầu cuối wterm đạt được kết quả 11 giây tương tự trong 0,26 giây.
Nga

2
Russ: cảm ơn vì đã phản hồi! Về phía tôi, một fdopenbộ đệm lớn hơn (2MB) chắc chắn đã tạo ra một sự khác biệt lớn: nó đã làm giảm thời gian in từ nhiều giây xuống 0,05 giây, giống như đầu ra tệp (sử dụng gnome-terminal).
Pi Delport

88

Cảm ơn tất cả các ý kiến! Cuối cùng tôi đã tự trả lời nó với sự giúp đỡ của bạn. Nó cảm thấy bẩn khi trả lời câu hỏi của riêng bạn, mặc dù.

Câu hỏi 1: Tại sao in ấn đến thiết bị xuất chuẩn chậm?

Trả lời: In ra thiết bị xuất chuẩn không phải là chậm. Đây là thiết bị đầu cuối bạn làm việc với đó là chậm. Và nó có khá nhiều không có gì để làm với bộ đệm I / O ở phía ứng dụng (ví dụ: bộ đệm tập tin python). Xem bên dưới.

Câu hỏi 2: Nó có thể được tăng tốc không?

Trả lời: Có, nhưng có thể không phải từ phía chương trình (phía thực hiện 'in' đến thiết bị xuất chuẩn). Để tăng tốc, sử dụng trình giả lập thiết bị đầu cuối khác nhanh hơn.

Giải trình...

Tôi đã thử một chương trình thiết bị đầu cuối 'nhẹ' tự mô tả được gọi wtermvà nhận được kết quả tốt hơn đáng kể . Dưới đây là đầu ra của tập lệnh thử nghiệm của tôi (ở cuối câu hỏi) khi chạy wtermở 1920x1200 trên cùng một hệ thống trong đó tùy chọn in cơ bản mất 12 giây bằng cách sử dụng gnome-terminal:

-----
tóm tắt thời gian (mỗi dòng 100k)
-----
in: 0,261 s
ghi vào tệp (+ fsync): 0.110 s
in với thiết bị xuất chuẩn = / dev / null: 0,050 s

0,26 giây là NHIỀU hơn 12 giây! Tôi không biết liệu wtermnó có thông minh hơn về cách nó hiển thị trên màn hình dọc theo đường mà tôi đã đề xuất hay không (hiển thị đuôi 'hiển thị' ở tốc độ khung hình hợp lý) hoặc liệu nó chỉ "làm ít" hơn gnome-terminal. Đối với mục đích câu hỏi của tôi, tôi đã có câu trả lời, mặc dù. gnome-terminalchậm

Vì vậy - Nếu bạn có một tập lệnh chạy dài mà bạn cảm thấy chậm và nó sẽ truyền một lượng lớn văn bản đến thiết bị xuất chuẩn ... hãy thử một thiết bị đầu cuối khác và xem liệu nó có tốt hơn không!

Lưu ý rằng tôi đã rút ngẫu nhiên khá nhiều wtermtừ kho ubfox / debian. Liên kết này có thể là cùng một thiết bị đầu cuối, nhưng tôi không chắc chắn. Tôi đã không kiểm tra bất kỳ trình giả lập thiết bị đầu cuối khác.


Cập nhật: Vì tôi phải gãi ngứa, tôi đã thử nghiệm cả đống trình giả lập thiết bị đầu cuối khác với cùng một kịch bản và toàn màn hình (1920x1200). Số liệu thống kê được thu thập thủ công của tôi ở đây:

0,3 giây
aterm 0,3s
rxvt 0,3s
mrxvt 0,4s
konsole 0,6s
yakuake 0,7s
lxterminal 7s
xterm 9s
gnome-terminal 12s
xfce4-terminal 12s
vala-terminal 18s
xvt 48s

Thời gian ghi lại được thu thập bằng tay, nhưng chúng khá nhất quán. Tôi đã ghi lại giá trị (ish) tốt nhất. YMMV, rõ ràng.

Như một phần thưởng, đó là một chuyến tham quan thú vị của một số trình giả lập thiết bị đầu cuối khác nhau hiện có! Tôi ngạc nhiên về bài kiểm tra 'thay thế' đầu tiên của mình hóa ra lại là bài hay nhất trong nhóm.


1
Bạn cũng có thể thử aterm. Dưới đây là kết quả trong bài kiểm tra của tôi bằng cách sử dụng tập lệnh của bạn. Aterm - print: 0.491 s, ghi vào tệp (+ fsync): 0.110 s, in với stdout = / dev / null: 0.087 s wterm - print: 0.521 s, ghi vào tệp (+ fsync): 0.105 s, in với thiết bị xuất chuẩn = / dev / null: 0,085 s
ếchstarr78

1
Làm thế nào để urxvt so với rxvt?
Daenyth

3
Ngoài ra, screen(chương trình) nên được đưa vào danh sách! (Hoặc byobu, là một trình bao bọc cho screencác cải tiến) Tiện ích này cho phép có một số thiết bị đầu cuối, giống như các tab trong thiết bị đầu cuối X. Tôi cho rằng việc in ra screenthiết bị đầu cuối hiện tại giống như in ra thiết bị đầu cuối đơn giản, nhưng in ở screenthiết bị đầu cuối của một trong hai và sau đó chuyển sang thiết bị khác không có hoạt động thì sao?
Armando Pérez Marqués

1
Thật kỳ lạ, một thời gian trước tôi đã so sánh các thiết bị đầu cuối khác nhau về tốc độ và thiết bị đầu cuối gnome xuất hiện tốt nhất trong các thử nghiệm khá nghiêm trọng trong khi xterm là chậm nhất. Có lẽ họ đã làm việc chăm chỉ để đệm từ đó. Ngoài ra hỗ trợ unicode có thể làm một sự khác biệt lớn.
Tomas Pruzina

2
iTerm2 trên OSX đã cho tôi : print: 0.587 s, write to file (+fsync): 0.034 s, print with stdout = /dev/null : 0.041 s. Và với 'màn hình' đang chạy trong iTerm2:print: 1.286 s, write to file (+fsync): 0.043 s, print with stdout = /dev/null : 0.033 s
rkulla

13

Chuyển hướng của bạn có thể không làm gì vì các chương trình có thể xác định liệu FD đầu ra của chúng có điểm không.

Có khả năng thiết bị xuất chuẩn được đệm dòng khi trỏ đến thiết bị đầu cuối (giống như stdouthành vi luồng của C ).

Như một thử nghiệm thú vị, hãy thử đường ống đầu ra cat.


Tôi đã thử thí nghiệm thú vị của riêng mình, và đây là kết quả.

$ 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

Tôi đã không nghĩ đến việc python kiểm tra FS đầu ra của nó. Tôi tự hỏi nếu con trăn đang kéo một trò lừa phía sau hậu trường? Tôi hy vọng là không, nhưng không biết.
Nga

+1 để chỉ ra sự khác biệt quan trọng trong bộ đệm
Peter G.

@Russ: các -ulực lượng tùy chọn stdin, stdoutstderrđể được chuyển hoán đệm, mà sẽ chậm hơn vì được khối đệm (do overhead)
Hasturkun

4

Tôi không thể nói về các chi tiết kỹ thuật vì tôi không biết chúng, nhưng điều này không làm tôi ngạc nhiên: thiết bị đầu cuối không được thiết kế để in nhiều dữ liệu như thế này. Thật vậy, bạn thậm chí còn cung cấp một liên kết đến một tải các công cụ GUI mà nó phải làm mỗi khi bạn muốn in một cái gì đó! Lưu ý rằng nếu bạn gọi tập lệnh pythonwthay vào đó, sẽ không mất 15 giây; đây hoàn toàn là một vấn đề GUI. Chuyển hướng stdoutđến một tập tin để tránh điều này:

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

In ấn đến thiết bị đầu cuối sẽ chậm. Thật không may khi viết một triển khai thiết bị đầu cuối mới Tôi thực sự không thể thấy bạn tăng tốc đáng kể như thế nào.


2

Ngoài đầu ra có thể mặc định ở chế độ đệm dòng, đầu ra cho thiết bị đầu cuối cũng khiến dữ liệu của bạn chảy vào thiết bị đầu cuối và dòng nối tiếp với thông lượng tối đa hoặc thiết bị đầu cuối giả và một quy trình riêng biệt đang xử lý màn hình vòng lặp sự kiện, kết xuất các ký tự từ một số phông chữ, di chuyển các bit hiển thị để thực hiện hiển thị cuộn. Kịch bản thứ hai có thể được trải rộng trên nhiều quy trình (ví dụ: máy chủ / máy khách telnet, ứng dụng đầu cuối, máy chủ hiển thị X11), do đó cũng có các vấn đề về chuyển đổi ngữ cảnh và độ trễ.


Thật! Điều này nhắc tôi thử giảm kích thước cửa sổ đầu cuối của mình (trong Gnome) xuống thứ gì đó trừng phạt (từ 1920x1200). Chắc chắn đủ ... thời gian in 2,8 giây so với 11,5 giây. Tốt hơn nhiều, nhưng vẫn ... tại sao nó bị đình trệ? Bạn sẽ nghĩ bộ đệm xuất chuẩn (hmm) có thể xử lý tất cả các dòng 100 nghìn và màn hình đầu cuối sẽ lấy bất cứ thứ gì nó có thể vừa trên màn hình từ đầu đuôi của bộ đệm và hoàn thành nó trong một lần chụp nhanh.
Nga

Xterm (hoặc gterm, trong trường hợp này) sẽ hiển thị màn hình cuối cùng của bạn nhanh hơn nếu nó không nghĩ rằng nó cũng phải hiển thị tất cả các đầu ra khác trên đường đi. Nếu cố gắng đi theo con đường này, nó có thể sẽ khiến cho trường hợp phổ biến của các cập nhật màn hình nhỏ có vẻ ít phản hồi hơn. Khi viết loại phần mềm này, đôi khi bạn có thể đối phó với nó bằng cách có các chế độ khác nhau và cố gắng phát hiện khi bạn cần chuyển sang / từ chế độ hoạt động nhỏ sang hàng loạt. Bạn có thể sử dụng cat big_file | tailhoặc thậm chí cat big_file | tee big_file.cpy | tailrất thường xuyên để tăng tốc độ này.
nargetoose
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.