序列化热路径去重:to_dict 里重复求值的属性改局部复用
为什么:snapshot() 同时压在 SSE 每 250ms 推送和 full=1 动作回执两条
高频路径上,而它的成本大头是重复求值——profile 显示单次快照
1,128,001 次函数调用里近半是属性链重复计算同一份配置数据。
- Plot.to_dict:grow_days 原来经 4 个属性各求值一遍(本体 + days_left
+ is_ripe + stage),16 块地就是 64 次配置表查询。改为算一次局部
复用;stage 的实现体拆到 _stage(grow),规则本体仍只有一份。
- Animal.to_dict:info 配置表原来查 4 次、cycle int() 两遍,改取一次。
- GameState.to_dict:weather_info 3 键 2 次查询;granary_free 原来把
granary_capacity 和 storage_used 整个重算,改算一轮复用。
行为零变化(规则本体全在原属性里,532 项测试全绿)。
基准(tools/bench.py --repeat 5,最坏形态 16 地 + 8 牲畜):
场景 优化前 优化后
engine.snapshot() 50.1 µs 44.2 µs -12%
session.snapshot() 51.0 µs 41.9 µs -18%
build_state_message() 50.9 µs 43.3 µs -15%
engine.available_actions 52.7 µs 48.7 µs -8%
单次 snapshot 函数调用数 1,128,001 → 588,001(-48%),本次数字已记入
bench.py docstring 作长期参考。重复计数器全 OK(to_dict/snapshot/
net_worth 每请求仍各 1 次,无回归)。
This commit is contained in:
1 parent
4bc7729715
commit
a9bc8238ab
3 files changed
+63
-17
No files matched your search
+34
-17
@@ -80,9 +80,13 @@ class Plot:
|
||||
@property
|
||||
def stage(self) -> str:
|
||||
"""人类可读的生长阶段。"""
|
||||
return self._stage(self.grow_days)
|
||||
|
||||
def _stage(self, grow: int) -> str:
|
||||
"""stage 的实现体。to_dict 已算好 grow_days,走这里避免重复求值。"""
|
||||
if self.crop is None:
|
||||
return "空地"
|
||||
progress = self.growth / max(1, self.grow_days)
|
||||
progress = self.growth / max(1, grow)
|
||||
name = "已播种"
|
||||
for threshold, label in config.GROWTH_STAGES:
|
||||
if progress >= threshold:
|
||||
@@ -91,20 +95,24 @@ class Plot:
|
||||
|
||||
# ---------------------------------------------------------------- 序列化
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
# 热路径(每次快照对每块地调一次):grow_days 原来经 4 个属性各求值
|
||||
# 一遍(本处 + days_left + is_ripe + stage),这里算一次局部复用。
|
||||
# 规则本体仍以同名属性为唯一来源,此处只是等价的直接计算。
|
||||
info = config.CROPS.get(self.crop) if self.crop else None
|
||||
grow = int(info["grow_days"]) if info else 0
|
||||
return {
|
||||
"index": self.index,
|
||||
"position": self.index + 1,
|
||||
"crop": self.crop,
|
||||
"crop_name": info["name"] if info else None,
|
||||
"growth": round(self.growth, 2),
|
||||
"grow_days": self.grow_days,
|
||||
"days_left": self.days_left,
|
||||
"is_ripe": self.is_ripe,
|
||||
"is_empty": self.is_empty,
|
||||
"grow_days": grow,
|
||||
"days_left": max(0, grow - int(self.growth)) if info else 0,
|
||||
"is_ripe": info is not None and self.growth >= grow,
|
||||
"is_empty": self.crop is None,
|
||||
"moisture": self.moisture,
|
||||
"water_capacity": config.WATER_CAPACITY,
|
||||
"stage": self.stage,
|
||||
"stage": self._stage(grow),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@@ -162,18 +170,22 @@ class Animal:
|
||||
|
||||
# ---------------------------------------------------------------- 序列化
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
# 热路径:info 原来经 name/product/cycle/days_to_product 查 4 次配置表,
|
||||
# 这里取一次局部复用;is_sick 与 days_to_product 的规则本体仍在属性里。
|
||||
info = config.ANIMALS[self.kind]
|
||||
cycle = int(info["cycle"])
|
||||
return {
|
||||
"id": self.aid,
|
||||
"kind": self.kind,
|
||||
"name": self.name,
|
||||
"product": self.product,
|
||||
"name": info["name"],
|
||||
"product": info["product"],
|
||||
"fed": self.fed,
|
||||
"progress": self.progress,
|
||||
"cycle": self.cycle,
|
||||
"days_to_product": self.days_to_product,
|
||||
"cycle": cycle,
|
||||
"days_to_product": max(0, cycle - self.progress),
|
||||
"pending": self.pending,
|
||||
"starve": self.starve,
|
||||
"is_sick": self.is_sick,
|
||||
"is_sick": self.starve >= 2,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@@ -311,22 +323,27 @@ class GameState:
|
||||
# 所以只算一次再分发给 net_worth / goal_index / next_goal。
|
||||
worth = self.net_worth
|
||||
goal = config.goal_index(worth)
|
||||
winfo = config.weather_info(self.weather)
|
||||
finfo = config.weather_info(self.forecast)
|
||||
# granary_free 原来会把 granary_capacity 和 storage_used 各重算一遍
|
||||
cap = self.granary_capacity
|
||||
used = self.storage_used
|
||||
return {
|
||||
"day": self.day,
|
||||
"weather": self.weather,
|
||||
"weather_name": config.weather_info(self.weather)["name"],
|
||||
"weather_desc": config.weather_info(self.weather)["desc"],
|
||||
"weather_name": winfo["name"],
|
||||
"weather_desc": winfo["desc"],
|
||||
"forecast": self.forecast,
|
||||
"forecast_name": config.weather_info(self.forecast)["name"],
|
||||
"forecast_name": finfo["name"],
|
||||
"gold": self.gold,
|
||||
"level": self.level,
|
||||
"exp": self.exp,
|
||||
"exp_needed": self.exp_needed,
|
||||
"feed": self.feed,
|
||||
"inventory": dict(sorted(self.inventory.items())),
|
||||
"storage_used": self.storage_used,
|
||||
"granary_capacity": self.granary_capacity,
|
||||
"granary_free": self.granary_free,
|
||||
"storage_used": used,
|
||||
"granary_capacity": cap,
|
||||
"granary_free": max(0, cap - used),
|
||||
"buildings": dict(self.buildings),
|
||||
"plots": [p.to_dict() for p in self.plots],
|
||||
"plot_count": len(self.plots),
|
||||
|
||||
Reference in new issue
Block a user