perf: RTL optimization — cached WA, spaces pool, stack-alloc fmt_int64

rdd.go:
- getWA() cached type assertion (avoid repeated interface check)
- waCache stores last WA pointer → O(1) for repeated calls

strings.go:
- spacesCache[257]: pre-built space strings for pad sizes 0-256
- spaces(n) returns cached string (no Repeat allocation)
- PadR/PadL use spaces() for fill=" " (most common case)
- Str() uses spaces() for right-padding

missing.go:
- fmt_int64: stack-allocated [20]byte array (was heap make([]byte))
- Reverse iteration (no prepend overhead)
- PadC uses spaces() for left/right padding

Benchmark (ext4, home dir):
  10K APPEND: 28ms → 26ms (Harbour 27ms!)
  50K APPEND: 130ms → 113ms (13% improvement)
  50K SCAN: 24ms → 23ms
  50K DUPKEY: 42ms → 35ms (17% improvement)
  CDX SCOPE: 12ms → 10ms (17% improvement)

82/82 stress PASS. 14 packages ALL PASS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-07 18:16:22 +09:00
parent 48cd4f9e5c
commit 5b378318a0
3 changed files with 56 additions and 13 deletions

View File

@@ -128,7 +128,7 @@ func PadC(t *hbrt.Thread) {
} else {
leftPad := (n - len(s)) / 2
rightPad := n - len(s) - leftPad
t.PushString(strings.Repeat(" ", leftPad) + s + strings.Repeat(" ", rightPad))
t.PushString(spaces(leftPad) + s + spaces(rightPad))
}
t.RetValue()
}
@@ -438,13 +438,16 @@ func fmt_int64(n int64) string {
if neg {
n = -n
}
buf := make([]byte, 0, 20)
var buf [20]byte // stack allocation, no heap
i := len(buf)
for n > 0 {
buf = append([]byte{byte('0' + n%10)}, buf...)
i--
buf[i] = byte('0' + n%10)
n /= 10
}
if neg {
buf = append([]byte{'-'}, buf...)
i--
buf[i] = '-'
}
return string(buf)
return string(buf[i:])
}

View File

@@ -2,7 +2,7 @@
// All rights reserved.
// RDD-related RTL functions: EOF(), BOF(), Found(), RecNo(), RecCount(), Deleted().
// These read the current workarea state from Thread.WA.
// Optimized: no Frame/EndProc for 0-param functions (called millions of times in loops).
package hbrtl
import (
@@ -92,14 +92,12 @@ func rtlDeleted(t *hbrt.Thread) {
}
func rtlFieldGet(t *hbrt.Thread) {
// FIELD->name is handled by gengo codegen, not this function.
// This is for FieldGet(n) function call.
t.Frame(1, 0)
defer t.EndProc()
n := int(t.Local(1).AsNumInt())
if wa := getWA(t); wa != nil {
if area := wa.Current(); area != nil {
val, err := area.GetValue(n - 1) // 1-based to 0-based
val, err := area.GetValue(n - 1) // 1-based 0-based
if err == nil {
t.PushValue(val)
t.RetValue()
@@ -111,13 +109,24 @@ func rtlFieldGet(t *hbrt.Thread) {
t.RetValue()
}
// getWA returns the WorkAreaManager with cached type assertion.
var waCache = struct {
iface interface{}
wam *hbrdd.WorkAreaManager
}{}
func getWA(t *hbrt.Thread) *hbrdd.WorkAreaManager {
if t.WA == nil {
return nil
}
if t.WA == waCache.iface {
return waCache.wam
}
wa, ok := t.WA.(*hbrdd.WorkAreaManager)
if !ok {
return nil
}
waCache.iface = t.WA
waCache.wam = wa
return wa
}

View File

@@ -12,6 +12,27 @@ import (
"strings"
)
// spacesCache: pre-built space strings for common pad sizes.
// Avoids strings.Repeat(" ", n) allocation in hot paths.
var spacesCache [257]string
func init() {
for i := range spacesCache {
spacesCache[i] = strings.Repeat(" ", i)
}
}
// spaces returns a string of n spaces, using cache for n <= 256.
func spaces(n int) string {
if n <= 0 {
return ""
}
if n < len(spacesCache) {
return spacesCache[n]
}
return strings.Repeat(" ", n)
}
// Str converts a numeric value to a string.
// Harbour: Str(nValue [, nWidth [, nDec]]) → cString
func Str(t *hbrt.Thread) {
@@ -49,7 +70,7 @@ func Str(t *hbrt.Thread) {
s := fmt.Sprintf("%*.*f", width, dec, d)
// Harbour pads with spaces if shorter
if len(s) < width {
s = strings.Repeat(" ", width-len(s)) + s
s = spaces(width-len(s)) + s
}
// Harbour returns asterisks if wider than width
if len(s) > width && width > 0 {
@@ -247,7 +268,7 @@ func Space(t *hbrt.Thread) {
if n < 0 {
n = 0
}
t.PushString(strings.Repeat(" ", int(n)))
t.PushString(spaces(int(n)))
t.RetValue()
}
@@ -268,7 +289,12 @@ func PadR(t *hbrt.Thread) {
if len(s) >= n {
t.PushString(s[:n])
} else {
t.PushString(s + strings.Repeat(fill, n-len(s)))
pad := n - len(s)
if fill == " " {
t.PushString(s + spaces(pad))
} else {
t.PushString(s + strings.Repeat(fill, pad))
}
}
t.RetValue()
}
@@ -290,7 +316,12 @@ func PadL(t *hbrt.Thread) {
if len(s) >= n {
t.PushString(s[len(s)-n:])
} else {
t.PushString(strings.Repeat(fill, n-len(s)) + s)
pad := n - len(s)
if fill == " " {
t.PushString(spaces(pad) + s)
} else {
t.PushString(strings.Repeat(fill, pad) + s)
}
}
t.RetValue()
}