Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Poor performance when reading primitive types that aren't UInt8? #106

Open
akosuas opened this issue Mar 17, 2021 · 1 comment
Open

Poor performance when reading primitive types that aren't UInt8? #106

akosuas opened this issue Mar 17, 2021 · 1 comment

Comments

@akosuas
Copy link

akosuas commented Mar 17, 2021

I am trying to read some data in a looping way, reading a mix of primitive types. Here's a simple MWE where I am just reading Int32s:

import TranscodingStreams

function writer()
    open(joinpath(homedir(), "test.dat"), write=true) do io
        write(io, rand(Int32, 1_000_000))
    end
end

function reader()
    open(TranscodingStreams.NoopStream, joinpath(homedir(), "test.dat")) do io
        for i in 1:1_000_000
            read(io, Int32)
            # read(io, UInt8)
        end
    end
end

writer()
reader()

julia> @time reader()
  0.025751 seconds (1.00 M allocations: 15.276 MiB)

This produces an allocation per read call, which becomes very slow in a tight loop! Reading 4 million UInt8s (see commented out line) has effectively zero allocations in the loop.

Is this expected behavior? Is my use-case uncommon? It does not seem to be specific to the NoopStream, since I first encountered it with the CodecZstd.ZsdtDecompressorStream.

Thanks.

@ikirill
Copy link
Contributor

ikirill commented Aug 3, 2022

The allocation happens here: https://github.com/JuliaLang/julia/blob/c094a89b9bf5ae9dccbf5d85c3925d619cfe1dcf/base/io.jl#L766, because in order to read from a stream julia allocates a Ref{Int32}, and reads into it.

Also, while read(io,UInt8) doesn't allocate, it is still much much slower compared to reading to a buffer of data all at once. For example, reading from stdin without any overhead of TranscodingStream has to at least obtain a lock on stdin, once per byte if you call it like that.

At least on my machine, reading from open("test.dat") is even slower than with NoopStream (3.9s vs 3.1s), despite allocating only 712 bytes. Reading one word at a time is an anti-pattern that shouldn't really be expected to work well no matter what.

This is the relevant stacktrace I'm getting with Profile.Allocs.@profile:

46-element Vector{Base.StackTraces.StackFrame}:
 maybe_record_alloc_to_profile at gc-alloc-profiler.h:42 [inlined]
 ijl_gc_pool_alloc at gc.c:1311
 RefValue at refvalue.jl:8 [inlined]
 Ref at refpointer.jl:136 [inlined]
 read at io.jl:766 [inlined]
 (::var"#7#8")(io::NoopStream{IOStream}) at REPL[14]:1
 open(f::var"#7#8", #unused#::Type{NoopStream}, args::String) at stream.jl:161
 reader(fn::String) at REPL[14]:1
 reader(fn::String)
 jl_apply at julia.h:1848 [inlined]
 do_call at interpreter.c:126
 eval_value at interpreter.c:215
 eval_body at interpreter.c:467
 eval_body at interpreter.c:522
 jl_interpret_toplevel_thunk at interpreter.c:750
 ip:0xffffffffffffffff
 ip:0x2442
 ip:0x7f4fe9967910
 jl_system_image_data at sys.so:?
 jl_toplevel_eval_flex at toplevel.c:912
 jl_toplevel_eval_flex at toplevel.c:856
 ijl_toplevel_eval_in at toplevel.c:971
 eval at boot.jl:370 [inlined]
 eval_user_input(ast::Any, backend::REPL.REPLBackend, mod::Module) at REPL.jl:152
 repl_backend_loop(backend::REPL.REPLBackend, get_module::Function) at REPL.jl:248
 start_repl_backend(backend::REPL.REPLBackend, consumer::Any; get_module::Function) at REPL.jl:233
 start_repl_backend at REPL.jl:230 [inlined]
 run_repl(repl::REPL.AbstractREPL, consumer::Any; backend_on_current_task::Bool) at REPL.jl:372
 run_repl(repl::REPL.AbstractREPL, consumer::Any) at REPL.jl:357
 run_repl(repl::REPL.AbstractREPL, consumer::Any) at sys.so:?
 (::Base.var"#991#993"{Bool, Bool, Bool})(REPL::Module) at client.jl:413
 (::Base.var"#991#993"{Bool, Bool, Bool})(REPL::Module) at sys.so:?
 jl_apply at julia.h:1848 [inlined]
 jl_f__call_latest at builtins.c:774
 #invokelatest#2 at essentials.jl:808 [inlined]
 invokelatest at essentials.jl:805 [inlined]
 run_main_repl(interactive::Bool, quiet::Bool, banner::Bool, history_file::Bool, color_set::Bool) at client.jl:397
 exec_options(opts::Base.JLOptions) at client.jl:314
 _start() at client.jl:514
 _start() at sys.so:?
 jl_apply at julia.h:1848 [inlined]
 true_main at jlapi.c:567
 jl_repl_entrypoint at jlapi.c:711
 main at loader_exe.c:59
 __libc_start_main at libc.so.6:?
 _start at julia:?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants