mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-24 07:18:54 +00:00
Optimize memory allocations for sites matrix vector stores
By
* Caching common sites matrix setups (e.g. the single site in single site setups).
* Adding a fast path to IntSets.HasAnyVector for the common case of single vector input.
```
AssembleDeepSiteWithManySections/depth=3/sectionsPerLevel=2/pagesPerSection=100-10 31.62m ± 46% 30.68m ± 42% ~ (p=0.310 n=6)
│ master.bench │ perfcommon.bench │
│ B/op │ B/op vs base │
AssembleDeepSiteWithManySections/depth=3/sectionsPerLevel=2/pagesPerSection=100-10 31.98Mi ± 0% 31.24Mi ± 0% -2.30% (p=0.002 n=6)
│ master.bench │ perfcommon.bench │
│ allocs/op │ allocs/op vs base │
AssembleDeepSiteWithManySections/depth=3/sectionsPerLevel=2/pagesPerSection=100-10 460.9k ± 0% 419.9k ± 0% -8.90% (p=0.002 n=6)
````
This commit is contained in:
@@ -54,7 +54,7 @@ type PathParser struct {
|
||||
|
||||
// Below gets created on demand.
|
||||
initOnce sync.Once
|
||||
sitesMatrixCache *maps.Cache[string, sitesmatrix.VectorStore] // Maps language code to sites matrix vector store.
|
||||
sitesMatrixCache *maps.Cache[string, sitesmatrix.VectorStore] // Maps language index to sites matrix vector store.
|
||||
}
|
||||
|
||||
func (pp *PathParser) init() {
|
||||
|
||||
@@ -878,6 +878,10 @@ func (c *Configs) Init(logger loggers.Logger) error {
|
||||
ConfiguredRoles: c.Base.Roles.Config,
|
||||
}
|
||||
|
||||
if err := c.ConfiguredDimensions.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
intSetsCfg := sitesmatrix.IntSetsConfig{
|
||||
ApplyDefaults: sitesmatrix.IntSetsConfigApplyDefaultsIfNotSet,
|
||||
}
|
||||
|
||||
@@ -575,7 +575,7 @@ func (a *allPagesAssembler) doCreatePages(prefix string) error {
|
||||
func() error {
|
||||
if i := len(missingVectorsForHomeOrRootSection); i > 0 {
|
||||
// Pick one, the rest will be created later.
|
||||
vec := missingVectorsForHomeOrRootSection.Sample()
|
||||
vec := missingVectorsForHomeOrRootSection.VectorSample()
|
||||
|
||||
kind := kinds.KindSection
|
||||
if s == "" {
|
||||
|
||||
@@ -426,12 +426,25 @@ func (n contentNodesMap) sample() contentNode {
|
||||
}
|
||||
|
||||
func (n contentNodesMap) siteVectors() sitesmatrix.VectorIterator {
|
||||
return sitesmatrix.VectorIteratorFunc(func(yield func(v sitesmatrix.Vector) bool) bool {
|
||||
for k := range n {
|
||||
if !yield(k) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
return n
|
||||
}
|
||||
|
||||
func (n contentNodesMap) ForEachVector(yield func(v sitesmatrix.Vector) bool) bool {
|
||||
for v := range n {
|
||||
if !yield(v) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (n contentNodesMap) LenVectors() int {
|
||||
return len(n)
|
||||
}
|
||||
|
||||
func (n contentNodesMap) VectorSample() sitesmatrix.Vector {
|
||||
for v := range n {
|
||||
return v
|
||||
}
|
||||
panic("no vectors")
|
||||
}
|
||||
|
||||
@@ -76,6 +76,10 @@ type RolesInternal struct {
|
||||
Sorted []RoleInternal
|
||||
}
|
||||
|
||||
func (r RolesInternal) Len() int {
|
||||
return len(r.Sorted)
|
||||
}
|
||||
|
||||
func (r RolesInternal) IndexDefault() int {
|
||||
for i, role := range r.Sorted {
|
||||
if role.Default {
|
||||
|
||||
@@ -76,9 +76,13 @@ func (v1 Vector) HasVector(v2 Vector) bool {
|
||||
}
|
||||
|
||||
func (v1 Vector) HasAnyVector(vp VectorProvider) bool {
|
||||
if vp.LenVectors() == 0 {
|
||||
n := vp.LenVectors()
|
||||
if n == 0 {
|
||||
return false
|
||||
}
|
||||
if n == 1 {
|
||||
return v1 == vp.VectorSample()
|
||||
}
|
||||
|
||||
return !vp.ForEachVector(func(v2 Vector) bool {
|
||||
if v1 == v2 {
|
||||
@@ -144,12 +148,16 @@ func (vs Vectors) ForEachVector(yield func(v Vector) bool) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (vs Vectors) LenVectors() int {
|
||||
return len(vs)
|
||||
}
|
||||
|
||||
func (vs Vectors) ToVectorStore() VectorStore {
|
||||
return newVectorStoreMapFromVectors(vs)
|
||||
}
|
||||
|
||||
// Sample returns one of the vectors in the set.
|
||||
func (vs Vectors) Sample() Vector {
|
||||
// VectorSample returns one of the vectors in the set.
|
||||
func (vs Vectors) VectorSample() Vector {
|
||||
for v := range vs {
|
||||
return v
|
||||
}
|
||||
@@ -161,15 +169,16 @@ type (
|
||||
// ForEachVector iterates over all vectors in the provider.
|
||||
// It returns false if the iteration was stopped early.
|
||||
ForEachVector(func(v Vector) bool) bool
|
||||
|
||||
// LenVectors returns the number of vectors in the provider.
|
||||
LenVectors() int
|
||||
|
||||
// VectorSample returns one of the vectors in the provider, usually the first or the only one.
|
||||
// This will panic if the provider is empty.
|
||||
VectorSample() Vector
|
||||
}
|
||||
)
|
||||
|
||||
type VectorIteratorFunc func(func(v Vector) bool) bool
|
||||
|
||||
func (f VectorIteratorFunc) ForEachVector(yield func(v Vector) bool) bool {
|
||||
return f(yield)
|
||||
}
|
||||
|
||||
// Bools holds boolean values for each dimension in the Hugo build matrix.
|
||||
type Bools [3]bool
|
||||
|
||||
@@ -198,13 +207,6 @@ type VectorProvider interface {
|
||||
// HasAnyVector returns true if any of the vectors in the provider matches any of the vectors in v.
|
||||
HasAnyVector(v VectorProvider) bool
|
||||
|
||||
// LenVectors returns the number of vectors in the provider.
|
||||
LenVectors() int
|
||||
|
||||
// VectorSample returns one of the vectors in the provider, usually the first or the only one.
|
||||
// This will panic if the provider is empty.
|
||||
VectorSample() Vector
|
||||
|
||||
// Equals returns true if this provider is equal to the other provider.
|
||||
EqualsVector(other VectorProvider) bool
|
||||
}
|
||||
@@ -227,6 +229,22 @@ type ToVectorStoreProvider interface {
|
||||
ToVectorStore() VectorStore
|
||||
}
|
||||
|
||||
func VectorIteratorToStore(vi VectorIterator) VectorStore {
|
||||
switch v := vi.(type) {
|
||||
case VectorStore:
|
||||
return v
|
||||
case ToVectorStoreProvider:
|
||||
return v.ToVectorStore()
|
||||
}
|
||||
|
||||
vectors := make(Vectors)
|
||||
vi.ForEachVector(func(v Vector) bool {
|
||||
vectors[v] = struct{}{}
|
||||
return true
|
||||
})
|
||||
return vectors.ToVectorStore()
|
||||
}
|
||||
|
||||
type weightedVectorStore struct {
|
||||
VectorStore
|
||||
weight int
|
||||
|
||||
@@ -71,10 +71,6 @@ func (s *vectorStoreMap) setVector(vec Vector) {
|
||||
s.sets[vec] = struct{}{}
|
||||
}
|
||||
|
||||
func (s *vectorStoreMap) Ordinal() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *vectorStoreMap) KeysSorted() ([]int, []int, []int) {
|
||||
var k0, k1, k2 []int
|
||||
for v := range s.sets {
|
||||
@@ -271,12 +267,45 @@ type ConfiguredDimension interface {
|
||||
ResolveIndex(string) int
|
||||
ResolveName(int) string
|
||||
ForEachIndex() iter.Seq[int]
|
||||
Len() int
|
||||
}
|
||||
|
||||
// ConfiguredDimensions holds the configured dimensions for the site matrix.
|
||||
type ConfiguredDimensions struct {
|
||||
ConfiguredLanguages ConfiguredDimension
|
||||
ConfiguredVersions ConfiguredDimension
|
||||
ConfiguredRoles ConfiguredDimension
|
||||
CommonSitesMatrix CommonSitestMatrix
|
||||
|
||||
singleVectorStoreCache *maps.Cache[Vector, *IntSets]
|
||||
}
|
||||
|
||||
func (c *ConfiguredDimensions) IsSingleVector() bool {
|
||||
return c.ConfiguredLanguages.Len() == 1 && c.ConfiguredRoles.Len() == 1 && c.ConfiguredVersions.Len() == 1
|
||||
}
|
||||
|
||||
// GetOrCreateSingleVectorStore returns a VectorStore for the given vector.
|
||||
func (c *ConfiguredDimensions) GetOrCreateSingleVectorStore(vec Vector) *IntSets {
|
||||
store, _ := c.singleVectorStoreCache.GetOrCreate(vec, func() (*IntSets, error) {
|
||||
is := &IntSets{}
|
||||
is.setValuesInNilSets(vec, true, true, true)
|
||||
return is, nil
|
||||
})
|
||||
return store
|
||||
}
|
||||
|
||||
func (c *ConfiguredDimensions) Init() error {
|
||||
c.singleVectorStoreCache = maps.NewCache[Vector, *IntSets]()
|
||||
b := NewIntSetsBuilder(c).WithDefaultsIfNotSet().Build()
|
||||
defaultVec := b.VectorSample()
|
||||
c.singleVectorStoreCache.Set(defaultVec, b)
|
||||
c.CommonSitesMatrix.DefaultSite = b
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type CommonSitestMatrix struct {
|
||||
DefaultSite VectorStore
|
||||
}
|
||||
|
||||
func (c *ConfiguredDimensions) ResolveNames(v Vector) types.Strings3 {
|
||||
@@ -317,6 +346,8 @@ type IntSets struct {
|
||||
h *hashOnce
|
||||
}
|
||||
|
||||
var NilStore *IntSets = nil
|
||||
|
||||
type hashOnce struct {
|
||||
once sync.Once
|
||||
hash uint64
|
||||
@@ -363,7 +394,7 @@ func (s *IntSets) Intersects(other *IntSets) bool {
|
||||
// Complement returns a new VectorStore that contains all vectors in s that are not in any of ss.
|
||||
func (s *IntSets) Complement(ss ...VectorProvider) VectorStore {
|
||||
if len(ss) == 0 || (len(ss) == 1 && ss[0] == s) {
|
||||
return nil
|
||||
return NilStore
|
||||
}
|
||||
|
||||
for _, v := range ss {
|
||||
@@ -372,8 +403,7 @@ func (s *IntSets) Complement(ss ...VectorProvider) VectorStore {
|
||||
continue
|
||||
}
|
||||
if vv.IsSuperSet(s) {
|
||||
var s *IntSets
|
||||
return s
|
||||
return NilStore
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,6 +514,9 @@ func (s *IntSets) HasLanguage(lang int) bool {
|
||||
return s.languages.Has(lang)
|
||||
}
|
||||
|
||||
// LenVectors returns the total number of vectors represented by the IntSets.
|
||||
// This is the Cartesian product of the lengths of the individual sets.
|
||||
// This will be 0 if s is nil or any of the sets is empty.
|
||||
func (s *IntSets) LenVectors() int {
|
||||
if s == nil {
|
||||
return 0
|
||||
@@ -526,6 +559,10 @@ func (s *IntSets) HasAnyVector(v VectorProvider) bool {
|
||||
if s.LenVectors() == 0 || v.LenVectors() == 0 {
|
||||
return false
|
||||
}
|
||||
if v.LenVectors() == 1 {
|
||||
// Fast path.
|
||||
return s.HasVector(v.VectorSample())
|
||||
}
|
||||
|
||||
if vs, ok := v.(*IntSets); ok {
|
||||
// Fast path.
|
||||
@@ -688,6 +725,14 @@ type IntSetsBuilder struct {
|
||||
|
||||
func (b *IntSetsBuilder) Build() *IntSets {
|
||||
b.s.init()
|
||||
|
||||
if b.s.LenVectors() == 1 {
|
||||
// Cache it or use the existing cached version, which will allow b.s to be GCed.
|
||||
bb, _ := b.cfg.singleVectorStoreCache.GetOrCreate(b.s.VectorSample(), func() (*IntSets, error) {
|
||||
return b.s, nil
|
||||
})
|
||||
return bb
|
||||
}
|
||||
return b.s
|
||||
}
|
||||
|
||||
@@ -889,6 +934,10 @@ type testDimension struct {
|
||||
names []string
|
||||
}
|
||||
|
||||
func (m testDimension) Len() int {
|
||||
return len(m.names)
|
||||
}
|
||||
|
||||
func (m testDimension) IndexDefault() int {
|
||||
return 0
|
||||
}
|
||||
@@ -933,9 +982,13 @@ func (m *testDimension) IndexMatch(match predicate.P[string]) (iter.Seq[int], er
|
||||
|
||||
// NewTestingDimensions creates a new ConfiguredDimensions for testing.
|
||||
func NewTestingDimensions(languages, versions, roles []string) *ConfiguredDimensions {
|
||||
return &ConfiguredDimensions{
|
||||
c := &ConfiguredDimensions{
|
||||
ConfiguredLanguages: &testDimension{names: languages},
|
||||
ConfiguredVersions: &testDimension{names: versions},
|
||||
ConfiguredRoles: &testDimension{names: roles},
|
||||
}
|
||||
if err := c.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -14,20 +14,21 @@
|
||||
package sitesmatrix_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/bits-and-blooms/bitset"
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/common/hashing"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/hugolib/sitesmatrix"
|
||||
)
|
||||
|
||||
var testDims = sitesmatrix.NewTestingDimensions([]string{"en", "no"}, []string{"v1", "v2", "v3"}, []string{"admin", "editor", "viewer", "guest"})
|
||||
func newTestDims() *sitesmatrix.ConfiguredDimensions {
|
||||
return sitesmatrix.NewTestingDimensions([]string{"en", "no"}, []string{"v1", "v2", "v3"}, []string{"admin", "editor", "viewer", "guest"})
|
||||
}
|
||||
|
||||
func TestIntSets(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
testDims := newTestDims()
|
||||
|
||||
sets := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
|
||||
maps.NewOrderedIntSet(1, 2),
|
||||
@@ -103,31 +104,6 @@ func TestIntSets(t *testing.T) {
|
||||
c.Assert(allCount, qt.Equals, 18)
|
||||
}
|
||||
|
||||
func TestBitSetExperiments(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
a, b, d := bitset.New(10), bitset.New(10), bitset.New(10)
|
||||
|
||||
ints := func(v *bitset.BitSet) []uint {
|
||||
var ints []uint
|
||||
for i := range v.EachSet() {
|
||||
ints = append(ints, i)
|
||||
}
|
||||
return ints
|
||||
}
|
||||
|
||||
a.Set(3).Set(4)
|
||||
b.Set(4).Set(5).Set(6).Set(7)
|
||||
d.Set(3).Set(4)
|
||||
|
||||
c.Assert(b.Test(5), qt.Equals, true)
|
||||
|
||||
fmt.Println("a:", ints(a), "==>", ints(b), "=> Intersection:", ints(a.Intersection(b)), "=> Symdiff:", ints(a.SymmetricDifference(b)))
|
||||
fmt.Println("===>", ints(a.Difference(b)))
|
||||
fmt.Println("===>", ints(b.Difference(a)))
|
||||
fmt.Println("===> d", d.Difference(a).Count())
|
||||
}
|
||||
|
||||
func TestIntSetsComplement(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
@@ -147,6 +123,8 @@ func TestIntSetsComplement(t *testing.T) {
|
||||
runOne := func(c *qt.C, test test) {
|
||||
c.Helper()
|
||||
|
||||
testDims := newTestDims()
|
||||
|
||||
self := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
|
||||
maps.NewOrderedIntSet(test.left.v0...),
|
||||
maps.NewOrderedIntSet(test.left.v1...),
|
||||
@@ -281,6 +259,8 @@ func TestIntSetsComplement(t *testing.T) {
|
||||
func TestIntSetsComplementOfComplement(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
testDims := newTestDims()
|
||||
|
||||
sets1 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
|
||||
maps.NewOrderedIntSet(1),
|
||||
maps.NewOrderedIntSet(1),
|
||||
@@ -313,6 +293,8 @@ func TestIntSetsComplementOfComplement(t *testing.T) {
|
||||
}
|
||||
|
||||
func BenchmarkIntSetsComplement(b *testing.B) {
|
||||
testDims := newTestDims()
|
||||
|
||||
sets1 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
|
||||
maps.NewOrderedIntSet(1, 2, 3),
|
||||
maps.NewOrderedIntSet(1, 2, 3),
|
||||
@@ -376,6 +358,8 @@ func BenchmarkIntSetsComplement(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkSets(b *testing.B) {
|
||||
testDims := newTestDims()
|
||||
|
||||
sets1 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
|
||||
maps.NewOrderedIntSet(1, 2),
|
||||
maps.NewOrderedIntSet(1, 2, 3),
|
||||
@@ -485,6 +469,7 @@ func BenchmarkSets(b *testing.B) {
|
||||
|
||||
func TestVectorStoreMap(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
testDims := newTestDims()
|
||||
|
||||
c.Run("Complement", func(c *qt.C) {
|
||||
v1 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
|
||||
@@ -509,3 +494,18 @@ func TestVectorStoreMap(t *testing.T) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkHasAnyVectorSingle(b *testing.B) {
|
||||
testDims := newTestDims()
|
||||
set := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
|
||||
maps.NewOrderedIntSet(0),
|
||||
maps.NewOrderedIntSet(0),
|
||||
maps.NewOrderedIntSet(0),
|
||||
).Build()
|
||||
|
||||
v := sitesmatrix.Vector{0, 0, 0}
|
||||
|
||||
for b.Loop() {
|
||||
_ = set.HasAnyVector(v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,10 @@ type VersionsInternal struct {
|
||||
Sorted []VersionInternal
|
||||
}
|
||||
|
||||
func (r VersionsInternal) Len() int {
|
||||
return len(r.Sorted)
|
||||
}
|
||||
|
||||
func (r VersionsInternal) IndexDefault() int {
|
||||
for i, version := range r.Sorted {
|
||||
if version.Default {
|
||||
|
||||
@@ -92,6 +92,10 @@ func (ls LanguagesInternal) ResolveIndex(name string) int {
|
||||
panic(fmt.Sprintf("no language found for name %q", name))
|
||||
}
|
||||
|
||||
func (ls LanguagesInternal) Len() int {
|
||||
return len(ls.Sorted)
|
||||
}
|
||||
|
||||
// IndexMatch returns an iterator for the roles that match the filter.
|
||||
func (ls LanguagesInternal) IndexMatch(match predicate.P[string]) (iter.Seq[int], error) {
|
||||
return func(yield func(i int) bool) {
|
||||
|
||||
@@ -245,7 +245,13 @@ func buildSitesComplementsFromSitesConfig(
|
||||
conf config.AllProvider,
|
||||
fim *hugofs.FileMeta,
|
||||
sitesConfig sitesmatrix.Sites,
|
||||
) *sitesmatrix.IntSets {
|
||||
) sitesmatrix.VectorStore {
|
||||
if sitesConfig.Complements.IsZero() {
|
||||
if fim != nil && fim.SitesComplements != nil {
|
||||
return fim.SitesComplements
|
||||
}
|
||||
return sitesmatrix.NilStore
|
||||
}
|
||||
intsetsCfg := sitesmatrix.IntSetsConfig{
|
||||
Globs: sitesConfig.Complements,
|
||||
}
|
||||
@@ -259,27 +265,22 @@ func buildSitesComplementsFromSitesConfig(
|
||||
return sitesComplements.Build()
|
||||
}
|
||||
|
||||
func buildSitesMatrixFromVectorIterator(
|
||||
sitesMatrix sitesmatrix.VectorIterator,
|
||||
) sitesmatrix.VectorStore {
|
||||
switch v := sitesMatrix.(type) {
|
||||
case sitesmatrix.VectorStore:
|
||||
return v
|
||||
case sitesmatrix.ToVectorStoreProvider:
|
||||
return v.ToVectorStore()
|
||||
default:
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
panic(fmt.Sprintf("unsupported type %T", v))
|
||||
}
|
||||
}
|
||||
|
||||
func buildSitesMatrixFromSitesConfig(
|
||||
conf config.AllProvider,
|
||||
sitesMatrixBase sitesmatrix.VectorIterator,
|
||||
sitesConfig sitesmatrix.Sites,
|
||||
) *sitesmatrix.IntSets {
|
||||
) sitesmatrix.VectorStore {
|
||||
if sitesConfig.Matrix.IsZero() && sitesMatrixBase != nil {
|
||||
if sitesMatrixBase.LenVectors() == 1 {
|
||||
return conf.ConfiguredDimensions().GetOrCreateSingleVectorStore(sitesMatrixBase.VectorSample())
|
||||
}
|
||||
return sitesmatrix.VectorIteratorToStore(sitesMatrixBase)
|
||||
}
|
||||
|
||||
if conf.ConfiguredDimensions().IsSingleVector() {
|
||||
return conf.ConfiguredDimensions().CommonSitesMatrix.DefaultSite
|
||||
}
|
||||
|
||||
intsetsCfg := sitesmatrix.IntSetsConfig{
|
||||
Globs: sitesConfig.Matrix,
|
||||
}
|
||||
@@ -317,7 +318,7 @@ func (p *PageConfigEarly) CompileEarly(pi *paths.Path, cascades *page.PageMatche
|
||||
}
|
||||
|
||||
if sitesMatrixBaseOnly {
|
||||
p.SitesMatrix = buildSitesMatrixFromVectorIterator(sitesMatrixBase)
|
||||
p.SitesMatrix = sitesmatrix.VectorIteratorToStore(sitesMatrixBase)
|
||||
} else {
|
||||
p.SitesMatrix = buildSitesMatrixFromSitesConfig(
|
||||
conf,
|
||||
|
||||
Reference in New Issue
Block a user