Hi all,
I’m running into couple of issues and a bottleneck when printing characters to the screen in Julia. This is because I was trying to add some new features to TerminalUserInterfaces.jl: https://github.com/kdheepak/TerminalUserInterfaces.jl
I’m trying to recreate the following Python code in Julia:
import os
import time
def main():
delay = 1.0 / 1200
while True:
byte = os.read(0, 1)
if not byte:
break
os.write(1, byte)
time.sleep(delay)
if __name__ == "__main__":
main()
This is code from a PyCon keynote talk by Brandon Rhodes: Keynote by Brandon Rhodes - YouTube
The way the speaker runs this code is by using the following:
python animation.py < animation.txt
Here’s the animation.txt
file if someone is interested in reproducing the animation:
https://gist.github.com/kdheepak/ff6b124a8511537f4266142c0a10bb52#file-animation-txt
Firstly, I’m not able to write the code in Julia the way the speaker did. When I run julia animation.jl < animation.txt
, it appears that bytesavailable(stdin)
is never greater than zero (?). I’m not sure what I’m doing wrong here. I’ve even tried using some Stdlib functions but those error for some reason:
# import REPL
# term = REPL.Terminals.TTYTerminal("xterm", stdin, stdout, stderr)
# REPL.Terminals.raw!(term, true) # This line and the following line throws an error
# Base.start_reading(stdin)
function main()
delay = 1.0 / 1200
while bytesavailable(stdin) > 0
b = UInt8[0]
readbytes!(stdin, b)
write(stdout, b[1])
sleep(delay)
end
end
main()
The first few lines are based on this stack overflow answer:
https://stackoverflow.com/a/60956065/5451769
These are the errors I get when I uncomment the first 3 lines or the 4th line:
ERROR: LoadError: MethodError: no method matching check_open(::IOStream)
Closest candidates are:
check_open(::Union{Base.LibuvServer, Base.LibuvStream}) at ~/Applications/Julia-nightly.app/Contents/Resources/julia/share/julia/base/stream.jl:386
check_open(::Base.Filesystem.File) at ~/Applications/Julia-nightly.app/Contents/Resources/julia/share/julia/base/filesystem.jl:114
Stacktrace:
[1] raw!(t::REPL.Terminals.TTYTerminal, raw::Bool)
@ REPL.Terminals ~/Applications/Julia-nightly.app/Contents/Resources/julia/share/julia/stdlib/v1.9/REPL/src/Terminals.jl:138
[2] top-level scope
ERROR: LoadError: MethodError: no method matching start_reading(::IOStream)
Closest candidates are:
start_reading(::Base.BufferStream) at ~/Applications/Julia-nightly.app/Contents/Resources/julia/share/julia/base/stream.jl:1541
start_reading(::Base.LibuvStream) at ~/Applications/Julia-nightly.app/Contents/Resources/julia/share/julia/base/stream.jl:832
Stacktrace:
[1] top-level scope
I’m able to get around this issue by writing the code this way:
function main()
delay = 1.0 / 1200
data = readavailable(stdin)
for b in data
write(stdout, b[1])
sleep(delay)
end
end
main()
This reads all the data from stdin
at the beginning and then write
’s out the stdout
.
The thing is that Julia is several orders of magnitude slower than Python to print out to the terminal. Here’s the timing result when commenting out the sleep
functions in both Python and Julia.
python animation.py < animation.txt 0.04s user 0.03s system 80% cpu 0.082 total
julia animation.jl < animation.txt 0.70s user 0.27s system 85% cpu 1.136 total
Python finishing in 0.04 seconds but Julia takes 0.70 seconds. This makes certain kinds of animations / widgets in the terminal untenable in Julia when I’m implementing them in TerminalUserInterfaces.jl
.
I could be wrong about this, but I think this has to do with buffered vs unbuffered writes to stdout, and Julia defaulting to blocking and waiting for the write
function to finish outputting text to the terminal.
I guess my question is how to get Julia to print
and “return to execution” as fast as possible, i.e. without waiting for the character or byte to actually be written out to the terminal? I need to print something like this sequentially for TerminalUserInterfaces.jl
so I don’t think using async
, Threads
, or multiprocessing would work. Any suggestions?