构建类似 Faiss 的 ANN 并非人人都喜欢
Source: Dev.to
(一份你根本没请求的生存指南)
构建一个类似 Faiss 的 ANN 系统并不难。
构建一个 快速 的 ANN 系统却会让你质疑自己所有的人生决定。
如果你在想,“向量搜索能有多难?”… 恭喜你——这篇文章就是为你准备的。
Act 1: The Innocent Beginning (Python Era)
你从 Python 开始。
生活很美好 —— NumPy 正常工作。
- 准确率看起来不错。
- 延迟……还能接受。
你对自己说:“我先原型一下,之后再优化。” —— 经典的新手错误。
Act 2: “Let’s Rewrite It in C++” (Boss Music Starts)
某个时候查询变慢了,你说出了禁忌之词:“为了速度,重写成 C++ 吧”。
突然:
- 你不再调试逻辑,而是在调试存在本身。
- 段错误、未定义行为、内存崩溃……原因你誓言是非法的。
- 修复一个 bug → 三个新 bug 诞生。
Act 3: Speed‑Bound → Memory‑Bound (The Plot Twist)
一开始你是受速度限制:
- 糟糕的循环
- 差劲的数据布局
- 未优化的数学运算
你修复了这些,延迟下降,感觉自己无敌——但再也没有提升。
领悟:你已经不再受速度限制,而是受内存限制,这才是真正的痛苦开始的地方。
Act 4: Milliseconds Matter (You Finally Understand Big Tech)
秒级已经轻松,毫秒级才是战争。
- 改动一个文件 → 延迟飙升,QPS 下降。
- 缓存未命中爆炸。
经验教训
- 缓存未命中可能导致数百 QPS 的损失。
- 内存访问延迟主导 CPU 速度。
- “快代码”如果数据放错位置,毫无意义。
Act 5: SIMD, AVX, OpenMP (False Hope Arc)
你全力以赴:
- SIMD、AVX2/AVX‑512
- OpenMP、BLAS
- 手工调优循环
现实再度敲门:
- 小批量 → OpenMP 开销大于收益。
- 线程争夺缓存;更多核心 ≠ 更快速度。
- 优化本身也需要再优化。
Act 6: Python Bindings (New Boss, Same Pain)
“好吧,我就把它用 Python 绑定暴露出来。”
欢迎来到 pybind11 + CMake 地狱:
- CMake 找不到 pybind,或拒绝它。
- 难懂的编译器信息。
额外的头疼:
- 同时管理 Python、C++ 与 NumPy 的内存。
- 召回率下降,速度说谎。
你意识到 NumPy 的数学 ≠ C++ 的速度,甚至一度想把 CPU 扔出窗外。
Act 7: Scalar C++ Reality Check
你尝试纯标量 C++。
惊讶:经过良好优化的 NumPy/Cython 可以击败天真的 C++。
你的自尊崩溃。
于是你:
- 学习数据对齐、缓存行、预取。
- 明白“只要 C++”根本不够。
Final Act: The Faiss Reality Check
经过所有调优——内存、缓存、布局、QPS、延迟——你与 Faiss 对标,却远远不及。
Faiss 不仅仅是算法;它是多年低层痛苦、调优与内存掌控的结晶。
Advice From Someone Who Survived (Barely)
步骤 1:先用 Python 起步。
- 先实现算法。
- 验证准确性。
- 如果已经足够好,就停在这里,开心就好。
步骤 2:只有在以下情况下才迁移到 C++:
- 碰到真实的内存瓶颈。
- 碰到真实的延迟上限。
- 你已经了解自己将要承担的代价。
步骤 3:优化地狱
- SIMD、AVX、OpenMP(慎用)
- 缓存感知设计
- 以内存为先的思考
如果你走到这一步… 恭喜。这正是正式开始恨自己人生的时刻。
- 编写 ANN 引擎很有趣。
- 编写一个快速的 ANN 引擎很痛苦。
- 编写一个能与 Faiss 竞争的?那就是老板战马拉松。
如果你还能坚持到这里——致敬。 🫡
如果你正打算开始——我已经警告过你了。
现在请原谅我,我要再去基准测试并为缓存未命中哭泣。
所以说… 已经走到一半了。还有未完成的事。ANN 正在酝酿。它会开源——不是“很快™”,不是“创业很快”。 但很快——那种代码已经写好、痛苦已经付出的“很快”。