performance: Misc allocation improvements

```
                               │ stash.bench  │      perf-allloc20251118.bench      │
                               │    sec/op    │    sec/op     vs base               │
CreateShortcodePlaceholders-10   54.16n ± ∞ ¹   39.06n ± ∞ ¹  -27.87% (p=0.029 n=4)
¹ need >= 6 samples for confidence interval at level 0.95

                               │ stash.bench │     perf-allloc20251118.bench     │
                               │    B/op     │    B/op      vs base              │
CreateShortcodePlaceholders-10   52.00 ± ∞ ¹   48.00 ± ∞ ¹  -7.69% (p=0.029 n=4)
¹ need >= 6 samples for confidence interval at level 0.95

                               │ stash.bench │     perf-allloc20251118.bench      │
                               │  allocs/op  │  allocs/op   vs base               │
CreateShortcodePlaceholders-10   2.000 ± ∞ ¹   1.000 ± ∞ ¹  -50.00% (p=0.029 n=4)
¹ need >= 6 samples for confidence interval at level 0.95
````
This commit is contained in:
Bjørn Erik Pedersen
2025-11-18 13:58:19 +01:00
parent fee095768c
commit f33c1a3fdb
6 changed files with 48 additions and 23 deletions
+1 -1
View File
@@ -86,7 +86,7 @@ type HugoSites struct {
// Cache for page listings. // Cache for page listings.
cachePages *dynacache.Partition[string, page.Pages] cachePages *dynacache.Partition[string, page.Pages]
// Cache for content sources. // Cache for content sources.
cacheContentSource *dynacache.Partition[string, *resources.StaleValue[[]byte]] cacheContentSource *dynacache.Partition[uint64, *resources.StaleValue[[]byte]]
// Before Hugo 0.122.0 we managed all translations in a map using a translationKey // Before Hugo 0.122.0 we managed all translations in a map using a translationKey
// that could be overridden in front matter. // that could be overridden in front matter.
+9 -18
View File
@@ -19,7 +19,6 @@ import (
"fmt" "fmt"
"html/template" "html/template"
"io" "io"
"strconv"
"strings" "strings"
"sync/atomic" "sync/atomic"
"unicode/utf8" "unicode/utf8"
@@ -69,12 +68,6 @@ func (m *pageMetaSource) parseFrontMatter(
h *HugoSites, h *HugoSites,
sid uint64, sid uint64,
) error { ) error {
var sourceKey string
if sourceKey == "" {
sourceKey = strconv.FormatUint(sid, 10)
}
var filename string var filename string
if m.f != nil { if m.f != nil {
filename = m.f.Filename() filename = m.f.Filename()
@@ -83,7 +76,6 @@ func (m *pageMetaSource) parseFrontMatter(
m.pi = &contentParseInfo{ m.pi = &contentParseInfo{
h: h, h: h,
sid: sid, sid: sid,
sourceKey: sourceKey,
openSource: m.openSource, openSource: m.openSource,
shortcodeParseInfo: newShortcodeHandler(filename, h.Deps), shortcodeParseInfo: newShortcodeHandler(filename, h.Deps),
} }
@@ -163,8 +155,7 @@ func (c *cachedContent) getOrCreateScope(scope string, pco *pageContentOutput) *
type contentParseInfo struct { type contentParseInfo struct {
h *HugoSites h *HugoSites
sid uint64 sid uint64
sourceKey string
// The source bytes. // The source bytes.
openSource hugio.OpenReadSeekCloser openSource hugio.OpenReadSeekCloser
@@ -323,7 +314,7 @@ Loop:
currShortcode.pos = it.Pos() currShortcode.pos = it.Pos()
currShortcode.length = iter.Current().Pos() - it.Pos() currShortcode.length = iter.Current().Pos() - it.Pos()
if currShortcode.placeholder == "" { if currShortcode.placeholder == "" {
currShortcode.placeholder = createShortcodePlaceholder("s", pi.sid, currShortcode.ordinal) currShortcode.placeholder = createShortcodePlaceholder("s", pi.sid, uint64(currShortcode.ordinal))
} }
if currShortcode.name != "" { if currShortcode.name != "" {
@@ -335,7 +326,7 @@ Loop:
currShortcode.params = s currShortcode.params = s
} }
currShortcode.placeholder = createShortcodePlaceholder("s", pi.sid, ordinal) currShortcode.placeholder = createShortcodePlaceholder("s", pi.sid, uint64(ordinal))
ordinal++ ordinal++
s.shortcodes = append(s.shortcodes, currShortcode) s.shortcodes = append(s.shortcodes, currShortcode)
@@ -390,10 +381,10 @@ func (c *cachedContent) mustSource() []byte {
} }
func (pi *contentParseInfo) contentSource(s resource.StaleInfo) ([]byte, error) { func (pi *contentParseInfo) contentSource(s resource.StaleInfo) ([]byte, error) {
key := pi.sourceKey key := pi.sid
versionv := s.StaleVersion() versionv := s.StaleVersion()
v, err := pi.h.cacheContentSource.GetOrCreate(key, func(string) (*resources.StaleValue[[]byte], error) { v, err := pi.h.cacheContentSource.GetOrCreate(key, func(uint64) (*resources.StaleValue[[]byte], error) {
b, err := pi.readSourceAll() b, err := pi.readSourceAll()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -457,13 +448,13 @@ type contentPlainPlainWords struct {
} }
func (c *cachedContentScope) keyScope(ctx context.Context) string { func (c *cachedContentScope) keyScope(ctx context.Context) string {
return hugo.GetMarkupScope(ctx) + c.pco.po.f.Name return hugo.GetMarkupScope(ctx) + c.pco.po.f.Name + fmt.Sprintf("_%d", c.pi.sid)
} }
func (c *cachedContentScope) contentRendered(ctx context.Context) (contentSummary, error) { func (c *cachedContentScope) contentRendered(ctx context.Context) (contentSummary, error) {
cp := c.pco cp := c.pco
ctx = tpl.Context.DependencyScope.Set(ctx, pageDependencyScopeGlobal) ctx = tpl.Context.DependencyScope.Set(ctx, pageDependencyScopeGlobal)
key := c.pi.sourceKey + "/" + c.keyScope(ctx) key := c.keyScope(ctx)
versionv := c.version(cp) versionv := c.version(cp)
@@ -616,7 +607,7 @@ var setGetContentCallbackInContext = contexthelpers.NewContextDispatcher[func(*p
func (c *cachedContentScope) contentToC(ctx context.Context) (contentTableOfContents, error) { func (c *cachedContentScope) contentToC(ctx context.Context) (contentTableOfContents, error) {
cp := c.pco cp := c.pco
key := c.pi.sourceKey + "/" + c.keyScope(ctx) key := c.keyScope(ctx)
versionv := c.version(cp) versionv := c.version(cp)
v, err := c.pm.contentTableOfContents.GetOrCreate(key, func(string) (*resources.StaleValue[contentTableOfContents], error) { v, err := c.pm.contentTableOfContents.GetOrCreate(key, func(string) (*resources.StaleValue[contentTableOfContents], error) {
@@ -737,7 +728,7 @@ func (c *cachedContent) version(cp *pageContentOutput) uint32 {
func (c *cachedContentScope) contentPlain(ctx context.Context) (contentPlainPlainWords, error) { func (c *cachedContentScope) contentPlain(ctx context.Context) (contentPlainPlainWords, error) {
cp := c.pco cp := c.pco
key := c.pi.sourceKey + "/" + c.keyScope(ctx) key := c.keyScope(ctx)
versionv := c.version(cp) versionv := c.version(cp)
+2 -2
View File
@@ -192,8 +192,8 @@ func (scp *ShortcodeWithPage) Unwrapv() any {
// Note - this value must not contain any markup syntax // Note - this value must not contain any markup syntax
const shortcodePlaceholderPrefix = "HAHAHUGOSHORTCODE" const shortcodePlaceholderPrefix = "HAHAHUGOSHORTCODE"
func createShortcodePlaceholder(sid string, id uint64, ordinal int) string { func createShortcodePlaceholder(sid string, id, ordinal uint64) string {
return shortcodePlaceholderPrefix + strconv.FormatUint(id, 10) + sid + strconv.Itoa(ordinal) + "HBHB" return shortcodePlaceholderPrefix + strconv.FormatUint(id, 10) + sid + strconv.FormatUint(ordinal, 10) + "HBHB"
} }
type shortcode struct { type shortcode struct {
+23
View File
@@ -0,0 +1,23 @@
// Copyright 2025 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hugolib
import "testing"
func BenchmarkCreateShortcodePlaceholders(b *testing.B) {
var ordinal int = 42
for b.Loop() {
createShortcodePlaceholder("shortcodeName", 32, uint64(ordinal))
}
}
+1 -1
View File
@@ -451,7 +451,7 @@ func newHugoSites(
page.Pages](d.MemCache, "/pags/all", page.Pages](d.MemCache, "/pags/all",
dynacache.OptionsPartition{Weight: 10, ClearWhen: dynacache.ClearOnRebuild}, dynacache.OptionsPartition{Weight: 10, ClearWhen: dynacache.ClearOnRebuild},
), ),
cacheContentSource: dynacache.GetOrCreatePartition[string, *resources.StaleValue[[]byte]](d.MemCache, "/cont/src", dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}), cacheContentSource: dynacache.GetOrCreatePartition[uint64, *resources.StaleValue[[]byte]](d.MemCache, "/cont/src", dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}),
translationKeyPages: maps.NewSliceCache[page.Page](), translationKeyPages: maps.NewSliceCache[page.Page](),
currentSite: first[0], currentSite: first[0],
skipRebuildForFilenames: make(map[string]bool), skipRebuildForFilenames: make(map[string]bool),
+12 -1
View File
@@ -398,11 +398,22 @@ func BenchmarkSets(b *testing.B) {
} }
}) })
b.Run("HasAnyVector", func(b *testing.B) { b.Run("HasAnyVector(Sets)", func(b *testing.B) {
for b.Loop() { for b.Loop() {
_ = sets1.HasAnyVector(sets2) _ = sets1.HasAnyVector(sets2)
} }
}) })
b.Run("HasAnyVector(Vector)", func(b *testing.B) {
for b.Loop() {
_ = sets1.HasAnyVector(v1)
}
})
b.Run("HasAnyVector(&Vector)", func(b *testing.B) {
for b.Loop() {
_ = sets1.HasAnyVector(&v1)
}
})
b.Run("FirstVector", func(b *testing.B) { b.Run("FirstVector", func(b *testing.B) {
for b.Loop() { for b.Loop() {