-
Notifications
You must be signed in to change notification settings - Fork 23
Description
Motivation: Make naive string handling code faster
In the ARC/ORC implementation string literals are already not copied and not destroyed. When these strings are potentially mutated nimPrepareStrMutationV2
is called to copy the constant string into a buffer that allows for mutations. Currently the condition (s.p.cap and strlitFlag) == strlitFlag
is used to determine whether s.p
points to a constant string.
In Delphi/FPC a refcount of -1 is used instead to allow for a more general COW mechanism. This RFC proposes to copy this design as much everyday Nim code is written without the current expensive copies in mind, e.g.:
proc getName(p: Person): string = p.name
This is particularly troublesome for async
procs where the snippet env.field = param
is introduced so that parameters can be captured inside closures. However, a sink
parameter probably has the same benefit.
As a side effect Nim's ARC strings can support the old GC_ref
/ GC_unref
calls.
Expected compat problems
It means that cast[seq[char]](mystr)
stops working. We can detect this case in the compiler
and fusion / compat
can introduce castToSeq
/ castToString
cast operations that
work for all runtimes.
Alternatives/additions considered but rejected (for now)
Make Nim's strings use C++'s SSO
SSO looks like a rather fragile questionable optimization, so copying short strings around doesn't involve touching the heap, yet long strings are as slow as before to copy? And almost every operation internally needs to distinguish between "is this a long or a shorts string". Also, in Nim filenames/paths are currently not a dedicated type and directly stored as string too; most paths are longer than what is covered by the "short string" representation.
Make Nim's string slices O(1)
People coming from Go (and for some reason also people coming from Python) expect substr
and s[1..3]
to be faster than they really are. In the old runtime this was even worse as string allocations cause the occasional GC run. In the current new runtime the allocations remain. If the string implementation is changed from
type
NimStringV2 {.core.} = object
len: int
p: ptr NimStrPayload ## can be nil if len == 0.
to
type
NimStringV2 {.core.} = object
len, offset: int
p: ptr NimStrPayload ## can be nil if len == 0.
The slice operation can be done in O(1) time. However, every slice creation would
cause a inc(s.p.refcount)
operation and also involve a corresponding destruction step. This
probably means that it's not fast enough in practice and so our effort is better spent on
using more openArray[char]
with borrowing rules instead. This also has the benefit that short string slices cannot keep a multi megabyte sized string buffer alive for too long; a problem
big enough in the JVM world that they moved from O(1) slicing to O(N) slicing in a patch release.
Implementation effort
- Patch
strs_v2.nim
- Patch the Nim C code generator so that string literals have a refcount field too.
- Run some benchmarks.