Skip to content

Commit 233a7c4

Browse files
committed
implement Memory{T} as a new backend for Array{T}
TODO: add more tests for Memory specifically TODO: add to julia-licm the gc_loaded TODO: make sure all tests are passing (updated as needed) TODO: write API documentation for Memory (as needed) and devdoc updates (as needed) TODO: test Memory owner relationship (avoid implied unalias copy) of Memory in staticdata TODO: can we detect ownership for dataids for more accurate range checking? TODO: add memoryowner intrinsic (since this is a hidden field) TODO: add memoryoffset intrinsic (since this is computed better with exact idiv) TODO: implement dataids for Memory aliasing check and fix dataids for Array also makes memory allocation ccalls safer, for catching Serialization and deepcopy bugs in packages. more things to do TODO: rename ptr to ptr_or_offset TODO: don't mutate Memory length when making a String, but set Array ref field to empty Memory TODO: add alias `AtomicMemory{T} = GenericMemory{:atomic,T}` TODO: document jl_memoryt_slice TODO: add AddrSpace::Int parameter to GenericMemory Future possible kinds of possible Memories: GenericMemory{:atomic, 0, Int} GenericMemory{:not_atomic, 0, Int} GenericMemory{:unsync, 0, Int} GenericMemory{:normal, 0, Int} GenericMemory{:default, 0, Int} GenericMemory{:racy, 0, Int} GenericMemory{:local, 0, Int} GenericMemory{:const, 0, Int} GenericMemory{:ntuple, 0, Int} GenericMemory{:value, 0, Int}
1 parent bdd3ffd commit 233a7c4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

103 files changed

+5034
-3252
lines changed

base/Base.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ using Core.Intrinsics, Core.IR
99
const _included_files = Array{Tuple{Module,String},1}(Core.undef, 1)
1010
function include(mod::Module, path::String)
1111
ccall(:jl_array_grow_end, Cvoid, (Any, UInt), _included_files, UInt(1))
12-
Core.arrayset(true, _included_files, (mod, ccall(:jl_prepend_cwd, Any, (Any,), path)), arraylen(_included_files))
12+
len = getfield(_included_files.size, 1)
13+
Core.arrayset(true, _included_files, (mod, ccall(:jl_prepend_cwd, Any, (Any,), path)), len)
1314
Core.println(path)
1415
ccall(:jl_uv_flush, Nothing, (Ptr{Nothing},), Core.io_pointer(Core.stdout))
1516
Core.include(mod, path)
@@ -186,6 +187,7 @@ include("strings/lazy.jl")
186187

187188
# array structures
188189
include("indices.jl")
190+
include("genericmemory.jl")
189191
include("array.jl")
190192
include("abstractarray.jl")
191193
include("subarray.jl")

base/abstractarray.jl

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1285,8 +1285,6 @@ function getindex(A::AbstractArray, I...)
12851285
error_if_canonical_getindex(IndexStyle(A), A, I...)
12861286
_getindex(IndexStyle(A), A, to_indices(A, I)...)
12871287
end
1288-
# To avoid invalidations from multidimensional.jl: getindex(A::Array, i1::Union{Integer, CartesianIndex}, I::Union{Integer, CartesianIndex}...)
1289-
@propagate_inbounds getindex(A::Array, i1::Integer, I::Integer...) = A[to_indices(A, (i1, I...))...]
12901288

12911289
@inline unsafe_getindex(A::AbstractArray, I...) = @inbounds getindex(A, I...)
12921290

