Copying bytes from AbstractVector to Ptr

I am trying to efficiently copy data from an AbstractVector{UInt8} into a Ptr{UInt8}

I currently have tried the following:

function unsafe_read1!(
        dest::Ptr{UInt8},
        src::AbstractVector{UInt8},
        so::Integer,
        nbytes::UInt
    )::Nothing
    temp = Vector{UInt8}(undef, nbytes)
    copyto!(temp, 1, src, so, nbytes)
    GC.@preserve temp unsafe_copyto!(dest, pointer(temp), nbytes)
    nothing
end

A new AbstractVector{UInt8} would then need to implement Base.copyto!(dest::Vector{UInt8} ... to make this performant.

Is there a way to avoid allocating the temporary vector?

You could copy directly:

function unsafe_read2!(
        dest::Ptr{T},
        src::AbstractVector{T},
        so::Integer,
        nbytes::Integer) where T
    for i in 1:nbytes
        unsafe_store!(dest, src[so+i-1], i)
    end
end

That removes the allocation but is significantly slower. It also fails with CuArray with ERROR: Scalar indexing is disallowed.

julia> using BenchmarkTools

julia> s = @view(rand(UInt8, 10000)[1:2:end]);

julia> dest = zeros(UInt8, length(s));

julia> d = pointer(dest);

julia> @btime unsafe_read1!($d, $s, 1, $nbytes)
  1.719 μs (3 allocations: 5.00 KiB)

julia> @btime unsafe_read2!($d, $s, 1, $nbytes)
  10.329 μs (0 allocations: 0 bytes)

It seems I don’t understand the question, because this works:

function unsafe_read1!(dest::Ptr{UInt8},src::AbstractVector{UInt8},nbytes::UInt)
    unsafe_copyto!(dest, pointer(src), nbytes)
    nothing
end
nbytes=UInt(10)
julia> temp=Vector{UInt8}(undef, nbytes)
10-element Vector{UInt8}:
 0x00
 0x27
 0xf3
 0x64
 0x4c
 0x02
 0x00
 0x00
 0x03
 0x00
dest=pointer(temp)
src=zeros(UInt8,nbytes)
unsafe_read1!(dest,src,nbytes)
julia> temp
10-element Vector{UInt8}:
 0x00
 0x00
 0x00
 0x00
 0x00
 0x00
 0x00
 0x00
 0x00
 0x00

I want to support reading a specific range of bytes that are not contiguous in CPU memory, for example, the bytes in src may be compressed, in a hard drive, or in GPU memory.

You could use unsafe_wrap to associate a Vector to dest without allocating:

function unsafe_read3!(
    dest::Ptr{UInt8},
    src::AbstractVector{UInt8},
    so::Integer,
    nbytes::Integer
    )::Nothing

    dest_array = unsafe_wrap(Vector{UInt8}, dest, nbytes)
    copyto!(dest_array, 1, src, so, nbytes)
    return nothing
end

After the setup from @nhz2’s second post, and with nbytes = length(s) (and the original unsafe_read1! modified to accept nbytes::Integer, like in @matthias314 's unsafe_read2!):

julia> unsafe_read3!(d, s, 1, nbytes)

julia> all(dest .== s)
true

julia> @btime unsafe_read1!($d, $s, 1, $nbytes)
  1.370 μs (1 allocation: 5.06 KiB)

julia> @btime unsafe_read2!($d, $s, 1, $nbytes)
  20.000 μs (0 allocations: 0 bytes)

julia> @btime unsafe_read3!($d, $s, 1, $nbytes)
  1.200 μs (1 allocation: 48 bytes)

(Okay, apparently this allocated a little, but I imagine you can live with 48 bytes. :slight_smile: )

If you want to have it faster, you can use @inbounds. Ignoring manual bounds checking, that gives

function unsafe_read3!(
        dest::Ptr{T},
        src::AbstractVector{T},
        so::Integer,
        nbytes::Integer) where T
    for i in 1:nbytes
        unsafe_store!(dest, @inbounds(src[so+i-1]), i)
    end
end

With nbytes = 4000, I get

julia> @b unsafe_read1!($d, $s, 1, $nbytes)
2.992 μs (1 allocs: 4.062 KiB)

julia> @b unsafe_read3!($d, $s, 1, $nbytes)
1.845 μs
2 Likes