diff --git a/README.md b/README.md index 38b0ac7..7313359 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@
Go Reference - Go Report Card - License + Go Report Card + License Version
@@ -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 @@
Go Reference - Go Report Card - License + Go Report Card + License Version
@@ -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)