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:
@@ -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:])
|
||||
}
|
||||
|
||||
17
hbrtl/rdd.go
17
hbrtl/rdd.go
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user