or
or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up
Syntax | Example | Reference | |
---|---|---|---|
# Header | Header | 基本排版 | |
- Unordered List |
|
||
1. Ordered List |
|
||
- [ ] Todo List |
|
||
> Blockquote | Blockquote |
||
**Bold font** | Bold font | ||
*Italics font* | Italics font | ||
~~Strikethrough~~ | |||
19^th^ | 19th | ||
H~2~O | H2O | ||
++Inserted text++ | Inserted text | ||
==Marked text== | Marked text | ||
[link text](https:// "title") | Link | ||
 | Image | ||
`Code` | Code |
在筆記中貼入程式碼 | |
```javascript var i = 0; ``` |
|
||
:smile: | ![]() |
Emoji list | |
{%youtube youtube_id %} | Externals | ||
$L^aT_eX$ | LaTeX | ||
:::info This is a alert area. ::: |
This is a alert area. |
On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?
Please give us some advice and help us improve HackMD.
Syncing
xxxxxxxxxx
Julep: Redesigning
Array
using a new lower-level container typeHistory and motivation
From its inception, Julia has had an
Array
type intended to be used any time you need "a bunch of things together in some order". This is one of the most important and most unusual types in the language. WhenArray
was designed, it was believed (possibly by multiple people, but Jeff is willing to take the blame) that the easiest-to-use interface would be a single array type that does most of what users need. Many Julia programmers now feel that the language and community have outgrown this trade-off, and problems have emerged:Array
has many features, with an unusually large amount of functionality written in C. This makes the code harder to maintain and harder for the compiler to optimize.Array
is slower than it should be, since it is implemented with branches in C instead of compiler specialization.Array
struct layout is opaque, inhibiting optimizations and understanding. One notable example of this ispush!
which is expected to be fast almost all of the time, but has significant overhead when resizing the array is not needed.Array
, even if tolerable in most cases, is a significant cost when arrays are used to implement other types likeDict
.Array
has the following "fancy" features beyond the obvious function of storing a series of elements:Array
s to share data without copying. Optionally, we will callfree
on the pointer when theArray
is garbage collected.Array
can use aString
as storage to allow zero-copy string initialization.Here we propose a new lower-level storage type and to reimplement
Array
in Julia using it.New GenericMemory type
Also
Core.GenericPtr
andCore.AddrSpace
We propose a new type with the following representation:
The intention of this abstraction is to represent "a range of memory beginning at a fixed location". The memory space can either be directly after the metadata fields (in which case the
ptr
field is redundant) or refer to foreign memory via theptr
field. In both casesptr
points to the data. When the data is stored in-line, we predict no extra overhead from loading theptr
field since it shares a cache line with the data.Memory
has constructors similar to those of theVector
type.GenericMemory
generally does not have any constructors.The
kind
parameterThe
kind
type parameter specifies the required access style of the data, currently intended to support either atomic (:atomic
) or "plain" (:not_atomic
) access. The details and implementation for:atomic
have not yet been written. In brief though, this is expected to be similar to@atomic
fields, with implied:monotonic
order ongetindex
(unless specified), but explicit ordering required onsetindex!
. The alignment will be increased to support atomic access as required for the current hardware, and locks will be added (per element) if necessary.In the future, there are other possible uses for
kind
such as:const
.The
addrspace
parameterThe
addrspace
parameter is required to beCore.CPU
for allocations in main memory. It is present to permit extension by other systems such as GPUs, which might define values such as:The exact semantics of these other addrspaces is defined by the specific backend, but will error if the user is attempting to access these on the CPU.
Type parameter order
The order of the type parameters in
GenericMemory
might be surprising. We did it this way for two reasons:GenericMemory{T}
, but that would create an abstract type, which we want to discourage. TheMemory{T}
alias solves this.Resizing
To handle very large data, it is useful to expose the ability to expand a region of memory by extending the OS kernel's mapping. This is supported because one can construct a new
Memory
struct with a largerlength
which points to the same data as the originalMemory
. Growing blocks by moving, or any form of shrinking blocks, is left to higher-level abstractions to implement.Note this type does not and cannot implement
resize!
or related functions likepush!
, because it has different semantics.Several specific interface functions have been proposed here (names are provisional):
expand(mem, n)
: Grow size ton
elements if possible, otherwise do nothing. Returns a newMemory
ornothing
.expand_upto(mem, n)
: Grow size to at mostn
elements, possibly fewer, returning the new (or same)Memory
.New GenericMemoryRef type
The
GenericMemoryRef
type is similar to the existingRefArray
type, in that it points to a location within another object. Unlike that type, it has special codegen support so that the element offset computation can be elided.Note that
GenericMemoryRef
here is always a valid index (unlike C/C++/LLVM GEP which lets it point one element past the end), except when theGenericMemory
is empty, in which case accessing it can throw a BoundsError.New Builtin functions
The process of getting or setting elements is decomposed into two operations: constructing a
MemoryRef
, and then accessing it. The reason for this is that constructing aMemoryRef
is consistent and effect-free, but loading and storing elements are not. In particular the bounds check is part ofMemoryRef
construction, and these properties make code transformations easier.Deprecated builtin functions
arraylen
,arrayref
,arrayset
New
Array
definitionFor
N==1
, the axes andref
are mutable andaxes[1]
is the length. For otherN
, the fields areconst
, and the length is.ref.mem.length
orprod(.axes)
.String-backed arrays
Base.StringVector
returns aVector
whose storage is aString
, allowing conversion via theString
constructor without copying. This is used to initialize (immutable) strings efficiently. Declaring the data field ofArray
asUnion{Memory, String}
is not a good idea.We handle this by making an
Array
whose memory is backed by aMemory{UInt8}
whose storage is owned by a newString
.Important
When calling
String(::Vector)
, we set the Vector's length to 0 and replace itsMemory
with an empty one. TheString
-backedMemory
will still exist, but we assume code will not reference it directly.Implementation considerations
Dimension-dependent const-ness
Compilers always want to know when a value is constant.
Array
has the complication that the size is constant only when N≠1. Our layout system cannot express this, so it needs to be hacked into the compiler and will continue to be.Batched allocation of
Array
andMemory
This design requires allocating two objects (an Array and a Memory) for most arrays, giving it a slight disadvantage over the current implementation in some cases where the Array does not later change size. We think this could be improved by adding a feature to the GC to allocate two objects at once: (1) Space big enough to hold both objects is allocated, (2) A header bit in the first object indicates that it is followed by another, (3) The compiler recognizes this pattern:
and replaces it with a single allocation call that contains both objects. We do not currently believe this is necessary however.
Zero-sized
Memory
Because all zero-sized memory has no actual mutable content, we can instead allocate only one of these for all consumers. This can be stored in the
.instance
field when first allocated and reused for all future allocations as an optimization (like the zero-size String optimization).Secondly, this is the one case where
MemoryRef
may point to invalid memory, so we have a boundscheck parameter on read/write access to verify this case is not present.Element layout access
It is important (especially in the GC) to have fast access to the layout metadata for an array's element type. Thus, we set
Memory{T}.layout
to thelayout
of the element type but with the opaque layout flag set.unwrap_unionall(GenericMemory).layout
holds the layout metadata for theGenericMemory
type itself.Previously,
Array
did layout computations on array construction, which was one reason allocating arrays was surprisingly slow.Union
types were especially a problem since unlikeDataType
they do not have a field to store a layout.Allocation optimizations
We need to consider how this design will affect advanced array optimizations including SROA of arrays, allocation elision, and allocation hoisting. The two-layer object structure might make this more difficult.
GenericMemoryRef representation
For efficiency, the
GenericMemoryRef
data field is aPtr{Cvoid, AS}
to the referenced element for most arrays, but needs to be interpreted as an Int index for bitsunion and ghost value (elsize=0) elements. TheBase.memoryoffset
,pointer
, andCore.memoryref
functions can be used to convert between the two representations.New compiler features
The addrspace Loaded will now have a function form, which combines a Tracked pointer and a raw pointer to return a Loaded pointer.
The codegen for it will attempt to move this over some GEP call chains, and later the -llvm-julia-licm pass will do more of that as well.
Initial results
push!
is ~2.2x faster in the common caseInt[]
)Dict
,IDDict
, andBase64
's buffer type useMemory
to save space (forDict
it saves 72 bytes per instance).Memory
(80% faster for emptyDict{Int, Int}
)resize!
ing it. This makesBitSet()
roughly 2x faster.Vector
which was previously inaccessable memory to julia.