From 58ae50b7d61b7fb86038c8adfee0fc84745fffda Mon Sep 17 00:00:00 2001 From: araq Date: Mon, 9 Mar 2026 15:05:13 +0100 Subject: [PATCH] now with insane benchmark numbers --- compiler/ccgliterals.nim | 8 +++--- lib/std/syncio.nim | 3 +++ lib/system/strs_v3.nim | 58 +++++++++++++++++++++++++--------------- 3 files changed, 44 insertions(+), 25 deletions(-) diff --git a/compiler/ccgliterals.nim b/compiler/ccgliterals.nim index 6493461321..4d1b5b0c6e 100644 --- a/compiler/ccgliterals.nim +++ b/compiler/ccgliterals.nim @@ -225,10 +225,10 @@ proc genStringLiteralV3Const(m: BModule; n: PNode; isConst: bool; result: var Bu do: var di: StructInitializer res.addStructInitializer(di, kind = siOrderedStruct): - res.addField(di, name = "rc"): - res.addIntValue(1) res.addField(di, name = "fullLen"): res.addIntValue(s.len) + res.addField(di, name = "rc"): + res.addIntValue(1) res.addField(di, name = "capImpl"): res.addIntValue(0) # static, never freed res.addField(di, name = "data"): @@ -296,10 +296,10 @@ proc genStringLiteralV3(m: BModule; n: PNode; isConst: bool; result: var Builder do: var di: StructInitializer res.addStructInitializer(di, kind = siOrderedStruct): - res.addField(di, name = "rc"): - res.addIntValue(1) res.addField(di, name = "fullLen"): res.addIntValue(s.len) + res.addField(di, name = "rc"): + res.addIntValue(1) res.addField(di, name = "capImpl"): res.addIntValue(0) # bit 0 = 0: static, never freed res.addField(di, name = "data"): diff --git a/lib/std/syncio.nim b/lib/std/syncio.nim index 164b35666a..60e221c31e 100644 --- a/lib/std/syncio.nim +++ b/lib/std/syncio.nim @@ -486,6 +486,7 @@ proc readLine*(f: File, line: var string): bool {.tags: [ReadIOEffect], # fixes #9634; this pattern may need to be abstracted as a template if reused; # likely other io procs need this for correctness. fgetsSuccess = c_fgets(cast[cstring](addr line[pos]), sp.cint, f) != nil + when declared(completeStore): completeStore(line) if fgetsSuccess: break when not defined(nimscript): if errno == EINTR: @@ -565,6 +566,7 @@ proc readAllBuffer(file: File): string = var buffer = newString(BufSize) while true: var bytesRead = readBuffer(file, addr(buffer[0]), BufSize) + when declared(completeStore): completeStore(buffer) if bytesRead == BufSize: result.add(buffer) else: @@ -591,6 +593,7 @@ proc readAllFile(file: File, len: int64): string = # Speeds things up. result = newString(len) let bytes = readBuffer(file, addr(result[0]), len) + when declared(completeStore): completeStore(result) if endOfFile(file): if bytes.int64 < len: result.setLen(bytes) diff --git a/lib/system/strs_v3.nim b/lib/system/strs_v3.nim index 3e237e299a..f02c14d519 100644 --- a/lib/system/strs_v3.nim +++ b/lib/system/strs_v3.nim @@ -54,11 +54,23 @@ proc swarKey(x: uint): uint {.inline.} = # ---- accessors ---- # Memory layout is identical on both endiannesses: byte 0 = slen, bytes 1-7 = inline chars. # But the integer value of `bytes` differs: on LE slen is in the LSB, on BE in the MSB. -template ssLen(s: SmallString): int = + +template ssLenOf(bytes: uint): int = + ## Extract slen from an already-loaded `bytes` word. Zero-cost (register op only). + ## Use when `bytes` is already in a register (e.g. loaded for SWAR comparison). when system.cpuEndian == littleEndian: - int(s.bytes and 0xFF'u) + int(bytes and 0xFF'u) else: - int(s.bytes shr (8 * (sizeof(uint) - 1))) + int(bytes shr (8 * (sizeof(uint) - 1))) + +template ssLen(s: SmallString): int = + ## Load slen via a direct byte access. A byte load (movzx) lets the C compiler + ## prove that slen is at offset 0, distinct from inline char writes at offsets 1+, + ## enabling register-caching of slen across char-write loops (e.g. nimAddCharV1). + when system.cpuEndian == littleEndian: + int(cast[ptr byte](unsafeAddr s.bytes)[]) + else: + int(cast[ptr byte](cast[uint](unsafeAddr s.bytes) + uint(sizeof(uint) - 1))[]) template setSSLen(s: var SmallString; v: int) = # Single byte store — equivalent to old `s.slen = byte(v)`. @@ -121,13 +133,11 @@ template guts(s: SmallString): (int, ptr UncheckedArray[char]) = (slen, inlinePtr(s)) proc nimStrAtV3*(s: var SmallString; i: int): char {.compilerproc, inline.} = - let slen = ssLen(s) - if slen <= PayloadSize: - # unchecked: when i >= 7 we store into the `more` overlay + if ssLen(s) <= PayloadSize: + # short/medium: data is in the inline bytes overlay result = inlinePtr(s)[i] - elif i < AlwaysAvail: - result = inlinePtr(s)[i] # hot prefix: no heap dereference for the first 7 bytes else: + # long: always use heap data (completeStore keeps more.data canonical) result = s.more.data[i] proc nimStrPutV3*(s: var SmallString; i: int; c: char) {.compilerproc, inline.} = @@ -153,14 +163,16 @@ proc cmpInlineBytes(a, b: ptr UncheckedArray[char]; n: int): int {.inline.} = proc cmpStringPtrs(a, b: ptr SmallString): int {.inline.} = # Compare two SmallStrings by pointer to avoid struct copies in the hot path. - let aslen = a[].ssLen - let bslen = b[].ssLen + let abytes = a.bytes + let bbytes = b.bytes + let aslen = ssLenOf(abytes) + let bslen = ssLenOf(bbytes) if aslen <= AlwaysAvail and bslen <= AlwaysAvail: # SWAR path: both short (≤7 bytes). All data lives in the `bytes` field. # Zeroed-padding invariant ensures bytes past the null are 0. # swarKey puts char[0] in the MSB → integer comparison is lexicographic. - let aw = swarKey(a.bytes) - let bw = swarKey(b.bytes) + let aw = swarKey(abytes) + let bw = swarKey(bbytes) if aw < bw: return -1 if aw > bw: return 1 return aslen - bslen @@ -197,24 +209,28 @@ proc cmpStringPtrs(a, b: ptr SmallString): int {.inline.} = if result == 0: result = la - lb proc cmp(a, b: SmallString): int {.inline.} = - # For short strings: stay in registers — no address-taking, no stack spill. - let aslen = ssLen(a) - let bslen = ssLen(b) + # Load bytes once per string — used for both slen check and SWAR key. + let abytes = a.bytes + let bbytes = b.bytes + let aslen = ssLenOf(abytes) + let bslen = ssLenOf(bbytes) if aslen <= AlwaysAvail and bslen <= AlwaysAvail: - let aw = swarKey(a.bytes) - let bw = swarKey(b.bytes) + let aw = swarKey(abytes) + let bw = swarKey(bbytes) if aw < bw: return -1 if aw > bw: return 1 return aslen - bslen cmpStringPtrs(unsafeAddr a, unsafeAddr b) proc `==`(a, b: SmallString): bool {.inline.} = - if ssLen(a) != ssLen(b): return false - let slen = ssLen(a) + let abytes = a.bytes + let bbytes = b.bytes + let slen = ssLenOf(abytes) + if slen != ssLenOf(bbytes): return false if slen <= AlwaysAvail: - return a.bytes == b.bytes # SWAR: single word comparison + return abytes == bbytes # SWAR: single word comparison # slen > AlwaysAvail: compare inline prefix word (contains slen + chars 0-6) - if a.bytes != b.bytes: return false + if abytes != bbytes: return false if slen <= PayloadSize: let (la, pa) = a.guts let (_, pb) = b.guts