diff --git a/README.md b/README.md
index 38b0ac7..7313359 100644
--- a/README.md
+++ b/README.md
@@ -18,8 +18,8 @@
@@ -608,7 +608,7 @@ Contributions of code, issue reports, or suggestions are welcome! Please follow
## 📄 License
-This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
+This project is licensed under the Apache 2.0 License - see the [LICENSE](LICENSE) file for details.
diff --git a/README_ZH.md b/README_ZH.md
index c4f5b7c..5646239 100644
--- a/README_ZH.md
+++ b/README_ZH.md
@@ -18,8 +18,8 @@
@@ -615,7 +615,7 @@ go test -bench=. -benchmem
## 📄 许可证
-该项目采用 MIT 许可证 - 详情请查看 [LICENSE](LICENSE) 文件。
+该项目采用 Apache 2.0 许可证 - 详情请查看 [LICENSE](LICENSE) 文件。
diff --git a/animation/types.go b/animation/types.go
index 3c3c50f..4a41103 100644
--- a/animation/types.go
+++ b/animation/types.go
@@ -2,6 +2,23 @@ package animation
import (
"strings"
+ "sync"
+)
+
+var (
+ // 字符串构建器对象池
+ animationBuilderPool = sync.Pool{
+ New: func() interface{} {
+ return new(strings.Builder)
+ },
+ }
+
+ // 动画映射对象池
+ animationMapPool = sync.Pool{
+ New: func() interface{} {
+ return make(map[string]string)
+ },
+ }
)
// AnimationType 表示动画类型
@@ -26,6 +43,8 @@ type Animation interface {
GenerateSVG() string
// GetTargetID 获取目标元素ID
GetTargetID() string
+ // GetType 获取动画类型
+ GetType() AnimationType
}
// BaseAnimation 基础动画结构,包含所有动画共有的属性
@@ -43,20 +62,20 @@ func (a *BaseAnimation) GetTargetID() string {
return a.TargetID
}
+// GetType 获取动画类型
+func (a *BaseAnimation) GetType() AnimationType {
+ return a.Type
+}
+
// Manager 动画管理器,负责管理所有动画
type Manager struct {
animations []Animation
}
-// GetAnimations 获取所有动画
-func (m *Manager) GetAnimations() []Animation {
- return m.animations
-}
-
// NewAnimationManager 创建一个新的动画管理器
func NewAnimationManager() *Manager {
return &Manager{
- animations: make([]Animation, 0),
+ animations: make([]Animation, 0, 10), // 预分配容量
}
}
@@ -65,25 +84,63 @@ func (m *Manager) AddAnimation(animation Animation) {
m.animations = append(m.animations, animation)
}
+// GetAnimations 获取所有动画
+func (m *Manager) GetAnimations() []Animation {
+ return m.animations
+}
+
+// GetAnimationsByType 获取指定类型的动画
+func (m *Manager) GetAnimationsByType(animType AnimationType) []Animation {
+ // 预分配足够容量
+ result := make([]Animation, 0, len(m.animations)/2)
+
+ for _, anim := range m.animations {
+ if anim.GetType() == animType {
+ result = append(result, anim)
+ }
+ }
+
+ return result
+}
+
// GenerateSVGAnimations 生成SVG动画代码
func (m *Manager) GenerateSVGAnimations() string {
if len(m.animations) == 0 {
return ""
}
- var sb strings.Builder
+ // 从对象池获取构建器
+ sb := animationBuilderPool.Get().(*strings.Builder)
+ sb.Reset()
+ sb.Grow(1024) // 预分配足够的容量
+ defer animationBuilderPool.Put(sb)
// 添加SVG命名空间声明
sb.WriteString("\n")
- // 用于存储需要放在defs中的定义
- var defsContent strings.Builder
- // 用于存储需要直接添加到SVG中的动画元素
- var animationsContent strings.Builder
- // 用于存储旋转动画的映射,键为目标元素ID
- rotateAnimations := make(map[string]string)
+ // 获取定义内容构建器
+ defsContent := animationBuilderPool.Get().(*strings.Builder)
+ defsContent.Reset()
+ defsContent.Grow(512)
+ defer animationBuilderPool.Put(defsContent)
- // 处理所有动画
+ // 获取动画内容构建器
+ animationsContent := animationBuilderPool.Get().(*strings.Builder)
+ animationsContent.Reset()
+ animationsContent.Grow(512)
+ defer animationBuilderPool.Put(animationsContent)
+
+ // 获取旋转动画映射
+ rotateAnimations := animationMapPool.Get().(map[string]string)
+ defer func() {
+ // 清空映射并归还
+ for k := range rotateAnimations {
+ delete(rotateAnimations, k)
+ }
+ animationMapPool.Put(rotateAnimations)
+ }()
+
+ // 一次性处理所有动画以减少循环开销
for _, anim := range m.animations {
svgCode := anim.GenerateSVG()
if svgCode == "" {
@@ -91,16 +148,15 @@ func (m *Manager) GenerateSVGAnimations() string {
}
// 根据动画类型决定放置位置
- switch a := anim.(type) {
- case *GradientAnimation:
+ switch anim.GetType() {
+ case Gradient:
// 渐变定义需要放在defs中
defsContent.WriteString(svgCode)
- case *RotateAnimation:
+ case Rotate:
// 旋转动画需要包裹目标元素,先存储起来
- // 提取animateTransform标签
if start := strings.Index(svgCode, ""); end != -1 {
- rotateAnimations[a.GetTargetID()] = svgCode[start : start+end+2]
+ rotateAnimations[anim.GetTargetID()] = svgCode[start : start+end+2]
}
}
default:
diff --git a/benchmark/cache_benchmark_test.go b/benchmark/cache_benchmark_test.go
index 0e05059..1264bb0 100644
--- a/benchmark/cache_benchmark_test.go
+++ b/benchmark/cache_benchmark_test.go
@@ -83,13 +83,33 @@ func BenchmarkCacheSizes(b *testing.B) {
// BenchmarkCacheCompression 测试缓存压缩对性能的影响
func BenchmarkCacheCompression(b *testing.B) {
compressionLevels := []struct {
- name string
- level int
+ name string
+ options cache.CompressOptions
}{
- {"NoCompression", 0},
- {"LowCompression", 3},
- {"MediumCompression", 6},
- {"HighCompression", 9},
+ {
+ name: "NoCompression",
+ options: cache.CompressOptions{
+ Enabled: false,
+ },
+ },
+ {
+ name: "DefaultCompression",
+ options: cache.CompressOptions{
+ Enabled: true,
+ Level: 5, // 默认压缩级别
+ MinSize: 1024, // 最小压缩大小
+ CompressionRatio: 0.8, // 最小压缩比率
+ },
+ },
+ {
+ name: "BestCompression",
+ options: cache.CompressOptions{
+ Enabled: true,
+ Level: 9, // 最高压缩级别
+ MinSize: 1024, // 最小压缩大小
+ CompressionRatio: 0.8, // 最小压缩比率
+ },
+ },
}
for _, cl := range compressionLevels {
@@ -100,12 +120,8 @@ func BenchmarkCacheCompression(b *testing.B) {
pn.WithSize(231, 231)
pn.WithDefaultCache()
- if cl.level > 0 {
- pn.WithCompression(cache.CompressOptions{
- Enabled: true,
- Level: cl.level,
- MinSizeBytes: 100,
- })
+ if cl.options.Enabled {
+ pn.WithCompression(cl.options)
}
b.ResetTimer()
diff --git a/cache/cache.go b/cache/cache.go
index 0409c9c..5a203db 100644
--- a/cache/cache.go
+++ b/cache/cache.go
@@ -2,10 +2,28 @@ package cache
import (
"container/list"
+ "strconv"
+ "strings"
"sync"
"time"
)
+var (
+ // 字符串构建器对象池
+ stringBuilderPool = sync.Pool{
+ New: func() interface{} {
+ return new(strings.Builder)
+ },
+ }
+
+ // 缓存项对象池
+ cacheItemPool = sync.Pool{
+ New: func() interface{} {
+ return new(CacheItem)
+ },
+ }
+)
+
// CacheOptions 缓存配置选项
type CacheOptions struct {
Enabled bool // 是否启用缓存
@@ -14,6 +32,8 @@ type CacheOptions struct {
EvictionType string // 缓存淘汰策略,支持"lru"(最近最少使用)和"fifo"(先进先出)
Compression CompressOptions // 压缩选项
Monitoring MonitorOptions // 监控选项
+ // 新增选项:是否启用预热
+ Preheating bool // 是否启用缓存预热
}
// DefaultCacheOptions 默认缓存配置
@@ -24,6 +44,7 @@ var DefaultCacheOptions = CacheOptions{
EvictionType: "lru", // 默认使用LRU淘汰策略
Compression: DefaultCompressOptions, // 默认压缩选项
Monitoring: DefaultMonitorOptions, // 默认监控选项
+ Preheating: false, // 默认不启用预热
}
// CacheKey 缓存键结构
@@ -34,6 +55,37 @@ type CacheKey struct {
Part int
}
+// String 返回缓存键的字符串表示
+func (k CacheKey) String() string {
+ // 从对象池获取构建器
+ sb := stringBuilderPool.Get().(*strings.Builder)
+ sb.Reset()
+
+ // 预分配足够容量
+ sb.Grow(64)
+
+ // 构建键字符串
+ sb.WriteString(k.Id)
+ sb.WriteByte('_')
+ if k.SansEnv {
+ sb.WriteString("true")
+ } else {
+ sb.WriteString("false")
+ }
+ sb.WriteByte('_')
+ sb.WriteString(strconv.Itoa(k.Theme))
+ sb.WriteByte('_')
+ sb.WriteString(strconv.Itoa(k.Part))
+
+ // 获取结果
+ result := sb.String()
+
+ // 归还构建器
+ stringBuilderPool.Put(sb)
+
+ return result
+}
+
// CacheItem 缓存项结构
type CacheItem struct {
SVG string // SVG内容
@@ -43,6 +95,15 @@ type CacheItem struct {
LastUsed time.Time // 最后使用时间
}
+// Reset 重置缓存项以便重用
+func (item *CacheItem) Reset() {
+ item.SVG = ""
+ item.Compressed = nil
+ item.IsCompressed = false
+ item.CreatedAt = time.Time{}
+ item.LastUsed = time.Time{}
+}
+
// PNCache SVG缓存结构
type PNCache struct {
Options CacheOptions
@@ -58,7 +119,7 @@ type PNCache struct {
func NewCache(options CacheOptions) *PNCache {
cache := &PNCache{
Options: options,
- Items: make(map[CacheKey]*list.Element),
+ Items: make(map[CacheKey]*list.Element, options.Size),
EvictionList: list.New(),
Hits: 0,
Misses: 0,
@@ -103,6 +164,11 @@ func (c *PNCache) Get(key CacheKey) (string, bool) {
// 删除过期项
c.EvictionList.Remove(element)
delete(c.Items, key)
+
+ // 将缓存项归还到对象池
+ cacheItem.Reset()
+ cacheItemPool.Put(cacheItem)
+
c.Misses++
return "", false
}
@@ -178,15 +244,15 @@ func (c *PNCache) Set(key CacheKey, svg string) {
c.evictItem()
}
- // 创建新的缓存项
+ // 从对象池获取新的缓存项
now := time.Now()
- cacheItem := &CacheItem{
- SVG: svg,
- Compressed: compressed,
- IsCompressed: isCompressed,
- CreatedAt: now,
- LastUsed: now,
- }
+ cacheItem := cacheItemPool.Get().(*CacheItem)
+ cacheItem.Reset()
+ cacheItem.SVG = svg
+ cacheItem.Compressed = compressed
+ cacheItem.IsCompressed = isCompressed
+ cacheItem.CreatedAt = now
+ cacheItem.LastUsed = now
// 添加到链表和映射
element := c.EvictionList.PushFront(cacheItem)
@@ -214,6 +280,9 @@ func (c *PNCache) evictItem() {
// 从链表中移除
c.EvictionList.Remove(element)
+ // 获取缓存项并归还到对象池
+ cacheItem := element.Value.(*CacheItem)
+
// 从映射中找到并删除对应的键
for k, v := range c.Items {
if v == element {
@@ -221,6 +290,10 @@ func (c *PNCache) evictItem() {
break
}
}
+
+ // 重置并归还缓存项
+ cacheItem.Reset()
+ cacheItemPool.Put(cacheItem)
}
}
@@ -229,7 +302,14 @@ func (c *PNCache) Clear() {
c.Mutex.Lock()
defer c.Mutex.Unlock()
- c.Items = make(map[CacheKey]*list.Element)
+ // 归还所有缓存项到对象池
+ for e := c.EvictionList.Front(); e != nil; e = e.Next() {
+ item := e.Value.(*CacheItem)
+ item.Reset()
+ cacheItemPool.Put(item)
+ }
+
+ c.Items = make(map[CacheKey]*list.Element, c.Options.Size)
c.EvictionList = list.New()
c.Hits = 0
c.Misses = 0
@@ -277,6 +357,9 @@ func (c *PNCache) RemoveExpired() int {
c.EvictionList.Remove(element)
// 从映射中删除
delete(c.Items, key)
+ // 归还缓存项到对象池
+ cacheItem.Reset()
+ cacheItemPool.Put(cacheItem)
count++
}
}
@@ -357,11 +440,18 @@ func (c *PNCache) DeleteItem(key CacheKey) bool {
return false
}
+ // 获取缓存项
+ cacheItem := element.Value.(*CacheItem)
+
// 从链表中移除
c.EvictionList.Remove(element)
// 从映射中删除
delete(c.Items, key)
+ // 重置并归还缓存项
+ cacheItem.Reset()
+ cacheItemPool.Put(cacheItem)
+
return true
}
diff --git a/cache/compress.go b/cache/compress.go
index 2f2c6ad..e128fae 100644
--- a/cache/compress.go
+++ b/cache/compress.go
@@ -5,60 +5,95 @@ import (
"compress/gzip"
"io"
"strings"
+ "sync"
)
-// CompressOptions 压缩选项
+var (
+ // 字符串构建器对象池
+ optimizeBuilderPool = sync.Pool{
+ New: func() interface{} {
+ return new(strings.Builder)
+ },
+ }
+
+ // Gzip Writer对象池
+ gzipWriterPool = sync.Pool{
+ New: func() interface{} {
+ writer, _ := gzip.NewWriterLevel(nil, gzip.BestCompression)
+ return writer
+ },
+ }
+
+ // Gzip Reader对象池
+ gzipReaderPool = sync.Pool{
+ New: func() interface{} {
+ return new(gzip.Reader)
+ },
+ }
+
+ // 字节缓冲区对象池
+ bytesBufferPool = sync.Pool{
+ New: func() interface{} {
+ return new(bytes.Buffer)
+ },
+ }
+)
+
+// CompressOptions 压缩配置选项
type CompressOptions struct {
- Enabled bool // 是否启用压缩
- Level int // 压缩级别 (1-9),1为最快压缩,9为最佳压缩
- MinSizeBytes int // 最小压缩大小,小于此大小的数据不进行压缩
- Ratio float64 // 压缩比阈值,压缩后大小/原始大小,小于此值才保存压缩结果
+ Enabled bool // 是否启用压缩
+ Level int // 压缩级别,范围从-2(不压缩)到9(最高压缩)
+ MinSize int // 最小压缩大小,小于此大小的SVG不压缩
+ CompressionRatio float64 // 最小压缩比率,压缩后大小/原始大小,小于此比率才使用压缩结果
}
-// DefaultCompressOptions 默认压缩选项
+// DefaultCompressOptions 默认压缩配置
var DefaultCompressOptions = CompressOptions{
- Enabled: true,
- Level: 6, // 默认压缩级别为6,平衡压缩率和性能
- MinSizeBytes: 100, // 默认最小压缩大小为100字节
- Ratio: 0.9, // 默认压缩比阈值为0.9,即至少要压缩到原始大小的90%以下才保存压缩结果
+ Enabled: true,
+ Level: 9, // 默认使用最高压缩级别
+ MinSize: 1024, // 默认最小压缩大小为1KB
+ CompressionRatio: 0.8, // 默认最小压缩比率为0.8
}
-// CompressSVG 压缩SVG数据
-// 返回压缩后的数据和是否进行了压缩
+// CompressSVG 压缩SVG字符串
func CompressSVG(svg string, options CompressOptions) ([]byte, bool) {
- if !options.Enabled || len(svg) < options.MinSizeBytes {
+ if !options.Enabled {
return []byte(svg), false
}
- // 创建一个bytes.Buffer来存储压缩数据
- var buf bytes.Buffer
-
- // 创建一个gzip.Writer,设置压缩级别
- writer, err := gzip.NewWriterLevel(&buf, options.Level)
- if err != nil {
+ // 如果SVG太小,不进行压缩
+ if len(svg) < options.MinSize {
return []byte(svg), false
}
- // 写入SVG数据
- _, err = writer.Write([]byte(svg))
- if err != nil {
+ // 从对象池获取缓冲区
+ buf := bytesBufferPool.Get().(*bytes.Buffer)
+ buf.Reset()
+ defer bytesBufferPool.Put(buf)
+
+ // 从对象池获取gzip写入器
+ writer := gzipWriterPool.Get().(*gzip.Writer)
+ writer.Reset(buf)
+ defer gzipWriterPool.Put(writer)
+
+ // 写入数据
+ if _, err := writer.Write([]byte(svg)); err != nil {
return []byte(svg), false
}
- // 关闭writer,确保所有数据都被写入
- err = writer.Close()
- if err != nil {
+ // 关闭写入器
+ if err := writer.Close(); err != nil {
return []byte(svg), false
}
// 获取压缩后的数据
- compressed := buf.Bytes()
+ compressed := make([]byte, buf.Len())
+ copy(compressed, buf.Bytes())
- // 计算压缩比
+ // 计算压缩比率
ratio := float64(len(compressed)) / float64(len(svg))
-
- // 如果压缩比不理想,返回原始数据
- if ratio >= options.Ratio {
+ if ratio > options.CompressionRatio {
+ // 压缩效果不好,返回原始数据
return []byte(svg), false
}
@@ -77,7 +112,12 @@ func DecompressSVG(data []byte, isCompressed bool) (string, error) {
return string(data), nil
}
- // 创建一个gzip.Reader
+ // 获取缓冲区
+ buf := bytesBufferPool.Get().(*bytes.Buffer)
+ buf.Reset()
+ defer bytesBufferPool.Put(buf)
+
+ // 创建Reader
reader, err := gzip.NewReader(bytes.NewReader(data))
if err != nil {
return string(data), err
@@ -85,12 +125,11 @@ func DecompressSVG(data []byte, isCompressed bool) (string, error) {
defer reader.Close()
// 读取解压缩后的数据
- decompressed, err := io.ReadAll(reader)
- if err != nil {
+ if _, err := io.Copy(buf, reader); err != nil {
return string(data), err
}
- return string(decompressed), nil
+ return buf.String(), nil
}
// isGzipped 检查数据是否为gzip格式
@@ -101,6 +140,11 @@ func isGzipped(data []byte) bool {
// OptimizeSVG 优化SVG字符串,移除不必要的空白和注释
func OptimizeSVG(svg string) string {
+ // 如果SVG太小,不进行优化
+ if len(svg) < 100 {
+ return svg
+ }
+
// 移除XML注释
svg = removeXMLComments(svg)
@@ -110,33 +154,100 @@ func OptimizeSVG(svg string) string {
return svg
}
-// removeXMLComments 移除XML注释
+// removeXMLComments 移除XML注释 - 优化版本
func removeXMLComments(svg string) string {
- for {
- start := strings.Index(svg, "") + start
- if end > start {
- svg = svg[:start] + svg[end+3:]
+ // 获取构建器
+ sb := optimizeBuilderPool.Get().(*strings.Builder)
+ sb.Reset()
+ sb.Grow(len(svg))
+ defer optimizeBuilderPool.Put(sb)
+
+ for i := 0; i < len(svg); {
+ // 检查是否遇到注释开始
+ if i+3 < len(svg) && svg[i] == '<' && svg[i+1] == '!' && svg[i+2] == '-' && svg[i+3] == '-' {
+ // 查找注释结束
+ end := i + 4
+ for end+2 <= len(svg) {
+ if svg[end] == '-' && svg[end+1] == '-' && svg[end+2] == '>' {
+ end += 3
+ break
+ }
+ end++
+ }
+ // 跳过注释
+ i = end
} else {
- break
+ // 添加非注释字符
+ sb.WriteByte(svg[i])
+ i++
}
}
- return svg
+
+ return sb.String()
}
-// removeExtraWhitespace 移除多余的空白
+// removeExtraWhitespace 移除多余的空白 - 优化版本
func removeExtraWhitespace(svg string) string {
- // 替换多个空白字符为单个空格
- svg = strings.Join(strings.Fields(svg), " ")
+ // 获取构建器
+ sb := optimizeBuilderPool.Get().(*strings.Builder)
+ sb.Reset()
+ sb.Grow(len(svg))
+ defer optimizeBuilderPool.Put(sb)
- // 优化常见的SVG标签周围的空白
- svg = strings.ReplaceAll(svg, "> <", "><")
- svg = strings.ReplaceAll(svg, " />", "/>")
- svg = strings.ReplaceAll(svg, " =", "=")
- svg = strings.ReplaceAll(svg, "= ", "=")
+ inTag := false
+ inQuote := false
+ quoteChar := byte(0)
+ lastWasSpace := false
- return svg
+ for i := 0; i < len(svg); i++ {
+ c := svg[i]
+
+ // 处理引号内的内容
+ if inQuote {
+ sb.WriteByte(c)
+ if c == quoteChar {
+ inQuote = false
+ }
+ continue
+ }
+
+ // 处理标签
+ if c == '<' {
+ inTag = true
+ lastWasSpace = false
+ sb.WriteByte(c)
+ continue
+ }
+
+ if c == '>' {
+ inTag = false
+ lastWasSpace = false
+ sb.WriteByte(c)
+ continue
+ }
+
+ // 处理引号开始
+ if (c == '"' || c == '\'') && inTag {
+ inQuote = true
+ quoteChar = c
+ sb.WriteByte(c)
+ continue
+ }
+
+ // 处理空白字符
+ if c == ' ' || c == '\t' || c == '\n' || c == '\r' {
+ // 压缩连续空白
+ if !lastWasSpace && (inTag || i > 0 && svg[i-1] != '>') {
+ sb.WriteByte(' ')
+ lastWasSpace = true
+ }
+ continue
+ }
+
+ // 处理普通字符
+ lastWasSpace = false
+ sb.WriteByte(c)
+ }
+
+ return sb.String()
}
diff --git a/pixelnebula.go b/pixelnebula.go
index 0f171cf..046d2a3 100644
--- a/pixelnebula.go
+++ b/pixelnebula.go
@@ -10,6 +10,7 @@ import (
"regexp"
"strconv"
"strings"
+ "sync"
"time"
"github.com/landaiqing/go-pixelnebula/animation"
@@ -22,7 +23,6 @@ import (
const (
hashLength = 12
- keyFactor = 0.47
)
var (
@@ -30,6 +30,32 @@ var (
numberRegex = regexp.MustCompile(`[0-9]`)
// 使用非贪婪模式并优化颜色匹配模式
colorRegex = regexp.MustCompile(`#([^;]*);`)
+ // 使用字节池减少内存分配
+ hashBufPool = sync.Pool{
+ New: func() interface{} {
+ buf := make([]byte, 64)
+ return &buf
+ },
+ }
+ // 缓存键计算结果缓存
+ keyCache = make(map[string][2]int)
+ keyCacheMux sync.RWMutex
+ // 使用对象池减少内存分配
+ builderPool = sync.Pool{
+ New: func() interface{} {
+ return &strings.Builder{}
+ },
+ }
+ mapPool = sync.Pool{
+ New: func() interface{} {
+ return make(map[string]string, 6)
+ },
+ }
+ keyMapPool = sync.Pool{
+ New: func() interface{} {
+ return make(map[string][2]int, 6)
+ },
+ }
)
type PNOptions struct {
@@ -119,15 +145,16 @@ func (pn *PixelNebula) hashToNum(hash []string) int64 {
return 0
}
- // 将哈希字符串数组连接成一个字符串
+ // 使用位运算直接计算哈希值,减少内存分配和计算复杂度
var result int64
for _, h := range hash {
- num, err := strconv.ParseInt(h, 10, 64)
- if err != nil {
- continue
+ // 优化:直接处理字符,避免ParseInt的开销
+ for i := 0; i < len(h); i++ {
+ if h[i] >= '0' && h[i] <= '9' {
+ // 使用位运算进行计算: result = result*10 + (h[i] - '0')
+ result = (result << 3) + (result << 1) + int64(h[i]-'0')
+ }
}
- // 使用位运算和加法组合多个数字
- result = (result << 3) + (result << 1) + num // result * 8 + result * 2 + num
}
// 确保结果为正数
@@ -138,14 +165,37 @@ func (pn *PixelNebula) hashToNum(hash []string) int64 {
return result
}
+// getCacheKey 生成缓存键的哈希表示
+func (pn *PixelNebula) getCacheKey(id string, hash []string, index int, opts *PNOptions) string {
+ // 构造一个唯一的键字符串
+ var key string
+ if opts != nil && opts.StyleIndex >= 0 && opts.ThemeIndex >= 0 {
+ key = fmt.Sprintf("%s_%d_%d_%d", id, index, opts.StyleIndex, opts.ThemeIndex)
+ } else if len(hash) > 0 {
+ key = fmt.Sprintf("%s_%d_%s", id, index, strings.Join(hash, ""))
+ } else {
+ key = fmt.Sprintf("%s_%d", id, index)
+ }
+ return key
+}
+
// calcKey 计算主题和部分的键值
func (pn *PixelNebula) calcKey(hash []string, opts *PNOptions) [2]int {
- // 只有当明确设置了主题和风格索引时才使用固定值
+ // 检查是否使用固定值
if opts != nil && opts.StyleIndex >= 0 && opts.ThemeIndex >= 0 {
return [2]int{opts.StyleIndex, opts.ThemeIndex}
}
- // 直接使用哈希值,不进行 keyFactor 转换
+ // 尝试从缓存中获取结果
+ cacheKey := strings.Join(hash, "")
+ keyCacheMux.RLock()
+ if result, ok := keyCache[cacheKey]; ok {
+ keyCacheMux.RUnlock()
+ return result
+ }
+ keyCacheMux.RUnlock()
+
+ // 计算哈希值
hashNum := pn.hashToNum(hash)
// 获取可用的风格数量
@@ -154,14 +204,11 @@ func (pn *PixelNebula) calcKey(hash []string, opts *PNOptions) [2]int {
return [2]int{0, 0}
}
- // 使用哈希值计算风格索引
+ // 使用位运算优化取模操作
styleIndex := int(hashNum % int64(styleCount))
if styleIndex < 0 {
styleIndex = -styleIndex
}
- if styleIndex >= styleCount {
- styleIndex = styleCount - 1
- }
// 获取该风格下的主题数量
themeCount := pn.themeManager.ThemeCount(styleIndex)
@@ -170,16 +217,18 @@ func (pn *PixelNebula) calcKey(hash []string, opts *PNOptions) [2]int {
}
// 使用哈希值的不同部分计算主题索引
- // 使用更简单的计算方式
themeIndex := int(hashNum % int64(themeCount))
if themeIndex < 0 {
themeIndex = -themeIndex
}
- if themeIndex >= themeCount {
- themeIndex = themeCount - 1
- }
- return [2]int{styleIndex, themeIndex}
+ // 将结果存入缓存
+ result := [2]int{styleIndex, themeIndex}
+ keyCacheMux.Lock()
+ keyCache[cacheKey] = result
+ keyCacheMux.Unlock()
+
+ return result
}
// WithCache 设置缓存选项
@@ -584,10 +633,14 @@ func (pn *PixelNebula) generateSVG(id string, sansEnv bool, opts *PNOptions) (sv
}
}
- // 计算avatarId的哈希值
+ // 使用对象池获取缓冲区
+ hashBuf := hashBufPool.Get().(*[]byte)
+ defer hashBufPool.Put(hashBuf)
+
+ // 计算avatarId的哈希值 - 优化版本
pn.hasher.Reset()
pn.hasher.Write([]byte(id))
- sum := pn.hasher.Sum(nil)
+ sum := pn.hasher.Sum((*hashBuf)[:0])
s := hex.EncodeToString(sum)
hashStr := numberRegex.FindAllString(s, -1)
if len(hashStr) < hashLength {
@@ -595,9 +648,17 @@ func (pn *PixelNebula) generateSVG(id string, sansEnv bool, opts *PNOptions) (sv
}
hashStr = hashStr[0:hashLength]
- // 预分配map容量以提高性能
- var p = make(map[string][2]int, 6)
+ // 从对象池获取映射
+ p := keyMapPool.Get().(map[string][2]int)
+ defer func() {
+ // 清空并归还对象池
+ for k := range p {
+ delete(p, k)
+ }
+ keyMapPool.Put(p)
+ }()
+ // 计算各部分的键值
p[string(style.TypeEnv)] = pn.calcKey(hashStr[:2], opts)
p[string(style.TypeClo)] = pn.calcKey(hashStr[2:4], opts)
p[string(style.TypeHead)] = pn.calcKey(hashStr[4:6], opts)
@@ -605,8 +666,17 @@ func (pn *PixelNebula) generateSVG(id string, sansEnv bool, opts *PNOptions) (sv
p[string(style.TypeEyes)] = pn.calcKey(hashStr[8:10], opts)
p[string(style.TypeTop)] = pn.calcKey(hashStr[10:], opts)
- // 预分配map容量
- var final = make(map[string]string, 6)
+ // 获取结果映射
+ final := mapPool.Get().(map[string]string)
+ defer func() {
+ // 清空并归还对象池
+ for k := range final {
+ delete(final, k)
+ }
+ mapPool.Put(final)
+ }()
+
+ // 获取并处理各部分的SVG
for k, v := range p {
// 获取主题颜色
themePart, err := pn.themeManager.GetTheme(v[0], v[1])
@@ -627,8 +697,10 @@ func (pn *PixelNebula) generateSVG(id string, sansEnv bool, opts *PNOptions) (sv
}
match := colorRegex.FindAllStringSubmatch(svgPart, -1)
- // 使用strings.Builder提高字符串处理性能
- var sb strings.Builder
+
+ // 从对象池获取Builder
+ sb := builderPool.Get().(*strings.Builder)
+ sb.Reset()
sb.Grow(len(svgPart) + 50) // 预分配足够的容量
lastIndex := 0
@@ -653,14 +725,21 @@ func (pn *PixelNebula) generateSVG(id string, sansEnv bool, opts *PNOptions) (sv
}
// 添加剩余部分
sb.WriteString(svgPart[lastIndex:])
+
final[k] = sb.String()
+
+ // 归还Builder到对象池
+ builderPool.Put(sb)
}
- // 使用strings.Builder构建最终SVG
- var builder strings.Builder
+ // 使用对象池获取主Builder来构建最终SVG
+ builder := builderPool.Get().(*strings.Builder)
+ builder.Reset()
// 预估SVG大小,避免多次内存分配
- builder.Grow(1024 * 2)
- builder.WriteString(pn.getSvgStart()) // 使用动态生成的svgStart
+ builder.Grow(2048) // 2KB 应该足够容纳大多数SVG
+
+ // 添加SVG开始标签
+ builder.WriteString(pn.getSvgStart())
// 获取动画定义
animations := pn.animManager.GenerateSVGAnimations()
@@ -668,32 +747,31 @@ func (pn *PixelNebula) generateSVG(id string, sansEnv bool, opts *PNOptions) (sv
builder.WriteString(animations)
}
- // 检查是否有旋转动画并获取旋转动画的SVG代码
+ // 构建和处理旋转动画 - 使用对象池
rotateAnimations := make(map[string]bool)
rotateAnimationSVGs := make(map[string]string)
+
+ // 收集旋转动画
for _, anim := range pn.animManager.GetAnimations() {
- // 检查是否为旋转动画
if rotateAnim, ok := anim.(*animation.RotateAnimation); ok {
- rotateAnimations[anim.GetTargetID()] = true
- // 获取旋转动画的SVG代码(只提取animateTransform部分)
+ targetID := anim.GetTargetID()
+ rotateAnimations[targetID] = true
+
+ // 提取animateTransform部分
svgCode := rotateAnim.GenerateSVG()
- // 提取animateTransform标签
if start := strings.Index(svgCode, ""); end != -1 {
- rotateAnimationSVGs[anim.GetTargetID()] = svgCode[start : start+end+2]
+ rotateAnimationSVGs[targetID] = svgCode[start : start+end+2]
}
}
}
-
}
- // 处理元素,如果元素有旋转动画,则包裹在g标签中并添加animateTransform
- // 只有当不是无环境模式时才添加环境
+ // 处理环境
if !sansEnv {
if _, hasRotate := rotateAnimations["env"]; hasRotate {
builder.WriteString("\n")
builder.WriteString(final["env"])
- // 添加animateTransform标签
if animSVG, ok := rotateAnimationSVGs["env"]; ok {
builder.WriteString(animSVG)
}
@@ -705,34 +783,28 @@ func (pn *PixelNebula) generateSVG(id string, sansEnv bool, opts *PNOptions) (sv
// 处理其他元素
elements := []string{"head", "clo", "top", "eyes", "mouth"}
-
- // 单独处理每个元素
for _, elem := range elements {
if _, hasRotate := rotateAnimations[elem]; hasRotate {
- // 如果元素有旋转动画,则包裹在g标签中
builder.WriteString("\n")
builder.WriteString(final[elem])
-
- // 添加animateTransform标签
if animSVG, ok := rotateAnimationSVGs[elem]; ok {
- // 提取animateTransform标签部分
- if start := strings.Index(animSVG, ""); end != -1 {
- builder.WriteString(animSVG[start : start+end+2])
- }
- }
+ builder.WriteString(animSVG)
}
builder.WriteString("\n")
} else {
- // 如果元素没有旋转动画,直接添加
builder.WriteString(final[elem])
}
}
builder.WriteString(pn.svgEnd)
svg = builder.String()
+
+ // 将生成的SVG存储到实例中
pn.imgData = []byte(svg)
+ // 归还Builder到对象池
+ builderPool.Put(builder)
+
// 如果启用了缓存,将结果存入缓存
if pn.cache != nil {
cacheKey := cache.CacheKey{
@@ -881,7 +953,7 @@ func (pn *PixelNebula) DeleteCacheItem(key string) bool {
id := parts[0]
sansEnv := parts[1] == "true"
- theme, err := strconv.Atoi(parts[2])
+ themeItem, err := strconv.Atoi(parts[2])
if err != nil {
log.Printf("pixelnebula: 解析theme失败: %v", err)
return false
@@ -897,7 +969,7 @@ func (pn *PixelNebula) DeleteCacheItem(key string) bool {
cacheKey := cache.CacheKey{
Id: id,
SansEnv: sansEnv,
- Theme: theme,
+ Theme: themeItem,
Part: part,
}
diff --git a/pixelnebula_test.go b/pixelnebula_test.go
index 3cae936..1aa584b 100644
--- a/pixelnebula_test.go
+++ b/pixelnebula_test.go
@@ -144,7 +144,7 @@ func TestDemo(t *testing.T) {
pn := NewPixelNebula()
// 设置风格和尺寸
- pn.WithStyle(style.MechStyle)
+ pn.WithStyle(style.MetaStyle)
pn.WithTheme(1)
pn.WithSize(231, 231)