@@ -1526,7 +1524,8 @@ their component parts. A typical definition for an array that wraps a parent is
15261524
`Base.dataids(C::CustomArray) = dataids(C.parent)`.
15271525
"""
15281526
dataids(A::AbstractArray) = (UInt(objectid(A)),)
1529-
dataids(A::Array) = (UInt(pointer(A)),)
1527+
dataids(A::Memory) = (UInt(pointer(A)),)
1528+
dataids(A::Array) = dataids(A.ref.mem)
15301529
dataids(::AbstractRange) = ()
15311530
dataids(x) = ()
15321531

base/array.jl

Lines changed: 223 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ const DenseVecOrMat{T} = Union{DenseVector{T}, DenseMatrix{T}}
120120

121121
## Basic functions ##
122122

123-
using Core: arraysize, arrayset, const_arrayref
123+
using Core: arraysize, arrayset, arrayref, const_arrayref
124124

125125
"""
126126
@_safeindex
@@ -232,6 +232,7 @@ function _unsetindex!(A::Array{T}, i::Int) where {T}
232232
end
233233

234234

235+
# TODO: deprecate this (aligned_sizeof and/or elsize and/or sizeof(Some{T}) are more correct)
235236
"""
236237
Base.bitsunionsize(U::Union) -> Int
237238
@@ -252,7 +253,7 @@ function bitsunionsize(u::Union)
252253
return sz
253254
end
254255

255-
elsize(@nospecialize _::Type{A}) where {T,A<:Array{T}} = aligned_sizeof(T)
256+
elsize(::Type{A}) where {T,A<:Array{T}} = aligned_sizeof(T)
256257
function elsize(::Type{Ptr{T}}) where T
257258
# this only must return something valid for values which satisfy is_valid_intrinsic_elptr(T),
258259
# which includes Any and most concrete datatypes
@@ -261,15 +262,23 @@ function elsize(::Type{Ptr{T}}) where T
261262
return LLT_ALIGN(Core.sizeof(T), datatype_alignment(T))
262263
end
263264
elsize(::Type{Union{}}, slurp...) = 0
264-
sizeof(a::Array) = Core.sizeof(a)
265+
266+
sizeof(a::Array) = length(a) * elsize(typeof(a)) # TODO(jwn): add bitsunion bytes?
265267

266268
function isassigned(a::Array, i::Int...)
267269
@inline
268270
@boundscheck checkbounds(Bool, a, i...) || return false
269-
ii = (_sub2ind(size(a), i...) % UInt) - 1
270-
ccall(:jl_array_isassigned, Cint, (Any, UInt), a, ii) == 1
271+
ii = _sub2ind(size(a), i...)
272+
return @inbounds isassigned(memoryref(a.ref, ii, false))
273+
end
274+
275+
function isassigned(a::Vector, i::Int) # slight compiler simplification for the most common case
276+
@inline
277+
@boundscheck checkbounds(Bool, a, i) || return false
278+
return @inbounds isassigned(memoryref(a.ref, i, false))
271279
end
272280

281+
273282
## copy ##
274283

275284
"""
@@ -291,9 +300,12 @@ end
291300

292301

