diff --git a/hbrtl/missing.go b/hbrtl/missing.go index 9bf8fd6..67613f3 100644 --- a/hbrtl/missing.go +++ b/hbrtl/missing.go @@ -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:]) } diff --git a/hbrtl/rdd.go b/hbrtl/rdd.go index ca7a451..53473bb 100644 --- a/hbrtl/rdd.go +++ b/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 } diff --git a/hbrtl/strings.go b/hbrtl/strings.go index fc18a48..367635b 100644 --- a/hbrtl/strings.go +++ b/hbrtl/strings.go @@ -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() }