基于树索引的Annoy的搜索库其节点cpp解读
Annoy简介
Annoy是Erik Bernhardsson写的一个以树为数据结构的近似最近邻搜索库,并用在Spotify的推荐系统中。Annoy的核心是不断用选取的两个质心的法平面对空间进行分割,最终将每一个区分的子空间里面的样本数据限制在K以内。对于待插入的样本\(x_i\),从根节点依次使用法向量跟做内积运算,从而判断使用法平面的哪一边(左子树or右子树)。对于查询向量\(q_i\),采用同样的方式(在树结构上体现为从根节点向叶子节点递归遍历),即可定位到跟\(q_i\)在同一个子空间或者邻近的子空间的样本,这些样本即为\(q_i\)近邻。
struct ANNOY_NODE_ATTRIBUTE Node {
S n_descendants;
union {
S children[2]; // Will possibly store more than 2
T norm;
};
T dot_factor;
T v[1]; // We let this one overflow intentionally. Need to allocate at least 1 to make GCC happy
};
| 向量符号 | 含义说明 |
|---|---|
| S n_descendants; | 后代数量;这个节点下方的叶子节点(样本点)的总数量。 |
| union { ... }; | 一个内存优化技巧,union 内部的成员共享同一块内存空间。这意味着,在任何给定时间,你只能使用 children 或 norm 中的一个。 |
| S children[2]; | 内部节点(非叶子节点) 使用。存储指向左子节点和右子节点的索引(不是指针,Annoy 将所有节点存储在一个大数组中,通过索引访问)。 |
| T norm; | 叶子节点使用 |
| T dot_factor | 点积因子,用于Annoy的距离计算 |
| T v[1] | 划分向量(Splitting Vector)。这是一个灵活数组成员 (Flexible Array Member, FAM) 的技巧。 |
正如注释所说,v[1] 后面的内存被有意地溢出 (overflow intentionally),用来存储 D 维向量的其余 D−1 个分量。这使得 Annoy 可以高效地将节点的元数据和其划分向量(或叶子节点中的实际数据向量)存储在内存中连续的一块区域。
浙公网安备 33010602011771号