搜索核心 (Search)¶
simai_search.search.RhythmSearcher 实现了基于节奏型的模糊搜索算法。
搜索流程¶
-
Query 解析: 将用户的查询字符串(如
{16}1,1,)通过SimaiParser解析为相对时间序列(Deltas)。- 例如:
[{'time': 0.0, 'delta': 0.0, 'is_star': False}, ...]
- 例如:
-
数据库获取: 根据筛选条件预取
note_data。 -
匹配算法 (
_match_pattern):- 遍历谱面中的每一个音符作为潜在的“起始点”。
- BPM 检查: 如果指定了 BPM 范围,检查起始音符的 BPM 是否符合要求。
- 星星检查: 如果查询的第一个音符要求是星星 (
is_star=True),则起始音符必须是星星。 - 序列匹配:
- 对于查询序列中的每一个后续音符,检查谱面中是否存在对应时间点(
Start + Delta)的音符。 - 同时检查该时间点音符是否满足查询中指定的 BPM 和星星约束。
- 对于查询序列中的每一个后续音符,检查谱面中是否存在对应时间点(
- 容差 (Tolerance): 默认允许 0.01 拍的误差。
性能优化¶
内存缓存 (In-Memory Caching)¶
为了解决高频数据库读取带来的性能瓶颈,搜索模块实现了激进的缓存策略:
- 启动预热: 服务启动时,通过
_ensure_cache()方法一次性加载所有谱面的元数据(ID、标题、难度、等级)和预解析的节奏事件 (events) 到内存中。 - 内存匹配: 搜索请求直接在内存中的列表进行遍历和匹配,完全避免了数据库 I/O 和 JSON 解析开销。
- 内存优化:
- 为了防止内存溢出,原始谱面文本 (
raw_content) 不包含在启动缓存中。 - 仅当算法发现匹配项后,才会按需(On-Demand)从数据库获取原始文本以提取代码片段(Snippet)。
- 这种"双层加载"机制确保了在低内存服务器(如 1GB RAM)上的稳定性。
- 为了防止内存溢出,原始谱面文本 (
算法优化¶
- 使用
bisect_left(二分查找) 在有序的谱面事件列表中快速查找目标时间点,时间复杂度从 O(N) 降低到 O(log N) 对于单次匹配检查。