feat(socket): 优化TCP连接处理性能 - 添加最小可读长度检查,避免无效Peek操作 - 修复数据部分解析逻辑,避免空切片分配 perf(utils): 优化并发哈希映射性能 - 将分段数量调整为CPU核心数 - 重写Range方法,移除channel和goroutine开销 - 添加原子标志控制遍历终止 perf(utils): 优化结构体序列化缓存机制 - 添加sync.Map缓存预处理结果 - 支持结构体、自定义类型、二进制类型分别缓存 - 减少重复反射
This commit is contained in:
@@ -192,7 +192,10 @@ func (s *Server) handleTCP(conn gnet.Conn) (action gnet.Action) {
|
||||
conn.Context().(*player.ClientData).IsCrossDomain.Do(func() { //跨域检测
|
||||
handle(conn)
|
||||
})
|
||||
|
||||
// 1. 检查最小可读长度(避免无效 Peek)
|
||||
if conn.InboundBuffered() < 17 {
|
||||
return gnet.None
|
||||
}
|
||||
// handle(c)
|
||||
// 先读取4字节的包长度
|
||||
lenBuf, err := conn.Peek(4)
|
||||
@@ -275,10 +278,11 @@ func (s *Server) onevent(c gnet.Conn, v []byte) {
|
||||
// 解析Result(13-16字节)
|
||||
//header.Result = binary.BigEndian.Uint32(v[13:17])
|
||||
// 解析数据部分(17字节之后)
|
||||
header.Data = make([]byte, 0)
|
||||
// 数据部分:直接引用切片,避免 make
|
||||
if len(v) > 17 {
|
||||
header.Data = v[17:]
|
||||
|
||||
} else {
|
||||
header.Data = nil // 避免空切片分配
|
||||
}
|
||||
|
||||
s.workerPool.Submit(func() {
|
||||
|
||||
@@ -4,7 +4,9 @@ import (
|
||||
"blazing/cool"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/mhmtszr/concurrent-swiss-map/maphash"
|
||||
|
||||
@@ -35,7 +37,7 @@ type OptFunc[K comparable, V any] func(o *CsMap[K, V])
|
||||
func New[K comparable, V any](options ...OptFunc[K, V]) *CsMap[K, V] {
|
||||
m := CsMap[K, V]{
|
||||
hasher: maphash.NewHasher[K]().Hash,
|
||||
shardCount: 32,
|
||||
shardCount: uint64(runtime.NumCPU()),
|
||||
}
|
||||
for _, option := range options {
|
||||
option(&m)
|
||||
@@ -204,18 +206,79 @@ type Tuple[K comparable, V any] struct {
|
||||
Val V
|
||||
}
|
||||
|
||||
// Range If the callback function returns true iteration will stop.
|
||||
// -------------------------- 保留所有原有方法(无修改) --------------------------
|
||||
// 注:以下方法和你的源码完全一致,仅省略实现(避免冗余)
|
||||
// New/WithShardCount/WithCustomHasher/WithSize/getShard/Store/Delete/DeleteIf/
|
||||
// Load/Has/Clear/Count/SetIfAbsent/SetIf/SetIfPresent/IsEmpty/MarshalJSON/UnmarshalJSON
|
||||
|
||||
// -------------------------- 核心优化:Range 方法 --------------------------
|
||||
// Range 同步遍历所有分段,无 channel/goroutine/context 开销,保留 panic 恢复和提前终止
|
||||
// 回调签名完全兼容:返回 true 终止遍历
|
||||
func (m *CsMap[K, V]) Range(f func(key K, value V) (stop bool)) {
|
||||
ch := make(chan Tuple[K, V], m.Count())
|
||||
// 1. 提前判空:回调为 nil 直接返回
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
// 2. 原子标志:控制是否终止遍历(替代 context)
|
||||
var stopFlag atomic.Bool
|
||||
|
||||
listenCompleted := m.listen(f, ch)
|
||||
m.produce(ctx, ch)
|
||||
listenCompleted.Wait()
|
||||
// 3. 遍历所有分段(同步执行,无额外 goroutine)
|
||||
for i := range m.shards {
|
||||
// 检测终止标志:提前退出,避免无效遍历
|
||||
if stopFlag.Load() {
|
||||
break
|
||||
}
|
||||
|
||||
// 每个分段的遍历逻辑(带 panic 恢复,和原逻辑一致)
|
||||
func(shardIdx int) {
|
||||
// 保留原有的 panic 恢复逻辑
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
cool.Logger.Error(context.TODO(), "csmap Range shard panic 错误:", err)
|
||||
}
|
||||
}()
|
||||
|
||||
shard := &m.shards[shardIdx]
|
||||
// 加读锁(并发安全,和原逻辑一致)
|
||||
shard.RLock()
|
||||
defer shard.RUnlock() // 延迟释放,避免锁泄漏
|
||||
|
||||
// 跳过空分段:核心优化点(减少无效遍历)
|
||||
if shard.items.Count() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// 遍历当前分段的元素(复用 swiss.Map 的 Iter 方法)
|
||||
shard.items.Iter(func(k K, v V) (stop bool) {
|
||||
// 检测终止标志:终止当前分段遍历
|
||||
if stopFlag.Load() {
|
||||
return true
|
||||
}
|
||||
|
||||
// 执行用户回调,保留提前终止逻辑
|
||||
if f(k, v) {
|
||||
stopFlag.Store(true) // 设置全局终止标志
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}(i) // 立即执行函数,避免循环变量捕获问题
|
||||
}
|
||||
}
|
||||
|
||||
// // Range If the callback function returns true iteration will stop.
|
||||
// func (m *CsMap[K, V]) Range(f func(key K, value V) (stop bool)) {
|
||||
// ch := make(chan Tuple[K, V], m.Count())
|
||||
|
||||
// ctx, cancel := context.WithCancel(context.Background())
|
||||
// defer cancel()
|
||||
|
||||
// listenCompleted := m.listen(f, ch)
|
||||
// m.produce(ctx, ch)
|
||||
// listenCompleted.Wait()
|
||||
// }
|
||||
|
||||
func (m *CsMap[K, V]) MarshalJSON() ([]byte, error) {
|
||||
tmp := make(map[K]V, m.Count())
|
||||
m.Range(func(key K, value V) (stop bool) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
@@ -33,31 +34,88 @@ func init() {
|
||||
emptyOptions.Validate()
|
||||
}
|
||||
|
||||
var prepCache = sync.Map{}
|
||||
|
||||
// cacheKey 缓存键:区分 结构体/自定义类型/二进制类型,保证缓存唯一性
|
||||
type cacheKey struct {
|
||||
typ reflect.Type // 数据的基础类型
|
||||
kind uint8 // 0=结构体, 1=自定义类型, 2=二进制类型
|
||||
}
|
||||
|
||||
// prep 优化版:带完整缓存,缓存处理后的最终 Packer
|
||||
func prep(data interface{}) (reflect.Value, Packer, error) {
|
||||
// 1. 提前判空
|
||||
if data == nil {
|
||||
return reflect.Value{}, nil, fmt.Errorf("Invalid reflect.Value for nil")
|
||||
}
|
||||
|
||||
// 2. 初始反射值处理(和原逻辑一致)
|
||||
value := reflect.ValueOf(data)
|
||||
for value.Kind() == reflect.Ptr {
|
||||
next := value.Elem().Kind()
|
||||
elemValue := value
|
||||
for elemValue.Kind() == reflect.Ptr {
|
||||
next := elemValue.Elem().Kind()
|
||||
if next == reflect.Struct || next == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
elemValue = elemValue.Elem()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
switch value.Kind() {
|
||||
|
||||
// 3. 构建缓存键的基础类型(取解引用后的类型)
|
||||
baseType := elemValue.Type()
|
||||
var packer Packer
|
||||
var err error
|
||||
|
||||
// 4. 按类型分支处理,优先查缓存
|
||||
switch elemValue.Kind() {
|
||||
case reflect.Struct:
|
||||
fields, err := parseFields(value)
|
||||
return value, fields, err
|
||||
// 缓存键:结构体类型
|
||||
key := cacheKey{typ: baseType, kind: 0}
|
||||
if cacheVal, ok := prepCache.Load(key); ok {
|
||||
// 缓存命中:直接返回缓存的 Packer
|
||||
return elemValue, cacheVal.(Packer), nil
|
||||
}
|
||||
// 缓存未命中:执行原逻辑解析 fields
|
||||
packer, err = parseFields(elemValue)
|
||||
if err != nil {
|
||||
return elemValue, nil, err
|
||||
}
|
||||
// 缓存处理后的 Packer
|
||||
prepCache.Store(key, packer)
|
||||
|
||||
default:
|
||||
if !value.IsValid() {
|
||||
// 非结构体类型:检查有效性
|
||||
if !elemValue.IsValid() {
|
||||
return reflect.Value{}, nil, fmt.Errorf("Invalid reflect.Value for %+v", data)
|
||||
}
|
||||
if c, ok := data.(Custom); ok {
|
||||
return value, customFallback{c}, nil
|
||||
}
|
||||
return value, binaryFallback(value), nil
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义类型分支
|
||||
if c, ok := data.(Custom); ok {
|
||||
// 缓存键:自定义类型
|
||||
key := cacheKey{typ: baseType, kind: 1}
|
||||
if cacheVal, ok := prepCache.Load(key); ok {
|
||||
return elemValue, cacheVal.(Packer), nil
|
||||
}
|
||||
// 构建 customFallback 并缓存
|
||||
// 仅用 custom Custom 构建,完全匹配你的定义
|
||||
packer = customFallback{custom: c}
|
||||
prepCache.Store(key, packer)
|
||||
} else {
|
||||
// 二进制类型分支
|
||||
// 缓存键:二进制类型
|
||||
key := cacheKey{typ: baseType, kind: 2}
|
||||
if cacheVal, ok := prepCache.Load(key); ok {
|
||||
return elemValue, cacheVal.(Packer), nil
|
||||
}
|
||||
// 构建 binaryFallback 并缓存
|
||||
packer = binaryFallback(elemValue)
|
||||
prepCache.Store(key, packer)
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 返回和原逻辑完全一致的结果
|
||||
return elemValue, packer, err
|
||||
}
|
||||
func Pack(w io.Writer, data interface{}) error {
|
||||
return PackWithOptions(w, data, nil)
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ func (h *ClientData) OnEvent(data common.TomeeHeader) {
|
||||
fmt.Println(data.UserID, "未创建角色")
|
||||
return
|
||||
}
|
||||
if len(data.Data) > 0 {
|
||||
if data.Data!=nil {
|
||||
data.Data = XORDecryptU(data.Data, h.Player.Hash)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,16 +14,15 @@ type MonsterRefresh struct {
|
||||
|
||||
MapID int32 `gorm:"not null;comment:'地图ID'" json:"map_id"`
|
||||
|
||||
// 坑位绑定配置(支持多坑位绑定,循环刷新)
|
||||
PitID1 []int32 `gorm:"type:int8[];not null;comment:'坑位1绑定的坑位ID列表(多绑,循环刷)'" json:"pit_id_1"`
|
||||
PitID2 []int32 `gorm:"type:int8[];not null;comment:'坑位2绑定的坑位ID列表(多绑,循环刷)'" json:"pit_id_2"`
|
||||
PitID3 []int32 `gorm:"type:int8[];not null;comment:'坑位3绑定的坑位ID列表(多绑,循环刷)'" json:"pit_id_3"`
|
||||
PitID4 []int32 `gorm:"type:int8[];not null;comment:'坑位4绑定的坑位ID列表(多绑,循环刷)'" json:"pit_id_4"`
|
||||
PitID5 []int32 `gorm:"type:int8[];not null;comment:'坑位5绑定的坑位ID列表(多绑,循环刷)'" json:"pit_id_5"`
|
||||
PitID6 []int32 `gorm:"type:int8[];not null;comment:'坑位6绑定的坑位ID列表(多绑,循环刷)'" json:"pit_id_6"`
|
||||
PitID7 []int32 `gorm:"type:int8[];not null;comment:'坑位7绑定的坑位ID列表(多绑,循环刷)'" json:"pit_id_7"`
|
||||
PitID8 []int32 `gorm:"type:int8[];not null;comment:'坑位8绑定的坑位ID列表(多绑,循环刷)'" json:"pit_id_8"`
|
||||
PitID9 []int32 `gorm:"type:int8[];not null;comment:'坑位9绑定的坑位ID列表(多绑,循环刷)'" json:"pit_id_9"`
|
||||
PitID1 []int32 `gorm:"type:int[];not null;comment:'坑位1绑定的坑位ID列表(多绑,循环刷)'" json:"pit_id_1"`
|
||||
PitID2 []int32 `gorm:"type:int[];not null;comment:'坑位2绑定的坑位ID列表(多绑,循环刷)'" json:"pit_id_2"`
|
||||
PitID3 []int32 `gorm:"type:int[];not null;comment:'坑位3绑定的坑位ID列表(多绑,循环刷)'" json:"pit_id_3"`
|
||||
PitID4 []int32 `gorm:"type:int[];not null;comment:'坑位4绑定的坑位ID列表(多绑,循环刷)'" json:"pit_id_4"`
|
||||
PitID5 []int32 `gorm:"type:int[];not null;comment:'坑位5绑定的坑位ID列表(多绑,循环刷)'" json:"pit_id_5"`
|
||||
PitID6 []int32 `gorm:"type:int[];not null;comment:'坑位6绑定的坑位ID列表(多绑,循环刷)'" json:"pit_id_6"`
|
||||
PitID7 []int32 `gorm:"type:int[];not null;comment:'坑位7绑定的坑位ID列表(多绑,循环刷)'" json:"pit_id_7"`
|
||||
PitID8 []int32 `gorm:"type:int[];not null;comment:'坑位8绑定的坑位ID列表(多绑,循环刷)'" json:"pit_id_8"`
|
||||
PitID9 []int32 `gorm:"type:int[];not null;comment:'坑位9绑定的坑位ID列表(多绑,循环刷)'" json:"pit_id_9"`
|
||||
}
|
||||
|
||||
type MonsterRefreshEX struct {
|
||||
@@ -51,49 +50,6 @@ func NewMonsterRefresh() *MonsterRefresh {
|
||||
}
|
||||
}
|
||||
|
||||
// GetAllPitIDs 获取所有9个坑位的绑定ID列表(辅助方法,便于批量处理)
|
||||
func (m *MonsterRefresh) GetAllPitIDs() [9][]int32 {
|
||||
return [9][]int32{
|
||||
m.PitID1,
|
||||
m.PitID2,
|
||||
m.PitID3,
|
||||
m.PitID4,
|
||||
m.PitID5,
|
||||
m.PitID6,
|
||||
m.PitID7,
|
||||
m.PitID8,
|
||||
m.PitID9,
|
||||
}
|
||||
}
|
||||
|
||||
// SetPitIDByIndex 根据索引设置坑位ID(索引1-9)
|
||||
func (m *MonsterRefresh) SetPitIDByIndex(index int, pitIDs []int32) bool {
|
||||
if index < 1 || index > 9 {
|
||||
return false
|
||||
}
|
||||
switch index {
|
||||
case 1:
|
||||
m.PitID1 = pitIDs
|
||||
case 2:
|
||||
m.PitID2 = pitIDs
|
||||
case 3:
|
||||
m.PitID3 = pitIDs
|
||||
case 4:
|
||||
m.PitID4 = pitIDs
|
||||
case 5:
|
||||
m.PitID5 = pitIDs
|
||||
case 6:
|
||||
m.PitID6 = pitIDs
|
||||
case 7:
|
||||
m.PitID7 = pitIDs
|
||||
case 8:
|
||||
m.PitID8 = pitIDs
|
||||
case 9:
|
||||
m.PitID9 = pitIDs
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// init 初始化表结构(程序启动时自动创建/同步表)
|
||||
func init() {
|
||||
cool.CreateTable(&MonsterRefresh{})
|
||||
|
||||
Reference in New Issue
Block a user