Skip to content

Commit ae95b19

Browse files
authored
Add some fast methods for IO, and Base arrays (#22)
* Add some fast methods for IO, and Base arrays * Accept ambiguities
1 parent bc47484 commit ae95b19

File tree

6 files changed

+145
-5
lines changed

6 files changed

+145
-5
lines changed

docs/src/index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ const ImmutableMemoryView{T} = MemoryView{T, Immutable}
3333

3434
Immutable memory views are immutable, in that they do not support `setindex!` or other
3535
mutating methods. The existence of an `ImmutableMemoryView` does not protect its underlying
36-
data from being mutated through another variable.
36+
data from being mutated through another variable, or though explicitly unsafe functions,
37+
such as using raw pointers.
3738

3839
## Usage
3940
### Constructing memory views

src/MemoryViews.jl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ automatically work, even if `MemoryView(x)` returns a mutable view.
6767
6868
It is not possible to mutate memory though an `ImmutableMemoryView`, but the existence
6969
of the view does not protect the same memory from being mutated though another
70-
variable.
70+
variable, or through explicitly unsafe functions.
7171
7272
The precise memory layout of the data in a `MemoryView` follows that of `Memory`.
7373
This includes the fact that some elements in the array, such as `String`s,
@@ -130,7 +130,8 @@ else `NotMemory()`. The default implementation `MemoryKind(::Type)` returns `Not
130130
If `MemoryKind(T) isa IsMemory{M}`, the following must hold:
131131
1. `M` is a concrete subtype of `MemoryView`. To obtain `M` from an `m::IsMemory{M}`,
132132
use `inner(m)`.
133-
2. `MemoryView(T)` is a valid instance of `M`.
133+
2. `MemoryView(::T)` is a valid instance of `M` (except in cases where there can be invalid
134+
instances of `T` that instead errors, e.g. uninitialized instances).
134135
3. `MemoryView(x) == x` for all instances `x::T`
135136
136137
Some objects can be turned into `MemoryView` without being `IsMemory`.
@@ -179,5 +180,7 @@ MemoryKind(::Type{Union{}}) = NotMemory()
179180
include("construction.jl")
180181
include("basic.jl")
181182
include("experimental.jl")
183+
include("base_arrays.jl")
184+
include("io.jl")
182185

183186
end # module

src/base_arrays.jl

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
Base.Vector(x::MemoryView{T}) where {T} = Vector{T}(x)
2+
3+
function Base.Vector{T}(mem::MemoryView{T}) where {T}
4+
return copyto!(Vector{T}(undef, length(mem)), mem)
5+
end
6+
7+
Base.Memory(x::MemoryView{T}) where {T} = Memory{T}(x)
8+
9+
function Base.Memory{T}(x::MemoryView{T}) where {T}
10+
return if isempty(x)
11+
Memory{T}()
12+
else
13+
@inbounds copy!(Memory{T}(undef, length(x)), x)
14+
end
15+
end
16+
17+
18+
function Base.copyto!(A::Union{Memory{T}, Array{T}}, mem::MemoryView{T}) where {T}
19+
copyto!(MemoryView(A), mem)
20+
return A
21+
end
22+
23+
function Base.copy!(A::Union{Memory{T}, Array{T}}, mem::MemoryView{T}) where {T}
24+
length(A) == length(mem) || resize!(A, length(mem))
25+
copy!(MemoryView(A), mem)
26+
return A
27+
end
28+
29+
function Base.append!(v::Vector{T}, mem::MemoryView{T}) where {T}
30+
old_len = length(v)
31+
resize!(v, length(v) + length(mem))
32+
dst = @inbounds MemoryView(v)[(old_len + 1):end]
33+
@inbounds copy!(dst, mem)
34+
return v
35+
end

src/basic.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ end
146146

147147
function Base.unsafe_copyto!(dst::MutableMemoryView{T}, src::MemoryView{T}) where {T}
148148
iszero(length(src)) && return dst
149-
@inbounds unsafe_copyto!(dst.ref, src.ref, length(src))
149+
@inbounds unsafe_copyto!(dst.ref, src.ref, length(src) % UInt)
150150
return dst
151151
end
152152

src/io.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Note: This implementation intentionally throws ambiguity errors,
2+
# because the fallback definition for ::IO ::AbstractArray{UInt8}
3+
# is a performance disaster.
4+
# This encourages implementors of IOs to implement a method for this.
5+
function Base.readbytes!(io::IO, v::MutableMemoryView{UInt8}, nb::Integer = length(v))
6+
nb = Int(nb)::Int
7+
# A view of all the bytes not yet read
8+
remaining = @inbounds v[1:min(nb, length(v))]
9+
while !isempty(remaining)
10+
eof(io) && break
11+
# Read at least 1 byte if not EOF, thereby filling the internal buffer.
12+
# and read no more than the remaining number of bytes
13+
ba = clamp(bytesavailable(io), 1, length(remaining))
14+
GC.@preserve v unsafe_read(io, Base.unsafe_convert(Ptr{UInt8}, remaining), ba % UInt)
15+
remaining = remaining[(ba + 1):end]
16+
end
17+
return length(v) - length(remaining)
18+
end

test/runtests.jl

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,89 @@ end
593593
@test m2 == m1
594594
end
595595

596+
@testset "IO" begin
597+
# Empty IO
598+
buf = IOBuffer()
599+
v = fill(0xaa, 25)
600+
@test iszero(readbytes!(buf, MemoryView(v)))
601+
@test all(==(0xaa), v)
602+
603+
# Buffer running EOF
604+
data = b"Hello, world!"
605+
buf = IOBuffer(data)
606+
@test readbytes!(buf, MemoryView(v)) == length(data)
607+
@test v == vcat(data, fill(0xaa, 25 - length(data)))
608+
609+
# With nb being lower
610+
data = b"Hello, world!"
611+
buf = IOBuffer(data)
612+
v = fill(0xaa, 25)
613+
readbytes!(buf, MemoryView(v), 7)
614+
@test v[1:8] == b"Hello, \xaa"
615+
616+
# With nb being higher than the vector length
617+
data = b"Hello, world!"
618+
buf = IOBuffer(data)
619+
v = fill(0xaa, 8)
620+
readbytes!(buf, MemoryView(v), 10)
621+
@test v == b"Hello, w"
622+
end
623+
624+
@testset "Base arrays" begin
625+
@testset "Memory construction" begin
626+
v = ImmutableMemoryView([5, 2, 1])
627+
@test Memory(v) isa Memory{Int}
628+
@test Memory(v) == v
629+
630+
@test Memory{Int}(v) isa Memory{Int}
631+
@test Memory{Int}(v) == v
632+
@test Memory{Int}(v) !== parent(v)
633+
634+
@test isempty(Memory{Int}(v[1:0]))
635+
end
636+
637+
@testset "Vector construction" begin
638+
v = ImmutableMemoryView(["abc", "def", "hi"])
639+
@test Vector(v) isa Vector{String}
640+
@test Vector(v) == v
641+
642+
@test Vector{String}(v) isa Vector{String}
643+
@test Vector{String}(v) == v
644+
645+
@test isempty(Vector{String}(v[1:0]))
646+
end
647+
648+
@testset "Vector/Memory copying" begin
649+
v = [5, 1, 3, 6, 7, 2]
650+
m = ImmutableMemoryView([6, 2, 1])
651+
@test copyto!(v, m) === v
652+
@test v == [6, 2, 1, 6, 7, 2]
653+
654+
@test copy!(v, m) === v
655+
@test v == m
656+
657+
@test_throws MethodError copy!(Memory{Int}(undef, 2), m)
658+
659+
v = Memory{Int}(undef, 3)
660+
@test copy!(v, m) === v
661+
@test v == m
662+
end
663+
664+
@testset "Vector append!" begin
665+
m = ImmutableMemoryView([7, 2, 1])
666+
v = Int[]
667+
@test append!(v, m) === v
668+
@test v == m
669+
670+
m = m[1:0]
671+
append!(v, m)
672+
@test v == [7, 2, 1]
673+
674+
@test append!(v, MemoryView([2, 1])) === v
675+
@test v == [7, 2, 1, 2, 1]
676+
end
677+
end
678+
596679
@testset "MemoryKind" begin
597680
@test MemoryKind(Vector{Int16}) == IsMemory(MutableMemoryView{Int16})
598681
@test MemoryKind(typeof(codeunits(view("abc", 2:3)))) ==
@@ -650,4 +733,4 @@ end
650733
end
651734
end
652735

653-
Aqua.test_all(MemoryViews)
736+
Aqua.test_all(MemoryViews; ambiguities = false)

0 commit comments

Comments
 (0)