293302
function _unsafe_copyto!(dest, doffs, src, soffs, n)
303+
@_terminates_locally_meta
304+
# use pointer math to determine if they are deemed to alias
294305
destp = pointer(dest, doffs)
295306
srcp = pointer(src, soffs)
296-
@inbounds if destp < srcp || destp > srcp + n
307+
endp = pointer(src, soffs + n - 1)
308+
@inbounds if destp < srcp || destp > endp
297309
for i = 1:n
298310
if isassigned(src, soffs + i - 1)
299311
dest[doffs + i - 1] = src[soffs + i - 1]
@@ -324,6 +336,7 @@ that N is inbounds on either array. Incorrect usage may corrupt or segfault your
324336
the same manner as C.
325337
"""
326338
function unsafe_copyto!(dest::Array{T}, doffs, src::Array{T}, soffs, n) where T
339+
@_terminates_locally_meta
327340
t1 = @_gc_preserve_begin dest
328341
t2 = @_gc_preserve_begin src
329342
destp = pointer(dest, doffs)
@@ -1063,24 +1076,188 @@ function setindex!(A::Array{T}, X::Array{T}, c::Colon) where T
10631076
return A
10641077
end
10651078

1066-
# efficiently grow an array
1079+
# Pick new memory size for efficiently growing an array
1080+
# TODO: This should know about the size of our GC pools
1081+
# Specifically we are wasting ~10% of memory for small arrays
1082+
# by not picking memory sizes that max out a GC pool
1083+
function overallocation(maxsize)
1084+
maxsize < 8 && return 8;
1085+
# compute maxsize = maxsize + 4*maxsize^(7/8) + maxsize/8
1086+
# for small n, we grow faster than O(n)
1087+
# for large n, we grow at O(n/8)
1088+
# and as we reach O(memory) for memory>>1MB,
1089+
# this means we end by adding about 10% of memory each time
1090+
exp2 = sizeof(maxsize) * 8 - Core.Intrinsics.ctlz_int(maxsize)
1091+
maxsize += (1 << div(exp2 * 7, 8)) * 4 + div(maxsize, 8)
1092+
return maxsize
1093+
end
1094+
1095+
function array_new_memory(mem::Memory{T}, newlen::Int) where T
1096+
if ccall(:jl_mem_owner, Any, (Memory{T},), mem) isa String
1097+
# if data is in a String, keep it that way
1098+
# TODO: use jl_gc_expand_string(oldstr, newlen)?
1099+
str = _string_n(newlen)
1100+
return ccall(:jl_string_to_genericmemory, Memory{T}, (Any,), str)
1101+
else
1102+
# TODO: when implimented, this should use a memory growing call
1103+
mem = Memory{T}(undef, newlen)
1104+
return mem
1105+
end
1106+
end
10671107

1068-
_growbeg!(a::Vector, delta::Integer) =
1069-
ccall(:jl_array_grow_beg, Cvoid, (Any, UInt), a, delta)
1070-
_growend!(a::Vector, delta::Integer) =
1071-
ccall(:jl_array_grow_end, Cvoid, (Any, UInt), a, delta)
1072-
_growat!(a::Vector, i::Integer, delta::Integer) =
1073-
ccall(:jl_array_grow_at, Cvoid, (Any, Int, UInt), a, i - 1, delta)
1108+
function _growbeg!(a::Vector, delta::Integer)
1109+
delta = Int(delta)
1110+
delta == 0 && return # avoid attempting to index off the end
1111+
delta >= 0 || throw(ArgumentError("grow requires delta >= 0"))
1112+
ref = a.ref
1113+
mem = ref.mem
1114+
len = length(a)
1115+
offset = memoffset(ref)
1116+
newlen = len + delta
1117+
a.size = (newlen,)
1118+
# if offset is far enough advanced to fit data in existing memory without copying
1119+
if delta <= offset
1120+
a.ref = MemoryRef(ref, 1 - delta, false)
1121+
else
1122+
@noinline (function()
1123+
memlen = length(mem)
1124+
# since we will allocate the array in the middle of the memory we need at least 2*delta extra space
1125+
# the +1 is because I didn't want to have an off by 1 error.
1126+
newmemlen = max(overallocation(memlen), len+2*delta+1)
1127+
newoffset = div(newmemlen - newlen, 2)
1128+
# If there is extra data after the end of the array we can use that space so long as there is enough
1129+
# space at the end that there won't be quadratic behavior with a mix of growth from both ends.
1130+
# Specifically, we want to ensure that we will only do this operation once before
1131+
# increasing the size of the array, and that we leave enough space at both the benining and the end.
1132+
if newoffset + newlen + 1 < memlen
1133+
newoffset = div(memlen - newlen, 2)
1134+
newmem = mem
1135+
else
1136+
newmem = array_new_memory(mem, newmemlen)
1137+
end
1138+
if len > 0
1139+
unsafe_copyto!(newmem, newoffset+delta+1, mem, offset+1, len)
1140+
end
1141+
a.ref = MemoryRef(newmem, newoffset+1, false)
1142+
end)()
1143+
end
1144+
return
1145+
end
10741146

1075-
# efficiently delete part of an array
1147+
function _growend!(a::Vector, delta::Integer)
1148+
delta = Int(delta)
1149+
delta >= 0 || throw(ArgumentError("grow requires delta >= 0"))
1150+
ref = a.ref
1151+
mem = ref.mem
1152+
memlen = length(mem)
1153+
len = length(a)
1154+
newlen = len + delta
1155+
offset = memoffset(ref)
1156+
a.size = (newlen,)
1157+
newmemlen = offset + newlen
1158+
if memlen < newmemlen
1159+
@noinline (function()
1160+
if offset > div(5*newlen, 4)
1161+
# If the offset is far enough that we can copy without resizing
1162+
# while maintaining proportional spacing on both ends of the array
1163+
# note that this branch prevents infinite growth when doing combinations
1164+
# of push! and popfirst! (i.e. when using a Vector as a queue)
1165+
newmem = mem
1166+
newoffset = div(newlen, 8)
1167+
else
1168+
# grow either by our computed overallocation factor
1169+
# or exactly the requested size, whichever is larger
1170+
# TODO we should possibly increase the offset if the current offset is nonzero,
1171+
newmemlen2 = max(overallocation(memlen), newmemlen)
1172+
newmem = array_new_memory(mem, newmemlen2)
1173+
newoffset = offset
1174+
end
1175+
if len > 0
1176+
unsafe_copyto!(newmem, newoffset+1, mem, offset+1, len)
1177+
end
1178+
a.ref = MemoryRef(newmem, newoffset+1, false)
1179+
end)()
1180+
end
1181+
return
1182+
end
10761183

1077-
_deletebeg!(a::Vector, delta::Integer) =
1078-
ccall(:jl_array_del_beg, Cvoid, (Any, UInt), a, delta)
1079-
_deleteend!(a::Vector, delta::Integer) =
1080-
ccall(:jl_array_del_end, Cvoid, (Any, UInt), a, delta)
1081-
_deleteat!(a::Vector, i::Integer, delta::Integer) =
1082-
ccall(:jl_array_del_at, Cvoid, (Any, Int, UInt), a, i - 1, delta)
1184+
function _growat!(a::Vector, i::Integer, delta::Integer)
1185+
delta = Int(delta)
1186+
i == 1 && return _growbeg!(a, delta)
1187+
len = length(a)
1188+
i == len + 1 && return _growend!(a, delta)
1189+
delta >= 0 || throw(ArgumentError("grow requires delta >= 0"))
1190+
1 < i <= len || throw(BoundsError(a, i))
1191+
ref = a.ref
1192+
mem = ref.mem
1193+
memlen = length(mem)
1194+
newlen = len + delta
1195+
offset = memoffset(ref)
1196+
a.size = (newlen,)
1197+
newmemlen = offset + newlen
1198+
1199+
# which side would we rather grow into?
1200+
prefer_start = i <= div(len, 2)
1201+
# if offset is far enough advanced to fit data in begining of the memory
1202+
if prefer_start && delta <= offset
1203+
a.ref = MemoryRef(mem, offset-delta+1, false)
1204+
unsafe_copyto!(mem, offset-delta+1, mem, offset+1, i)
1205+
elseif !prefer_start && memlen >= newmemlen
1206+
unsafe_copyto!(mem, offset+delta+i, mem, offset+i, len-i+1)
1207+
else
1208+
# since we will allocate the array in the middle of the memory we need at least 2*delta extra space
1209+
# the +1 is because I didn't want to have an off by 1 error.
1210+
newmemlen = max(overallocation(memlen), len+2*delta+1)
1211+
newoffset = (newmemlen - newlen) ÷ 2
1212+
newmem = array_new_memory(mem, newmemlen)
1213+
a.ref = MemoryRef(newmem, newoffset+1, false)
1214+
unsafe_copyto!(newmem, newoffset+1, mem, offset+1, i)
1215+
unsafe_copyto!(newmem, newoffset+delta+i, mem, offset+i, len-i+1)
1216+
end
1217+
end
10831218

1219+
# efficiently delete part of an array
1220+
function _deletebeg!(a::Vector, delta::Integer)
1221+
delta = Int(delta)
1222+
len = length(a)
1223+
0 <= delta <= len || throw(ArgumentError("_deleteat! requires delta in 0:length(a)"))
1224+
for i in 1:delta
1225+
_unsetindex!(a, i)
1226+
end
1227+
newlen = len - delta
1228+
if newlen != 0 # if newlen==0 we could accidentally index past the memory
1229+
a.ref = MemoryRef(a.ref, delta + 1, false)
1230+
end
1231+
a.size = (newlen,)
1232+
return
1233+
end
1234+
function _deleteend!(a::Vector, delta::Integer)
1235+
delta = Int(delta)
1236+
len = length(a)
1237+
0 <= delta <= len || throw(ArgumentError("_deleteat! requires delta in 0:length(a)"))
1238+
newlen = len - delta
1239+
for i in newlen+1:len
1240+
_unsetindex!(a, i)
1241+
end
1242+
a.size = (newlen,)
1243+
return
1244+
end
1245+
function _deleteat!(a::Vector, i::Integer, delta::Integer)
1246+
i = Int(i)
1247+
len = length(a)
1248+
0 <= delta || throw(ArgumentError("_deleteat! requires delta >= 0"))
1249+
1 <= i <= len || throw(BoundsError(a, i))
1250+
i + delta <= len + 1 || throw(BoundsError(a, i + delta - 1))
1251+
newa = a
1252+
if 2*i + delta <= len
1253+
unsafe_copyto!(newa, 1 + delta, a, 1, i - 1)
1254+
_deletebeg!(a, delta)
1255+
else
1256+
unsafe_copyto!(newa, i, a, i + delta, len + 1 - delta - i)
1257+
_deleteend!(a, delta)
1258+
end
1259+
return
1260+
end
10841261
## Dequeue functionality ##
10851262

10861263
"""
@@ -1347,7 +1524,32 @@ function sizehint! end
13471524

