Reinterpret returns wrong values

You can also try a generated function like this to avoid the Vector{Any} of fields. Might be faster:

@generated function read_seq_generated(io, ::Type{T}) where T
    types = fieldtypes(T)
    quote
        Base.Cartesian.@ncall $(length(types)) $T i -> read(io, $types[i])
    end
end

In terms of performance I get:

@benchmark read_seq_generated(x, Adummy) setup=(x = IOBuffer(src)) evals=1

BenchmarkTools.Trial: 10000 samples with 1 evaluation.
 Range (min … max):   42.000 ns …  2.666 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     125.000 ns              ┊ GC (median):    0.00%
 Time  (mean ± σ):   118.733 ns ± 33.852 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

                                         █                      
  ▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▄▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▂ ▂
  42 ns           Histogram: frequency by time          167 ns <

 Memory estimate: 80 bytes, allocs estimate: 3
2 Likes

read(stream, T) isn’t entirely safe either, you rely on the stream’s bytes being neatly arranged in read-supported Julia types. For example, all primitive types are multiple of bytes, but there’s no guarantee that a data stream will adhere to that, maybe each datum only needs 1.5 bytes and you’re expected to bitmask and portably match the platform’s endianness.

A brief explainer, what a generated function can do that a normal generic function can’t is create a function body expression based on the argument types at the call. So read_pad_interpret has the same source code no matter the T, but read_seq_generated will look at Adummy and use the @ncall macro to write the equivalent of Adummy(read(io, types[1]), read(io, types[2]), read(io, types[3]) ... etc which is close to how you might manually write the most specific efficient call for Adummy. I want to hazard a guess that the indexing is done at compile-time because fieldtypes is a tuple, so it should be like if you wrote in the field types by hand too, but if it’s not I think you could write :($T( $(( :(read(io, $ft)) for ft in fieldtypes(T) )...) ))

1 Like

Have you tried just using StructIO.jl? i.e.

using StructIO

@io struct Adummy
    p1::SVector{4, UInt8}
    p2::SVector{2, UInt8}
    p3::UInt16
    p4::UInt32
    p5::UInt32
    p6::UInt32
    p7::UInt32
    p8::UInt8
    p9::UInt32
    p10::UInt32
end align_packed

unpack(io, Adummy) # reads Adummy struct from stream io, with no padding between fields
1 Like

StructIO.jl just worked great. Thank you. Even I didn’t have to use StaticArrays.

@io struct Adummy
    p1::NTuple{4, UInt8}
    p2::NTuple{2, UInt8}
    p3::UInt16
    p4::UInt32
    p5::UInt32
    p6::UInt32
    p7::UInt32
    p8::UInt8
    p9::UInt32
    p10::UInt32
end align_packed
1 Like