Upgrade to Pro — share decks privately, control downloads, hide ads and more …

俺の kd-tree

俺の kd-tree

Rust で kd-tree を自作し、crates.io に公開しました。kd tree とは?パフォーマンスは? 設計は? といったあたりをまとめた発表資料です。

Terada Yuichiro

November 24, 2020
Tweet

More Decks by Terada Yuichiro

Other Decks in Programming

Transcript

  1. こんなことを話します • kd-tree を自作して crates.io に publish してみた • そもそも

    kd-tree とは? • なぜ自作したの? • どんな実装? • パフォーマンスは?
  2. kd-tree とは • k-dimensional tree • k 次元空間の点群を高速に探索可能にするデータ構造 • こんな探索が出来ます

    – 【最近傍探索】点群から、指定座標に最も近い点を探索 – 【 k 近傍探索】近いものから k 個探索( k が被ってる…) – 【領域探索】指定領域に含まれる点を全て探索
  3. kd-tree の構築( 2D の場合) • 2 分木のノードに点をひとつ格納 • X で分割

    → Y で分割 → X で分割 → Y で分割 → … https://www.datasciencecentral.com/profiles/blogs/implementing-kd-tree-for-fast-range-search-nearest-neighbor
  4. 使ってみた • `kdtree` – 遅い – 点の逐次追加でツリーを構築するスタイル – 任意次元の kd

    tree が作れる – 点座標の数値型は `num_traits::Float` 型(つまり `f64` と `f32`) – 型が `KdTree<f64, T, [f64; 3]>` みたいになる(`f64` を 2回書く) • `fux_kdtree` – 速い( `kdtree` の 2.5〜 3倍) – 点の配列から一気に構築するスタイル – 3次元までしか作れない – 点座標の数値型は `f64` のみ – `fux_kdtree::kdtree::KdtreePointTrait` を実装しないといけない
  5. ベンチマーク(構築処理) fux_kdtree kdtree 俺の (f64) 俺の (i32) [0, 1]^3 の空間に

    一様な乱数で 10^n 個の点を生成 10^2 点 10^3 点 10^4 点
  6. 機能はまだ足りない • 最近傍探索しかできない – ◦ 【最近傍探索】指定座標に最も近い点を探索 – ☓ 【 k

    近傍探索】近いものから k 個探索 – ☓ 【領域探索】指定領域に含まれる点を全て探索 • 今後、機能追加していきたい
  7. 基本のき use kd_tree::KdTree; let items: Vec<[i32; 3]> = vec![[1, 2,

    3], [3, 1, 2], [2, 3, 1]]; let kdtree: KdTree<[i32; 3]> = KdTree::build(items); assert_eq!(kdtree.nearest(&[3, 1, 2]).unwrap().item, &[3, 1, 2]); • KdTree<[i32; 3]> ← 型がシンプル! • 整数座標が使える!(※ unsigned はダメ)
  8. f64 の座標を使う • 浮動小数点座標の場合は build_by_ordered_float() を使う。 – f64 は Ord

    を実装していないので。 – ordered_float::OrderedFloat を利用 use kd_tree::KdTree; let kdtree: KdTree<[f64; 3]> = KdTree::build_by_ordered_float(vec![ [1.0, 2.0, 3.0], [3.0, 1.0, 2.0], [2.0, 3.0, 1.0] ]); assert_eq!( kdtree.nearest(&[3.1, 0.9, 2.1]).unwrap().item, &[3.0, 1.0, 2.0] );
  9. マップのようにも使えます • KdMap<P,T> … Map のように使える kd tree – P

    に点座標 – T には任意の型が入る use kd_tree::KdMap; let kdmap: KdMap<[isize; 3], &'static str> = KdMap::build(vec![ ([1, 2, 3], "foo"), ([2, 3, 1], "bar"), ([3, 1, 2], "buzz"), ]); assert_eq!(kdmap.nearest(&[3, 1, 2]).unwrap().item.1, "buzz");
  10. KdPoint カスタム実装 use kd_tree::{KdPoint, KdTree}; struct Item { point: [f64;

    2], id: usize } impl KdPoint for Item { type Scalar = f64; type Dim = typenum::U2; // 2 dimensional tree. fn at(&self, k: usize) -> f64 { self.point[k] } } let kdtree: KdTree<Item> = KdTree::build_by_ordered_float(vec![ Item { point: [1.0, 2.0], id: 111 }, Item { point: [2.0, 3.0], id: 222 }, Item { point: [3.0, 4.0], id: 333 }, ]); assert_eq!(kdtree.nearest(&[1.9, 3.1]).unwrap().item.id, 222);
  11. KdSlice • KdTree のスライス型( String に対する str ) – Sized

    ではない → 常に参照型で扱う – KdTree は Deref<Target=KdSlice<...>> を実装 • KdSlice::sort() は items を move しない(ソートするだ け) use kd_tree::KdSlice; let mut items: Vec<[i32; 3]> = vec![[1, 2, 3], [3, 1, 2], [2, 3, 1]]; let kdtree: &KdSlice<[i32; 3]> = KdSlice::sort(&mut items); assert_eq!(kdtree.nearest(&[3, 1, 2]).unwrap().item, &[3, 1, 2]);
  12. 構築フロー items: &mut [T] x 座標でソート ↑items.len()/2 y 座標でソート ↑items.len()/2

    x 座標でソート ↑items.len()/2 このまま実装すると ソートが多いので遅い
  13. 並び替えるだけで kd tree になる • binary-tree と似ている – [T] を

    sort() しておけば binary_search() できる – 同様に、 [T] を並び替えておけば最近傍探索できる • 構築に必要なのは次元と kd_compare だけ – (注:最近傍探索は kd_compare だけでは足りません。距離が測 れないといけないので。) • このシンプルさ&柔軟さを最大限に活かした設計にしたい。
  14. KdSliceN<T, N> pub struct KdSliceN<T, N: Unsigned>(PhantomData<N>, [T]); impl<T, N:

    Unsigned> KdSliceN<T, N> { pub fn sort_by<F>(items: &mut [T], compare: F) -> &Self where F: Fn(&T, &T, usize) -> Ordering + Copy, { kd_sort_by(items, N::to_usize(), compare); unsafe { &*(items as *const _ as *const Self) } } } 次元 unsized 参照を返す unsafe を使ってキャスト
  15. KdTreeN<T, N> pub struct KdTreeN<T, N: Unsigned>(PhantomData<N>, Vec<T>); impl<T, N:

    Unsigned> KdTreeN<T, N> { pub fn build_by<F>(mut items: Vec<T>, compare: F) -> Self where F: Fn(&T, &T, usize) -> Ordering + Copy, { kd_sort_by(&mut items, N::to_usize(), compare); Self(PhantomData, items) } }
  16. Type aliases pub type KdSlice<T> = KdSliceN<T, <T as KdPoint>::Dim>;

    pub type KdTree<T> = KdTreeN<T, <T as KdPoint>::Dim>; pub type KdMap<P, T> = KdTree<(P, T)>; pub type KdMapSlice<P, T> = KdSlice<(P, T)>;
  17. まとめ • 自作 kd-tree を crates.io に公開した • 性能 –

    `kdtree` より速い(構築も探索も) – 探索は `fux_kdtree` より速い • 機能 – 整数座標が扱える – 高次元が扱える ※ CADDi では Rust でアルゴリズムを書きたいエンジニアを募集しています