Foundation Classes - Optimize PLib polynomial evaluation - #953
Conversation
Refactor PLib::EvalPolynomial and PLib::NoDerivativeEvalPolynomial for improved performance: - Replace memcpy/memset with direct initialization loops to enable compiler register allocation for small dimensions - Use local stack arrays instead of writing to output during computation, reducing memory round-trips (write only at the end) - Fuse derivative and value update loops to read intermediate values once per iteration instead of twice - Replace repetitive switch-case statements (~180 lines) with constexpr dispatch tables using std::array and std::integer_sequence - Add optimized runtime fallback functions for dimensions > 15 Performance improvements: - Dimensions 1-15: Compiler can allocate local arrays in CPU registers - All cases: Eliminated memcpy/memset function call overhead - Fused loops reduce memory reads by ~50%
There was a problem hiding this comment.
Pull request overview
This PR optimizes polynomial evaluation in the PLib package by refactoring the EvalPolynomial and NoDerivativeEvalPolynomial functions. The changes aim to improve performance through better memory access patterns and compiler optimization opportunities.
Key changes:
- Replaced
memcpy/memsetcalls with direct initialization loops to enable register allocation for small dimensions (1-15) - Introduced local stack arrays that reduce memory round-trips by writing to output only at the end of computation
- Fused derivative and value update loops to minimize redundant memory reads
| eval_step1<dim - 1>(poly, par, coef); | ||
| poly[dim] = poly[dim] * par + coef[dim]; | ||
| } | ||
| double aLocal[dim]; |
There was a problem hiding this comment.
Variable-length arrays (VLA) are not part of standard C++ (dim is a template parameter but the array declaration syntax resembles VLA). While this code is valid because dim is a compile-time constant template parameter, consider using std::array<double, dim> for clarity and to match the modern C++ style used elsewhere in the file (e.g., dispatch tables use std::array).
| double aLocal0[dim]; | ||
| double aLocal1[dim]; |
There was a problem hiding this comment.
Variable-length arrays (VLA) are not part of standard C++ (dim is a template parameter but the array declaration syntax resembles VLA). While this code is valid because dim is a compile-time constant template parameter, consider using std::array<double, dim> for clarity and consistency with modern C++ patterns used in the dispatch tables.
| double aLocal0[dim]; | ||
| double aLocal1[dim]; | ||
| double aLocal2[dim]; |
There was a problem hiding this comment.
Variable-length arrays (VLA) are not part of standard C++ (dim is a template parameter but the array declaration syntax resembles VLA). While this code is valid because dim is a compile-time constant template parameter, consider using std::array<double, dim> for clarity and to align with the modern C++ idioms used elsewhere in this file.
| static const std::array<EvalPoly0Func, THE_MAX_OPT_DIM> THE_EVAL_POLY0_TABLE = | ||
| makeDispatchTable<EvalPoly0Wrapper, EvalPoly0Func>( | ||
| std::make_integer_sequence<int, THE_MAX_OPT_DIM>{}); | ||
|
|
||
| static const std::array<EvalPoly1Func, THE_MAX_OPT_DIM> THE_EVAL_POLY1_TABLE = | ||
| makeDispatchTable<EvalPoly1Wrapper, EvalPoly1Func>( | ||
| std::make_integer_sequence<int, THE_MAX_OPT_DIM>{}); | ||
|
|
||
| static const std::array<EvalPoly2Func, THE_MAX_OPT_DIM> THE_EVAL_POLY2_TABLE = |
There was a problem hiding this comment.
These static const dispatch tables in an anonymous namespace may cause initialization order issues across translation units if accessed before static initialization completes. Consider using constexpr instead of static const, or implement as inline constexpr functions returning local constexpr arrays to guarantee initialization at compile time and avoid potential static initialization order fiasco.
| static const std::array<EvalPoly0Func, THE_MAX_OPT_DIM> THE_EVAL_POLY0_TABLE = | |
| makeDispatchTable<EvalPoly0Wrapper, EvalPoly0Func>( | |
| std::make_integer_sequence<int, THE_MAX_OPT_DIM>{}); | |
| static const std::array<EvalPoly1Func, THE_MAX_OPT_DIM> THE_EVAL_POLY1_TABLE = | |
| makeDispatchTable<EvalPoly1Wrapper, EvalPoly1Func>( | |
| std::make_integer_sequence<int, THE_MAX_OPT_DIM>{}); | |
| static const std::array<EvalPoly2Func, THE_MAX_OPT_DIM> THE_EVAL_POLY2_TABLE = | |
| inline constexpr std::array<EvalPoly0Func, THE_MAX_OPT_DIM> THE_EVAL_POLY0_TABLE = | |
| makeDispatchTable<EvalPoly0Wrapper, EvalPoly0Func>( | |
| std::make_integer_sequence<int, THE_MAX_OPT_DIM>{}); | |
| inline constexpr std::array<EvalPoly1Func, THE_MAX_OPT_DIM> THE_EVAL_POLY1_TABLE = | |
| makeDispatchTable<EvalPoly1Wrapper, EvalPoly1Func>( | |
| std::make_integer_sequence<int, THE_MAX_OPT_DIM>{}); | |
| inline constexpr std::array<EvalPoly2Func, THE_MAX_OPT_DIM> THE_EVAL_POLY2_TABLE = |
… for improved safety and performance
Refactor PLib::EvalPolynomial and PLib::NoDerivativeEvalPolynomial for improved performance:
Performance improvements: