Add manifold and first-order diff benchmarks - Add internal/ceres/autodiff_manifold_benchmark.cc to benchmark AutoDiffManifold PlusJacobian and MinusJacobian on Quaternion and Pose3 manifolds. - Add internal/ceres/first_order_function_differentiation_benchmark.cc to benchmark AutoDiffFirstOrderFunction and static/dynamic NumericDiffFirstOrderFunction on Rosenbrock. Change-Id: Ie58ae9f67952961ebefc1957d7ad9c1b56a18203
diff --git a/BUILD b/BUILD index ac8ff91..e82cdfb 100644 --- a/BUILD +++ b/BUILD
@@ -199,9 +199,11 @@ copts = ["-mllvm -inlinehint-threshold=1000000"], deps = TEST_DEPS + ["@google_benchmark//:benchmark"], ) for benchmark_name in [ + "autodiff_manifold_benchmark", "block_jacobi_preconditioner_benchmark", "dense_linear_solver_benchmark", "evaluation_benchmark", + "first_order_function_differentiation_benchmark", "invert_psd_matrix_benchmark", "jet_operator_benchmark", "parallel_for_benchmark",
diff --git a/internal/ceres/CMakeLists.txt b/internal/ceres/CMakeLists.txt index f85d0d6..ff2b267 100644 --- a/internal/ceres/CMakeLists.txt +++ b/internal/ceres/CMakeLists.txt
@@ -561,7 +561,14 @@ block_jacobi_preconditioner_benchmark.cc) add_dependencies_to_benchmark(block_jacobi_preconditioner_benchmark) + add_executable(autodiff_manifold_benchmark autodiff_manifold_benchmark.cc) + add_dependencies_to_benchmark(autodiff_manifold_benchmark) + add_executable(cost_function_differentiation_benchmark cost_function_differentiation_benchmark.cc) add_dependencies_to_benchmark(cost_function_differentiation_benchmark) + + add_executable(first_order_function_differentiation_benchmark + first_order_function_differentiation_benchmark.cc) + add_dependencies_to_benchmark(first_order_function_differentiation_benchmark) endif (_Ceres_FEATURE_BENCHMARKS)
diff --git a/internal/ceres/autodiff_manifold_benchmark.cc b/internal/ceres/autodiff_manifold_benchmark.cc new file mode 100644 index 0000000..5013e86 --- /dev/null +++ b/internal/ceres/autodiff_manifold_benchmark.cc
@@ -0,0 +1,171 @@ +// Ceres Solver - A fast non-linear least squares minimizer +// Copyright 2026 Google Inc. All rights reserved. +// http://ceres-solver.org/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors may be +// used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Author: sameeragarwal@google.com (Sameer Agarwal) + +#include <array> +#include <cmath> +#include <memory> + +#include "absl/log/check.h" +#include "benchmark/benchmark.h" +#include "ceres/autodiff_manifold.h" +#include "ceres/manifold.h" +#include "ceres/rotation.h" + +namespace ceres::internal { +namespace { + +struct QuaternionFunctor { + static constexpr int kAmbientSize = 4; + static constexpr int kTangentSize = 3; + + static std::array<double, kAmbientSize> MakeParameter() { + return {0.5, 0.5, 0.5, 0.5}; + } + + template <typename T> + bool Plus(const T* x, const T* delta, T* x_plus_delta) const { + const T squared_norm_delta = + delta[0] * delta[0] + delta[1] * delta[1] + delta[2] * delta[2]; + + T q_delta[4]; + if (squared_norm_delta > T(0.0)) { + const T norm_delta = sqrt(squared_norm_delta); + const T sin_delta_by_delta = sin(norm_delta) / norm_delta; + q_delta[0] = cos(norm_delta); + q_delta[1] = sin_delta_by_delta * delta[0]; + q_delta[2] = sin_delta_by_delta * delta[1]; + q_delta[3] = sin_delta_by_delta * delta[2]; + } else { + q_delta[0] = T(1.0); + q_delta[1] = delta[0]; + q_delta[2] = delta[1]; + q_delta[3] = delta[2]; + } + + QuaternionProduct(q_delta, x, x_plus_delta); + return true; + } + + template <typename T> + bool Minus(const T* y, const T* x, T* y_minus_x) const { + const T minus_x[4] = {x[0], -x[1], -x[2], -x[3]}; + T ambient_y_minus_x[4]; + QuaternionProduct(y, minus_x, ambient_y_minus_x); + const T u_sq = ambient_y_minus_x[1] * ambient_y_minus_x[1] + + ambient_y_minus_x[2] * ambient_y_minus_x[2] + + ambient_y_minus_x[3] * ambient_y_minus_x[3]; + if (u_sq > T(0.0)) { + const T u_norm = sqrt(u_sq); + const T theta = atan2(u_norm, ambient_y_minus_x[0]); + y_minus_x[0] = theta * ambient_y_minus_x[1] / u_norm; + y_minus_x[1] = theta * ambient_y_minus_x[2] / u_norm; + y_minus_x[2] = theta * ambient_y_minus_x[3] / u_norm; + } else { + y_minus_x[0] = ambient_y_minus_x[1]; + y_minus_x[1] = ambient_y_minus_x[2]; + y_minus_x[2] = ambient_y_minus_x[3]; + } + return true; + } +}; + +struct Pose3Functor { + static constexpr int kAmbientSize = 7; + static constexpr int kTangentSize = 6; + + static std::array<double, kAmbientSize> MakeParameter() { + return {0.5, 0.5, 0.5, 0.5, 1.0, 2.0, 3.0}; + } + + template <typename T> + bool Plus(const T* x, const T* delta, T* x_plus_delta) const { + QuaternionFunctor q; + q.Plus(x, delta, x_plus_delta); + x_plus_delta[4] = x[4] + delta[3]; + x_plus_delta[5] = x[5] + delta[4]; + x_plus_delta[6] = x[6] + delta[5]; + return true; + } + + template <typename T> + bool Minus(const T* y, const T* x, T* y_minus_x) const { + QuaternionFunctor q; + q.Minus(y, x, y_minus_x); + y_minus_x[3] = y[4] - x[4]; + y_minus_x[4] = y[5] - x[5]; + y_minus_x[5] = y[6] - x[6]; + return true; + } +}; + +template <typename Functor> +void BM_AutoDiffManifoldPlusJacobian(benchmark::State& state) { + constexpr int kAmbientSize = Functor::kAmbientSize; + constexpr int kTangentSize = Functor::kTangentSize; + const std::unique_ptr<Manifold> manifold = + std::make_unique<AutoDiffManifold<Functor, kAmbientSize, kTangentSize>>(); + const auto x = Functor::MakeParameter(); + std::array<double, kAmbientSize * kTangentSize> jacobian{}; + + CHECK(manifold->PlusJacobian(x.data(), jacobian.data())); + + for (auto _ : state) { + benchmark::DoNotOptimize(manifold->PlusJacobian(x.data(), jacobian.data())); + benchmark::DoNotOptimize(jacobian); + } +} + +template <typename Functor> +void BM_AutoDiffManifoldMinusJacobian(benchmark::State& state) { + constexpr int kAmbientSize = Functor::kAmbientSize; + constexpr int kTangentSize = Functor::kTangentSize; + const std::unique_ptr<Manifold> manifold = + std::make_unique<AutoDiffManifold<Functor, kAmbientSize, kTangentSize>>(); + const auto x = Functor::MakeParameter(); + std::array<double, kTangentSize * kAmbientSize> jacobian{}; + + CHECK(manifold->MinusJacobian(x.data(), jacobian.data())); + + for (auto _ : state) { + benchmark::DoNotOptimize( + manifold->MinusJacobian(x.data(), jacobian.data())); + benchmark::DoNotOptimize(jacobian); + } +} + +BENCHMARK_TEMPLATE(BM_AutoDiffManifoldPlusJacobian, QuaternionFunctor); +BENCHMARK_TEMPLATE(BM_AutoDiffManifoldMinusJacobian, QuaternionFunctor); +BENCHMARK_TEMPLATE(BM_AutoDiffManifoldPlusJacobian, Pose3Functor); +BENCHMARK_TEMPLATE(BM_AutoDiffManifoldMinusJacobian, Pose3Functor); + +} // namespace +} // namespace ceres::internal + +BENCHMARK_MAIN();
diff --git a/internal/ceres/first_order_function_differentiation_benchmark.cc b/internal/ceres/first_order_function_differentiation_benchmark.cc new file mode 100644 index 0000000..720e85e --- /dev/null +++ b/internal/ceres/first_order_function_differentiation_benchmark.cc
@@ -0,0 +1,176 @@ +// Ceres Solver - A fast non-linear least squares minimizer +// Copyright 2026 Google Inc. All rights reserved. +// http://ceres-solver.org/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors may be +// used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Author: sameeragarwal@google.com (Sameer Agarwal) + +#include <array> +#include <memory> + +#include "absl/log/check.h" +#include "benchmark/benchmark.h" +#include "ceres/autodiff_first_order_function.h" +#include "ceres/first_order_function.h" +#include "ceres/numeric_diff_first_order_function.h" + +namespace ceres::internal { +namespace { + +enum DiffType { + kAutoDiff, + kNumericForward, + kDynamicNumericForward, + kNumericCentral, + kDynamicNumericCentral, + kNumericRidders, + kDynamicNumericRidders, +}; + +// Controls whether FirstOrderFunction::Evaluate is called to compute only the +// objective value (gradient = nullptr) or both the objective value and the +// gradient vector (gradient != nullptr). +enum EvaluationType { + kCostOnly, + kCostAndGradient, +}; + +template <DiffType kDiffType> +constexpr NumericDiffMethodType NumericDiffMethod() { + if constexpr (kDiffType == kNumericForward || + kDiffType == kDynamicNumericForward) { + return FORWARD; + } else if constexpr (kDiffType == kNumericCentral || + kDiffType == kDynamicNumericCentral) { + return CENTRAL; + } else if constexpr (kDiffType == kNumericRidders || + kDiffType == kDynamicNumericRidders) { + return RIDDERS; + } +} + +template <DiffType kDiffType> +constexpr bool IsDynamicNumericDiff() { + return kDiffType == kDynamicNumericForward || + kDiffType == kDynamicNumericCentral || + kDiffType == kDynamicNumericRidders; +} + +template <int kParameters> +struct RosenbrockFirstOrderFunctor { + static constexpr int kNumParameters = kParameters; + + static std::array<double, kNumParameters> MakeParameters() { + std::array<double, kNumParameters> x{}; + for (int i = 0; i < kNumParameters; ++i) { + x[i] = 0.5 + 0.01 * i; + } + return x; + } + + template <typename T> + bool operator()(const T* const x, T* cost) const { + T sum = T(0.0); + for (int i = 0; i < kNumParameters - 1; ++i) { + const T t1 = T(1.0) - x[i]; + const T t2 = x[i + 1] - x[i] * x[i]; + sum += t1 * t1 + T(100.0) * t2 * t2; + } + *cost = sum; + return true; + } +}; + +template <DiffType kDiffType, typename Functor> +std::unique_ptr<FirstOrderFunction> CreateFirstOrderFunction() { + constexpr int kNumParameters = Functor::kNumParameters; + if constexpr (kDiffType == kAutoDiff) { + return std::make_unique<AutoDiffFirstOrderFunction<Functor, kNumParameters>>( + std::make_unique<Functor>()); + } else if constexpr (IsDynamicNumericDiff<kDiffType>()) { + constexpr NumericDiffMethodType kMethod = NumericDiffMethod<kDiffType>(); + return std::make_unique< + NumericDiffFirstOrderFunction<Functor, kMethod, DYNAMIC>>( + std::make_unique<Functor>(), kNumParameters); + } else { + constexpr NumericDiffMethodType kMethod = NumericDiffMethod<kDiffType>(); + return std::make_unique< + NumericDiffFirstOrderFunction<Functor, kMethod, kNumParameters>>( + std::make_unique<Functor>()); + } +} + +template <typename Functor, DiffType kDiffType, EvaluationType kEvalType> +void BM_FirstOrderFunction(benchmark::State& state) { + const std::unique_ptr<FirstOrderFunction> function = + CreateFirstOrderFunction<kDiffType, Functor>(); + const auto x = Functor::MakeParameters(); + + double cost = 0.0; + std::array<double, Functor::kNumParameters> gradient{}; + double* gradient_ptr = + (kEvalType == kCostAndGradient) ? gradient.data() : nullptr; + + CHECK(function->Evaluate(x.data(), &cost, gradient_ptr)); + + for (auto _ : state) { + benchmark::DoNotOptimize(function->Evaluate(x.data(), &cost, gradient_ptr)); + benchmark::DoNotOptimize(cost); + benchmark::DoNotOptimize(gradient); + } +} + +#define REGISTER_ROSENBROCK_FOR_DIFF_TYPE(Diff, N) \ + BENCHMARK_TEMPLATE(BM_FirstOrderFunction, \ + RosenbrockFirstOrderFunctor<N>, \ + Diff, \ + kCostOnly); \ + BENCHMARK_TEMPLATE(BM_FirstOrderFunction, \ + RosenbrockFirstOrderFunctor<N>, \ + Diff, \ + kCostAndGradient) + +#define REGISTER_ROSENBROCK_BENCHMARKS(N) \ + REGISTER_ROSENBROCK_FOR_DIFF_TYPE(kAutoDiff, N); \ + REGISTER_ROSENBROCK_FOR_DIFF_TYPE(kNumericForward, N); \ + REGISTER_ROSENBROCK_FOR_DIFF_TYPE(kDynamicNumericForward, N); \ + REGISTER_ROSENBROCK_FOR_DIFF_TYPE(kNumericCentral, N); \ + REGISTER_ROSENBROCK_FOR_DIFF_TYPE(kDynamicNumericCentral, N); \ + REGISTER_ROSENBROCK_FOR_DIFF_TYPE(kNumericRidders, N); \ + REGISTER_ROSENBROCK_FOR_DIFF_TYPE(kDynamicNumericRidders, N) + +REGISTER_ROSENBROCK_BENCHMARKS(4); +REGISTER_ROSENBROCK_BENCHMARKS(10); +REGISTER_ROSENBROCK_BENCHMARKS(16); +REGISTER_ROSENBROCK_BENCHMARKS(24); + +#undef REGISTER_ROSENBROCK_BENCHMARKS +#undef REGISTER_ROSENBROCK_FOR_DIFF_TYPE + +} // namespace +} // namespace ceres::internal + +BENCHMARK_MAIN();