From d10dee7046891b0a54667de63c6b16298b724d5b Mon Sep 17 00:00:00 2001 From: "k.koide" Date: Tue, 2 Apr 2024 13:05:03 +0900 Subject: [PATCH] odometry benchmark (native) --- BENCHMARK.md | 104 +++++++++++++++++++++++ README.md | 2 +- docs/assets/odometry_native.png | Bin 0 -> 31849 bytes scripts/plot_kdtree.py | 5 +- scripts/plot_odometry_native.py | 66 ++++++++++++++ scripts/run_odometry_benchmark_native.sh | 20 +++++ src/odometry_benchmark.cpp | 1 + 7 files changed, 195 insertions(+), 3 deletions(-) create mode 100644 BENCHMARK.md create mode 100644 docs/assets/odometry_native.png create mode 100644 scripts/plot_odometry_native.py create mode 100755 scripts/run_odometry_benchmark_native.sh diff --git a/BENCHMARK.md b/BENCHMARK.md new file mode 100644 index 0000000..9cd667f --- /dev/null +++ b/BENCHMARK.md @@ -0,0 +1,104 @@ +# Benchmark + +## Build + +```bash +cd small_gicp +mkdir build && cd build + +cmake .. -DBUILD_WITH_TBB=ON -DBUILD_WITH_PCL=ON -DBUILD_BENCHMARKS=ON + +# [optional] Build with Iridescence (visualization) +git clone https://github.com/koide3/iridescence --recursive +mkdir iridescence/build && cd iridescence/build +cmake .. && make -j +sudo make install + +cmake .. -DBUILD_WITH_IRIDESCENCE=ON + +# [optional] Build with fast_gicp +export FAST_GICP_INCLUDE_DIR=/your/fast_gicp/include +cmake .. -DBUILD_WITH_FAST_GICP=ON + +# Build +make -j + +# Test +# Show options +./odometry_benchmark + +# USAGE: odometry_benchmark [options] +# OPTIONS: +# --visualize +# --num_threads (default: 4) +# --num_neighbors (default: 20) +# --downsampling_resolution (default: 0.25) +# --voxel_resolution (default: 2.0) +# --engine (default: small_gicp) + +# Run odometry benchmark +./odometry_benchmark /your/kitti/dataset/velodyne /tmp/traj_lidar.txt --visualize --num_threads 16 --engine small_gicp_tbb +``` + +## Results + +All benchmarks were conducted on the KITTI 00 sequence. + +### Downsampling + +```bash +cd small_gicp/scripts +./run_downsampling_benchmark.sh +python3 plot_downsampling.py +``` + +- Single-threaded `small_gicp::voxelgrid_sampling` is about **1.3x faster** than `pcl::VoxelGrid`. +- Multi-threaded `small_gicp::voxelgrid_sampling_tbb` (6 threads) is about **3.2x faster** than `pcl::VoxelGrid`. +- `small_gicp::voxelgrid_sampling` gives accurate downsampling results (almost identical to those of `pcl::VoxelGrid`) while `pcl::ApproximateVoxelGrid` yields spurious points (up to 2x points). +- `small_gicp::voxelgrid_sampling` can process a larger point cloud with a fine voxel resolution compared to `pcl::VoxelGrid` (for a point cloud of 1000m width, minimum voxel resolution can be 0.5 mm). + +![downsampling_comp](docs/assets/downsampling_comp.png) + +- While TBB shows slightly better scalability, both the parallelism backends do not obtain a speed gain for the cases with threads more than 16. + +![downsampling_threads](docs/assets/downsampling_threads.png) + +### KdTree construction + +```bash +cd small_gicp/scripts +./run_kdtree_benchmark.sh +python3 plot_kdtree.py +``` + +- Multi-threaded implementation (TBB and OMP) can be up to **4x faster** than the single-threaded one (All the implementations are based on nanoflann). +- Basically the processing speed get faster as the number of threads increases, but the speed gain is not monotonic sometimes (because of the scheduling algorithm or some CPU(AMD 5995WX)-specific issues?). +- This benchmark only compares the construction time (query time is not included). + +![kdtree_time](docs/assets/kdtree_time.png) + +### Odometry estimation + +```bash +cd small_gicp/scripts +./run_odometry_benchmark.sh +python3 plot_odometry.py +``` + +- Single-thread `small_gicp::GICP` is about **2.4x and 1.9x faster** than `pcl::GICP` and `fast_gicp::GICP`, respectively. +- `small_gicp::(GICP|VGICP)` shows a better multi-thread scalability compared to `fast_gicp::(GICP|VGICP)`. +- `small_gicp::GICP` parallelized with [TBB flow graph](src/odometry_benchmark_small_gicp_tbb_flow.cpp) shows an excellent scalablity to many-threads situations (**~128 threads**) but with latency degradation. + +![odometry_time](docs/assets/odometry_time.png) + +**SIMD intrinsics (-march=native)** (We recommend keeping this feature disabled unless you are 100% sure what it is) + +- `BUILD_WITH_MARCH_NATIVE=ON` enables platform-specific intrinsics and squeezing the performance (**1.1x speedup for free**). +- However, you must ensure that all involved libraries are built with `-march=native`, otherwise the program will crash. +- Generally, it is difficult to properly set `-march=native` for all libraries, and we recommend keeping `BUILD_WITH_MARCH_NATIVE=OFF`. + +Results: +- `BUILD_WITH_MARCH_NATIVE=OFF` : `Eigen::SimdInstructionSetsInUse()=SSE, SSE2` +- `BUILD_WITH_MARCH_NATIVE=ON` : `Eigen::SimdInstructionSetsInUse()=AVX, SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2` + +![odometry_native](docs/assets/odometry_native.png) \ No newline at end of file diff --git a/README.md b/README.md index b357563..984c792 100644 --- a/README.md +++ b/README.md @@ -335,7 +335,7 @@ open3d.visualization.draw_geometries([target_o3d, source_o3d]) -## Benchmark +## [BENCHMARK](Benchmark.md) ### Downsampling diff --git a/docs/assets/odometry_native.png b/docs/assets/odometry_native.png new file mode 100644 index 0000000000000000000000000000000000000000..4868ede8739635dfdc86dc0213c669c80ef6050a GIT binary patch literal 31849 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 literal 0 HcmV?d00001 diff --git a/scripts/plot_kdtree.py b/scripts/plot_kdtree.py index 4022869..a30d9c1 100644 --- a/scripts/plot_kdtree.py +++ b/scripts/plot_kdtree.py @@ -21,16 +21,17 @@ def parse_result(filename): num_points.append(int(matched.group(1))) continue - matched = re.match(r'(\S+)_times=(\S+) \+\- (\S+)', line) + matched = re.match(r'(\S+)_times=(\S+) \+\- (\S+) \(median=(\S+)\)', line) if matched: method = matched.group(1) mean = float(matched.group(2)) std = float(matched.group(3)) + med = float(matched.group(4)) if method not in results: results[method] = [] - results[method].append(mean) + results[method].append(med) continue return (num_threads, num_points, results) diff --git a/scripts/plot_odometry_native.py b/scripts/plot_odometry_native.py new file mode 100644 index 0000000..5c44602 --- /dev/null +++ b/scripts/plot_odometry_native.py @@ -0,0 +1,66 @@ +#!/usr/bin/python3 +import os +import re +import numpy +from collections import namedtuple +from matplotlib import pyplot + +Result = namedtuple('Result', ['reg_mean', 'reg_std', 'tp_mean', 'tp_std']) + +def parse_result(filename): + reg_mean = None + reg_std = None + throughput_mean = None + throughput_std = None + with open(filename, 'r') as f: + for line in f.readlines(): + found = re.findall(r'([^=]+)\s*\+\-\s*(\S+)', line) + if not found or len(found) != 2: + found = re.findall(r'total_throughput=(\S+)', line) + if found: + throughput_mean = float(found[0]) + continue + + reg_mean = float(found[0][0].strip()) + reg_std = float(found[0][1].strip()) + throughput_mean = float(found[1][0].strip()) + throughput_std = float(found[1][1].strip()) + + return Result(reg_mean, reg_std, throughput_mean, throughput_std) + +def main(): + results_path = os.path.dirname(__file__) + '/results' + + results = {} + for filename in os.listdir(results_path): + found = re.findall(r'odometry_benchmark_(\S+)_(native|nonnative)_(\d+).txt', filename) + if not found: + continue + + rets = parse_result(results_path + '/' + filename) + results['{}_{}_{}'.format(found[0][0], found[0][1], found[0][2])] = rets + + fig, axes = pyplot.subplots(1, 1, figsize=(12, 2)) + axes = [axes] + + num_threads = [1, 2, 4, 8, 16, 32, 64, 128] + + print(results['small_gicp_native_1'], results['small_gicp_tbb_native_1']) + print(results['small_gicp_nonnative_1'], results['small_gicp_tbb_nonnative_1']) + + native = [results['small_gicp_tbb_native_{}'.format(N)].reg_mean for N in num_threads] + nonnative = [results['small_gicp_tbb_nonnative_{}'.format(N)].reg_mean for N in num_threads] + + axes[0].plot(num_threads, native, label='small_gicp_tbb (-march=native)', marker='o') + axes[0].plot(num_threads, nonnative, label='small_gicp_tbb (nonnative)', marker='o') + axes[0].set_xlabel('Number of threads [1, 2, ..., 128]') + axes[0].set_ylabel('Time [msec/scan]') + axes[0].set_xscale('log') + axes[0].grid() + axes[0].legend() + + pyplot.show() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/run_odometry_benchmark_native.sh b/scripts/run_odometry_benchmark_native.sh new file mode 100755 index 0000000..b9b2242 --- /dev/null +++ b/scripts/run_odometry_benchmark_native.sh @@ -0,0 +1,20 @@ +#!/bin/bash +dataset_path=$1 +exe_path=../build/odometry_benchmark + +mkdir results + +engines=(small_gicp small_gicp_tbb) +for engine in ${engines[@]}; do + N=1 + $exe_path $dataset_path $(printf "results/traj_lidar_%s_nonnative_%d.txt" $engine $N) --num_threads $N --engine $engine | tee $(printf "results/odometry_benchmark_%s_nonnative_%d.txt" $engine $N) +done + +engines=(small_gicp_tbb) +num_threads=(128 96 64 32 16 8 4 2) + +for N in ${num_threads[@]}; do + for engine in ${engines[@]}; do + $exe_path $dataset_path $(printf "results/traj_lidar_%s_nonnative_%d.txt" $engine $N) --num_threads $N --engine $engine | tee $(printf "results/odometry_benchmark_%s_nonnative_%d.txt" $engine $N) + done +done diff --git a/src/odometry_benchmark.cpp b/src/odometry_benchmark.cpp index c9b2799..088f2d2 100644 --- a/src/odometry_benchmark.cpp +++ b/src/odometry_benchmark.cpp @@ -54,6 +54,7 @@ int main(int argc, char** argv) { } } + std::cout << "SIMD in use=" << Eigen::SimdInstructionSetsInUse() << std::endl; std::cout << "dataset_path=" << dataset_path << std::endl; std::cout << "output_path=" << output_path << std::endl; std::cout << "registration_engine=" << engine << std::endl;