mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-24 15:28:54 +00:00
26f31ff6ce
Hugo's build process is roughly divided into three steps: 1. Process content (walk file system and insert source nodes into content tree) 2. Assemble content (assemble pages and resources according to sites matrix) 3. Render content In #13679 we consolidated the page creation logic into one place (the assemble step). This made it much simpler to reason about, but it lost us some performance esp. in big content trees. This commit re-introduces parallelization in the first step in the assemble step by handling each top level section in its own goroutine. This gives significant performance improvements for content trees with many sections. Compared to master: ``` AssembleDeepSiteWithManySections/depth=1/sectionsPerLevel=6/pagesPerSection=100-10 19.26m ± ∞ ¹ 14.54m ± ∞ ¹ -24.52% (p=0.029 n=4) AssembleDeepSiteWithManySections/depth=2/sectionsPerLevel=2/pagesPerSection=100-10 19.74m ± ∞ ¹ 16.45m ± ∞ ¹ -16.71% (p=0.029 n=4) AssembleDeepSiteWithManySections/depth=2/sectionsPerLevel=6/pagesPerSection=100-10 106.18m ± ∞ ¹ 71.23m ± ∞ ¹ -32.91% (p=0.029 n=4) AssembleDeepSiteWithManySections/depth=3/sectionsPerLevel=2/pagesPerSection=100-10 38.85m ± ∞ ¹ 30.47m ± ∞ ¹ -21.59% (p=0.029 n=4) ```
92 lines
2.0 KiB
Go
92 lines
2.0 KiB
Go
// Copyright 2024 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 collections
|
|
|
|
import (
|
|
"iter"
|
|
"slices"
|
|
"sync"
|
|
|
|
"github.com/gohugoio/hugo/common/hiter"
|
|
)
|
|
|
|
// Stack is a simple LIFO stack that is safe for concurrent use.
|
|
type Stack[T any] struct {
|
|
items []T
|
|
zero T
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
func NewStack[T any]() *Stack[T] {
|
|
return &Stack[T]{}
|
|
}
|
|
|
|
func (s *Stack[T]) Push(item T) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.items = append(s.items, item)
|
|
}
|
|
|
|
func (s *Stack[T]) Pop() (T, bool) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if len(s.items) == 0 {
|
|
return s.zero, false
|
|
}
|
|
item := s.items[len(s.items)-1]
|
|
s.items = s.items[:len(s.items)-1]
|
|
return item, true
|
|
}
|
|
|
|
func (s *Stack[T]) Peek() (T, bool) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
if len(s.items) == 0 {
|
|
return s.zero, false
|
|
}
|
|
return s.items[len(s.items)-1], true
|
|
}
|
|
|
|
func (s *Stack[T]) Len() int {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return len(s.items)
|
|
}
|
|
|
|
// All returns all items in the stack, from bottom to top.
|
|
func (s *Stack[T]) All() iter.Seq2[int, T] {
|
|
return hiter.Lock2(slices.All(s.items), s.mu.RLock, s.mu.RUnlock)
|
|
}
|
|
|
|
func (s *Stack[T]) Drain() []T {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
items := s.items
|
|
s.items = nil
|
|
return items
|
|
}
|
|
|
|
func (s *Stack[T]) DrainMatching(predicate func(T) bool) []T {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
var items []T
|
|
for i := len(s.items) - 1; i >= 0; i-- {
|
|
if predicate(s.items[i]) {
|
|
items = append(items, s.items[i])
|
|
s.items = slices.Delete(s.items, i, i+1)
|
|
}
|
|
}
|
|
return items
|
|
}
|