13481525
function sizehint!(a::Vector, sz::Integer)
13491526
ccall(:jl_array_sizehint, Cvoid, (Any, UInt), a, sz)
1350-
a
1527+
return a
1528+
#=n = length(a)
1529+
len = length(a)
1530+
ref = a.ref
1531+
mem = ref.mem
1532+
memlen = length(mem)
1533+
newlen = len + delta
1534+
offset = memoffset(ref)
1535+
elsz = jl_array_elsize(a)
1536+
if (elsz == 0)
1537+
return a
1538+
end
1539+
sz = max(sz, offset+len)
1540+
1541+
if sz <= memlen
1542+
size_t dec = memlen - sz;
1543+
# if we don't save at least an eighth of maxsize then its not worth it to shrink
1544+
if dec <= div(memlen, 8)
1545+
return a
1546+
jl_array_shrink(a, dec)
1547+
else
1548+
inc = sz - len;
1549+
_growend(a, inc);
1550+
a.size = (len,)
1551+
end
1552+
a=#
13511553
end
13521554

13531555
"""

base/bitset.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ mutable struct BitSet <: AbstractSet{Int}
1515
# 1st stored Int equals 64*offset
1616
offset::Int
1717

18-
BitSet() = new(resize!(Vector{UInt64}(undef, 4), 0), NO_OFFSET)
18+
BitSet() = new(Vector{UInt64}(MemoryRef(Memory{UInt64}(undef, 4)), (0,)), NO_OFFSET)
1919
end
2020

2121
"""

0 commit comments

Comments
 (0)