perf: 使用数组代替map优化元素计算性能
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful

This commit is contained in:
xinian
2026-04-06 07:16:57 +08:00
committed by cnb
parent 77057e01b6
commit 4ea9864833

View File

@@ -42,15 +42,15 @@ const (
maxMatrixSize = 227 // 矩阵维度覆盖最大属性ID 226 maxMatrixSize = 227 // 矩阵维度覆盖最大属性ID 226
) )
// 合法单属性ID集合快速校验 // 合法单属性ID集合按ID直接索引避免运行时 map 查找
var validSingleElementIDs = map[int]bool{ var validSingleElementIDs = [maxMatrixSize]bool{
1: true, 2: true, 3: true, 4: true, 5: true, 6: true, 7: true, 8: true, 9: true, 10: true, 1: true, 2: true, 3: true, 4: true, 5: true, 6: true, 7: true, 8: true, 9: true, 10: true,
11: true, 12: true, 13: true, 14: true, 15: true, 16: true, 17: true, 18: true, 19: true, 20: true, 11: true, 12: true, 13: true, 14: true, 15: true, 16: true, 17: true, 18: true, 19: true, 20: true,
221: true, 222: true, 223: true, 224: true, 225: true, 226: true, 221: true, 222: true, 223: true, 224: true, 225: true, 226: true,
} }
// 元素名称映射(全属性对应,便于日志输出) // 元素名称映射(按ID直接索引,便于日志输出)
var elementNameMap = map[ElementType]string{ var elementNameMap = [maxMatrixSize]string{
ElementTypeGrass: "GRASS", ElementTypeGrass: "GRASS",
ElementTypeWater: "WATER", ElementTypeWater: "WATER",
ElementTypeFire: "FIRE", ElementTypeFire: "FIRE",
@@ -198,46 +198,55 @@ type ElementCombination struct {
ID int // 组合唯一ID ID int // 组合唯一ID
} }
// 全局预加载资源(程序启动时init初始化,运行时直接使用 // 全局预加载资源(程序启动时初始化,运行时只读
var ( var (
// 元素组合池key=组合IDvalue=组合实例(预加载所有合法组合) validCombinationIDs [maxMatrixSize]bool
elementCombinationPool = make(map[int]*ElementCombination, 150) // 128双+26单=154预分配足够容量 elementCombinationPool [maxMatrixSize]ElementCombination
// 单属性克制矩阵预初始化所有特殊克制关系默认1.0 dualElementSecondaryPool [maxMatrixSize]ElementType
matrix [maxMatrixSize][maxMatrixSize]float64 matrix [maxMatrixSize][maxMatrixSize]float64
Calculator *ElementCalculator
) )
// init 预加载所有资源(程序启动时执行一次,无并发问题) // init 预加载所有资源(程序启动时执行一次,无并发问题)
func init() { func init() {
// 1. 初始化单属性克制矩阵
initFullTableMatrix() initFullTableMatrix()
initElementCombinationPool()
Calculator = NewElementCalculator()
}
// 2. 预加载所有单属性组合 func initElementCombinationPool() {
for id := range validSingleElementIDs { for id, valid := range validSingleElementIDs {
combo := &ElementCombination{ if !valid {
continue
}
validCombinationIDs[id] = true
elementCombinationPool[id] = ElementCombination{
Primary: ElementType(id), Primary: ElementType(id),
Secondary: nil,
ID: id, ID: id,
} }
elementCombinationPool[id] = combo
} }
// 3. 预加载所有双属性组合
for dualID, atts := range dualElementMap { for dualID, atts := range dualElementMap {
primaryID, secondaryID := atts[0], atts[1] primaryID, secondaryID := atts[0], atts[1]
// 按ID升序排序保证组合一致性
primary, secondary := ElementType(primaryID), ElementType(secondaryID) primary, secondary := ElementType(primaryID), ElementType(secondaryID)
if primary > secondary { if primary > secondary {
primary, secondary = secondary, primary primary, secondary = secondary, primary
} }
combo := &ElementCombination{
dualElementSecondaryPool[dualID] = secondary
validCombinationIDs[dualID] = true
elementCombinationPool[dualID] = ElementCombination{
Primary: primary, Primary: primary,
Secondary: &secondary, Secondary: &dualElementSecondaryPool[dualID],
ID: dualID, ID: dualID,
} }
elementCombinationPool[dualID] = combo
} }
} }
func isValidCombinationID(id int) bool {
return id > 0 && id < maxMatrixSize && validCombinationIDs[id]
}
// IsDual 判断是否为双属性 // IsDual 判断是否为双属性
func (ec *ElementCombination) IsDual() bool { func (ec *ElementCombination) IsDual() bool {
return ec.Secondary != nil return ec.Secondary != nil
@@ -245,84 +254,82 @@ func (ec *ElementCombination) IsDual() bool {
// Elements 获取所有属性列表 // Elements 获取所有属性列表
func (ec *ElementCombination) Elements() []ElementType { func (ec *ElementCombination) Elements() []ElementType {
if ec.IsDual() { if secondary := ec.Secondary; secondary != nil {
return []ElementType{ec.Primary, *ec.Secondary} return []ElementType{ec.Primary, *secondary}
} }
return []ElementType{ec.Primary} return []ElementType{ec.Primary}
} }
// String 友好格式化输出 // String 友好格式化输出
func (ec *ElementCombination) String() string { func (ec *ElementCombination) String() string {
primaryName := elementNameMap[ec.Primary] if secondary := ec.Secondary; secondary != nil {
if !ec.IsDual() { return fmt.Sprintf("(%s, %s)", elementNameMap[ec.Primary], elementNameMap[*secondary])
return fmt.Sprintf("(%s)", primaryName)
} }
return fmt.Sprintf("(%s, %s)", primaryName, elementNameMap[*ec.Secondary]) return fmt.Sprintf("(%s)", elementNameMap[ec.Primary])
} }
// ElementCalculator 无锁元素克制计算器(依赖预加载资源 // ElementCalculator 无锁元素克制计算器(所有倍数在初始化阶段预计算
type ElementCalculator struct { type ElementCalculator struct {
offensiveCache map[string]float64 // 攻击克制缓存(运行时填充,无并发写) offensiveTable [maxMatrixSize][maxMatrixSize]float64
} }
// NewElementCalculator 创建计算器实例(仅初始化缓存) // NewElementCalculator 创建计算器实例(构建只读查表缓存)
func NewElementCalculator() *ElementCalculator { func NewElementCalculator() *ElementCalculator {
return &ElementCalculator{ c := &ElementCalculator{}
offensiveCache: make(map[string]float64, 4096), // 预分配大容量缓存 c.initOffensiveTable()
return c
}
func (c *ElementCalculator) initOffensiveTable() {
for attackerID, valid := range validCombinationIDs {
if !valid {
continue
}
attacker := &elementCombinationPool[attackerID]
for defenderID, valid := range validCombinationIDs {
if !valid {
continue
}
defender := &elementCombinationPool[defenderID]
c.offensiveTable[attackerID][defenderID] = c.calculateMultiplier(attacker, defender)
}
} }
} }
// getMatrixValue 直接返回矩阵值修复核心问题不再将0转换为1 // getMatrixValue 直接返回矩阵值修复核心问题不再将0转换为1
func (c *ElementCalculator) getMatrixValue(attacker, defender ElementType) float64 { func (c *ElementCalculator) getMatrixValue(attacker, defender ElementType) float64 {
return matrix[attacker][defender] // 矩阵默认已初始化1.0,特殊值直接返回 return matrix[attacker][defender]
} }
// GetCombination 获取元素组合(直接从预加载池读取 // GetCombination 获取元素组合(直接按ID索引
func (c *ElementCalculator) GetCombination(id int) (*ElementCombination, error) { func (c *ElementCalculator) GetCombination(id int) (*ElementCombination, error) {
combo, exists := elementCombinationPool[id] if !isValidCombinationID(id) {
if !exists {
return nil, fmt.Errorf("invalid element combination ID: %d", id) return nil, fmt.Errorf("invalid element combination ID: %d", id)
} }
return combo, nil return &elementCombinationPool[id], nil
} }
// GetOffensiveMultiplier 计算攻击方→防御方的克制倍数(缓存优先 // GetOffensiveMultiplier 计算攻击方→防御方的克制倍数(只读查表
func (c *ElementCalculator) GetOffensiveMultiplier(attackerID, defenderID int) (float64, error) { func (c *ElementCalculator) GetOffensiveMultiplier(attackerID, defenderID int) (float64, error) {
// 1. 获取预加载的组合实例 if !isValidCombinationID(attackerID) {
attacker, err := c.GetCombination(attackerID) return 0, fmt.Errorf("attacker invalid: invalid element combination ID: %d", attackerID)
if err != nil {
return 0, fmt.Errorf("attacker invalid: %w", err)
} }
defender, err := c.GetCombination(defenderID) if !isValidCombinationID(defenderID) {
if err != nil { return 0, fmt.Errorf("defender invalid: invalid element combination ID: %d", defenderID)
return 0, fmt.Errorf("defender invalid: %w", err)
} }
return c.offensiveTable[attackerID][defenderID], nil
// 2. 缓存键(全局唯一)
cacheKey := fmt.Sprintf("a%d_d%d", attackerID, defenderID)
if val, exists := c.offensiveCache[cacheKey]; exists {
return val, nil
}
// 3. 核心计算+缓存
val := c.calculateMultiplier(attacker, defender)
c.offensiveCache[cacheKey] = val
return val, nil
} }
// calculateMultiplier 核心克制计算逻辑 // calculateMultiplier 核心克制计算逻辑
func (c *ElementCalculator) calculateMultiplier(attacker, defender *ElementCombination) float64 { func (c *ElementCalculator) calculateMultiplier(attacker, defender *ElementCombination) float64 {
// 场景1单→单
if !attacker.IsDual() && !defender.IsDual() { if !attacker.IsDual() && !defender.IsDual() {
return c.getMatrixValue(attacker.Primary, defender.Primary) return c.getMatrixValue(attacker.Primary, defender.Primary)
} }
// 场景2单→双
if !attacker.IsDual() { if !attacker.IsDual() {
y1, y2 := defender.Primary, *defender.Secondary y1, y2 := defender.Primary, *defender.Secondary
m1 := c.getMatrixValue(attacker.Primary, y1) m1 := c.getMatrixValue(attacker.Primary, y1)
m2 := c.getMatrixValue(attacker.Primary, y2) m2 := c.getMatrixValue(attacker.Primary, y2)
switch { switch {
case m1 == 2 && m2 == 2: case m1 == 2 && m2 == 2:
return 4.0 return 4.0
@@ -333,12 +340,10 @@ func (c *ElementCalculator) calculateMultiplier(attacker, defender *ElementCombi
} }
} }
// 场景3双→单
if !defender.IsDual() { if !defender.IsDual() {
return c.calculateDualToSingle(attacker.Primary, *attacker.Secondary, defender.Primary) return c.calculateDualToSingle(attacker.Primary, *attacker.Secondary, defender.Primary)
} }
// 场景4双→双
x1, x2 := attacker.Primary, *attacker.Secondary x1, x2 := attacker.Primary, *attacker.Secondary
y1, y2 := defender.Primary, *defender.Secondary y1, y2 := defender.Primary, *defender.Secondary
coeffY1 := c.calculateDualToSingle(x1, x2, y1) coeffY1 := c.calculateDualToSingle(x1, x2, y1)
@@ -350,7 +355,6 @@ func (c *ElementCalculator) calculateMultiplier(attacker, defender *ElementCombi
func (c *ElementCalculator) calculateDualToSingle(attacker1, attacker2, defender ElementType) float64 { func (c *ElementCalculator) calculateDualToSingle(attacker1, attacker2, defender ElementType) float64 {
k1 := c.getMatrixValue(attacker1, defender) k1 := c.getMatrixValue(attacker1, defender)
k2 := c.getMatrixValue(attacker2, defender) k2 := c.getMatrixValue(attacker2, defender)
switch { switch {
case k1 == 2 && k2 == 2: case k1 == 2 && k2 == 2:
return 4.0 return 4.0
@@ -361,60 +365,49 @@ func (c *ElementCalculator) calculateDualToSingle(attacker1, attacker2, defender
} }
} }
var Calculator = NewElementCalculator()
// TestAllScenarios 全场景测试(验证预加载和计算逻辑) // TestAllScenarios 全场景测试(验证预加载和计算逻辑)
func TestAllScenarios() { func TestAllScenarios() {
// 测试1单→单草→水
m1, _ := Calculator.GetOffensiveMultiplier(1, 2) m1, _ := Calculator.GetOffensiveMultiplier(1, 2)
fmt.Println("草→水: %.2f预期2.0", m1) fmt.Println("草→水: %.2f预期2.0", m1)
if math.Abs(m1-2.0) > 0.001 { if math.Abs(m1-2.0) > 0.001 {
fmt.Println("测试1失败实际%.2f", m1) fmt.Println("测试1失败实际%.2f", m1)
} }
// 测试2特殊单→单混沌→虚空
m2, _ := Calculator.GetOffensiveMultiplier(222, 226) m2, _ := Calculator.GetOffensiveMultiplier(222, 226)
fmt.Println("混沌→虚空: %.2f预期0.0", m2) fmt.Println("混沌→虚空: %.2f预期0.0", m2)
if math.Abs(m2-0.0) > 0.001 { if math.Abs(m2-0.0) > 0.001 {
fmt.Println("测试2失败实际%.2f", m2) fmt.Println("测试2失败实际%.2f", m2)
} }
// 测试3单→双火→冰龙43
m3, _ := Calculator.GetOffensiveMultiplier(3, 43) m3, _ := Calculator.GetOffensiveMultiplier(3, 43)
fmt.Println("火→冰龙: %.2f预期1.5", m3) fmt.Println("火→冰龙: %.2f预期1.5", m3)
if math.Abs(m3-1.5) > 0.001 { if math.Abs(m3-1.5) > 0.001 {
fmt.Println("测试3失败实际%.2f", m3) fmt.Println("测试3失败实际%.2f", m3)
} }
// 测试4双→特殊单混沌暗影92→神灵223
m4, _ := Calculator.GetOffensiveMultiplier(92, 223) m4, _ := Calculator.GetOffensiveMultiplier(92, 223)
fmt.Println("混沌暗影→神灵: %.2f预期1.25", m4) fmt.Println("混沌暗影→神灵: %.2f预期1.25", m4)
if math.Abs(m4-1.25) > 0.001 { if math.Abs(m4-1.25) > 0.001 {
fmt.Println("测试4失败实际%.2f", m4) fmt.Println("测试4失败实际%.2f", m4)
} }
// 测试5双→双虚空邪灵113→混沌远古98
m5, _ := Calculator.GetOffensiveMultiplier(113, 98) m5, _ := Calculator.GetOffensiveMultiplier(113, 98)
fmt.Println("虚空邪灵→混沌远古: %.2f预期0.875", m5) fmt.Println("虚空邪灵→混沌远古: %.2f预期0.875", m5)
if math.Abs(m5-0.875) > 0.001 { if math.Abs(m5-0.875) > 0.001 {
fmt.Println("测试5失败实际%.2f", m5) fmt.Println("测试5失败实际%.2f", m5)
} }
// 测试6缓存命中
m6, _ := Calculator.GetOffensiveMultiplier(113, 98) m6, _ := Calculator.GetOffensiveMultiplier(113, 98)
if math.Abs(m6-m5) > 0.001 { if math.Abs(m6-m5) > 0.001 {
fmt.Println("测试6失败缓存未命中") fmt.Println("测试6失败缓存未命中")
} }
// 测试7含无效组合电→地面
m7, _ := Calculator.GetOffensiveMultiplier(5, 7) m7, _ := Calculator.GetOffensiveMultiplier(5, 7)
fmt.Println("电→地面: %.2f预期0.0", m7) fmt.Println("电→地面: %.2f预期0.0", m7)
if math.Abs(m7-0.0) > 0.001 { if math.Abs(m7-0.0) > 0.001 {
fmt.Println("测试7失败实际%.2f", m7) fmt.Println("测试7失败实际%.2f", m7)
} }
// 测试8双属性含无效电战斗→地面
m8, _ := Calculator.GetOffensiveMultiplier(35, 7) m8, _ := Calculator.GetOffensiveMultiplier(35, 7)
fmt.Println("电战斗→地面: %.2f预期0.25", m8) fmt.Println("电战斗→地面: %.2f预期0.25", m8)
if math.Abs(m8-0.25) > 0.001 { if math.Abs(m8-0.25) > 0.001 {