Skip to content

Commit e36d52a

Browse files
perf(math): build matrix results without zero-filling them first (#349)
* perf(math): build matrix results without zero-filling them first Every Matrix operator that returns a new matrix value-initialised its result (data = {}) and then overwrote every element. Without the O3 pragmas #348 removed, a consumer that builds at -O2 keeps those loops rolled, so the zeroing survives. For a 3x3 result it is a memset call, and the Arm GNU toolchain's newlib-nano memset stores one byte at a time. e-foc runs an RLS update in the control interrupt during mechanical identification. Its covariance update, (P - g g^T / d) / lambda, builds four 3x3 temporaries. In e-foc's SIL scenario that times the control interrupt through a full calibration, the slowest execution rose from 237 to 377 cycles of the emulated 25 MHz clock (arm-none-eabi-gcc 15.2.1). The four memset calls were 596 of its 1892 instructions. - Matrix gains a private constructor that leaves the elements uninitialised. operator+, operator-, the matrix and scalar products, Transpose, GetBlock and GetColumn build their results with it, since each writes every element. - The default and initializer-list constructors still zero-fill, so a default-constructed or partly listed matrix keeps its zeros. - A test evaluates those operators in constant expressions, so a compiler that checks (Clang does, GCC does not) rejects an operator that leaves an element unwritten. The RLS update keeps its matrix expression and now makes no memset calls. The same scenario peaks at 250 cycles. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKhRVwu8yvxdKJgNo17oNc * fix(math): build the matrix product's result in a member function The matrix product is a friend function of its left operand's Matrix specialisation. It built its result, a Matrix of another shape, with that shape's private constructor. GCC and Clang allowed it, but MSVC rejects it (C2248): friendship goes to the Matrix specialisations, not to their friend functions. The operator now calls a private member function, Multiply, which builds the result in place, as Transpose, GetBlock and GetColumn already do. Members of every Matrix specialisation are friends of the others, so every compiler accepts it. A static helper that returned the uninitialised result was not used: GCC folds such an argument-less constexpr call into a zero-filled constant, which brings back the memset calls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HKhRVwu8yvxdKJgNo17oNc --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 31c7ac9 commit e36d52a

2 files changed

Lines changed: 61 additions & 20 deletions

File tree

‎numerical/math/Matrix.hpp‎

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,15 @@ namespace math
6363

6464
[[nodiscard]] OPTIMIZE_FOR_SPEED friend constexpr Matrix operator+(const Matrix& lhs, const Matrix& rhs)
6565
{
66-
Matrix result;
66+
Matrix result{ Uninitialized{} };
6767
for (size_type i = 0; i < size; ++i)
6868
result.data[i] = lhs.data[i] + rhs.data[i];
6969
return result;
7070
}
7171

7272
[[nodiscard]] OPTIMIZE_FOR_SPEED friend constexpr Matrix operator-(const Matrix& lhs, const Matrix& rhs)
7373
{
74-
Matrix result;
74+
Matrix result{ Uninitialized{} };
7575
for (size_type i = 0; i < size; ++i)
7676
result.data[i] = lhs.data[i] - rhs.data[i];
7777
return result;
@@ -80,23 +80,12 @@ namespace math
8080
template<size_t RhsCols>
8181
[[nodiscard]] OPTIMIZE_FOR_SPEED friend constexpr Matrix<T, Rows, RhsCols> operator*(const Matrix& lhs, const Matrix<T, Cols, RhsCols>& rhs)
8282
{
83-
Matrix<T, Rows, RhsCols> result;
84-
for (size_type i = 0; i < Rows; ++i)
85-
{
86-
for (size_type j = 0; j < RhsCols; ++j)
87-
{
88-
T sum{};
89-
for (size_type k = 0; k < Cols; ++k)
90-
sum += lhs.at(i, k) * rhs.at(k, j);
91-
result.at(i, j) = sum;
92-
}
93-
}
94-
return result;
83+
return lhs.Multiply(rhs);
9584
}
9685

9786
[[nodiscard]] OPTIMIZE_FOR_SPEED friend constexpr Matrix operator*(const Matrix& lhs, const T& scalar)
9887
{
99-
Matrix result;
88+
Matrix result{ Uninitialized{} };
10089
for (size_type i = 0; i < size; ++i)
10190
result.data[i] = lhs.data[i] * scalar;
10291
return result;
@@ -121,7 +110,19 @@ namespace math
121110
[[nodiscard]] constexpr Matrix<T, Rows, 1> GetColumn(size_type col) const;
122111

123112
private:
124-
std::array<T, Rows * Cols> data = {};
113+
template<typename, size_t, size_t>
114+
friend class Matrix;
115+
116+
struct Uninitialized
117+
{};
118+
119+
constexpr explicit Matrix(Uninitialized) noexcept
120+
{}
121+
122+
template<size_t RhsCols>
123+
[[nodiscard]] constexpr Matrix<T, Rows, RhsCols> Multiply(const Matrix<T, Cols, RhsCols>& rhs) const;
124+
125+
std::array<T, Rows * Cols> data;
125126
};
126127

127128
template<typename T, typename... U>
@@ -141,10 +142,13 @@ namespace math
141142
}
142143

143144
template<typename T, size_t Rows, size_t Cols>
144-
constexpr Matrix<T, Rows, Cols>::Matrix() noexcept = default;
145+
constexpr Matrix<T, Rows, Cols>::Matrix() noexcept
146+
: data{}
147+
{}
145148

146149
template<typename T, size_t Rows, size_t Cols>
147150
OPTIMIZE_FOR_SPEED constexpr Matrix<T, Rows, Cols>::Matrix(std::initializer_list<std::initializer_list<T>> init)
151+
: data{}
148152
{
149153
size_t row = 0;
150154
for (const auto& row_list : init)
@@ -263,11 +267,30 @@ namespace math
263267
return *this;
264268
}
265269

270+
template<typename T, size_t Rows, size_t Cols>
271+
template<size_t RhsCols>
272+
OPTIMIZE_FOR_SPEED constexpr Matrix<T, Rows, RhsCols>
273+
Matrix<T, Rows, Cols>::Multiply(const Matrix<T, Cols, RhsCols>& rhs) const
274+
{
275+
Matrix<T, Rows, RhsCols> result{ typename Matrix<T, Rows, RhsCols>::Uninitialized{} };
276+
for (size_type i = 0; i < Rows; ++i)
277+
{
278+
for (size_type j = 0; j < RhsCols; ++j)
279+
{
280+
T sum{};
281+
for (size_type k = 0; k < Cols; ++k)
282+
sum += at(i, k) * rhs.at(k, j);
283+
result.at(i, j) = sum;
284+
}
285+
}
286+
return result;
287+
}
288+
266289
template<typename T, size_t Rows, size_t Cols>
267290
OPTIMIZE_FOR_SPEED constexpr Matrix<T, Cols, Rows>
268291
Matrix<T, Rows, Cols>::Transpose() const
269292
{
270-
Matrix<T, Cols, Rows> result;
293+
Matrix<T, Cols, Rows> result{ typename Matrix<T, Cols, Rows>::Uninitialized{} };
271294
for (size_type i = 0; i < Rows; ++i)
272295
for (size_type j = 0; j < Cols; ++j)
273296
result.at(j, i) = at(i, j);
@@ -328,7 +351,7 @@ namespace math
328351
{
329352
static_assert(BlockRows <= Rows && BlockCols <= Cols,
330353
"Requested block exceeds source matrix dimensions");
331-
Matrix<T, BlockRows, BlockCols> result;
354+
Matrix<T, BlockRows, BlockCols> result{ typename Matrix<T, BlockRows, BlockCols>::Uninitialized{} };
332355
for (size_type r = 0; r < BlockRows; ++r)
333356
for (size_type c = 0; c < BlockCols; ++c)
334357
result.at(r, c) = at(rowOffset + r, colOffset + c);
@@ -339,7 +362,7 @@ namespace math
339362
[[nodiscard]] OPTIMIZE_FOR_SPEED constexpr Matrix<T, Rows, 1>
340363
Matrix<T, Rows, Cols>::GetColumn(size_type col) const
341364
{
342-
Vector<T, Rows> result;
365+
Vector<T, Rows> result{ typename Vector<T, Rows>::Uninitialized{} };
343366
for (size_type r = 0; r < Rows; ++r)
344367
result.at(r, 0) = at(r, col);
345368
return result;

‎numerical/math/test/TestMatrix.cpp‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,24 @@ TEST_F(MatrixFloatShapeTest, three_by_three_compound_assignment_and_trace)
427427
EXPECT_NEAR(square3.Transpose().at(0, 2), 7.0f, math::Tolerance<float>());
428428
}
429429

430+
TEST_F(MatrixFloatShapeTest, operators_write_every_element_of_their_result)
431+
{
432+
constexpr math::Matrix<float, 3, 1> column{ { 1.0f }, { 2.0f }, { 3.0f } };
433+
constexpr auto outer = column * column.Transpose();
434+
constexpr auto sum = outer + outer;
435+
constexpr auto difference = sum - outer;
436+
constexpr auto scaled = difference * 0.5f;
437+
constexpr auto block = scaled.GetBlock<2, 2>(1, 1);
438+
constexpr auto lastColumn = scaled.GetColumn(2);
439+
440+
EXPECT_NEAR(outer.at(2, 1), 6.0f, math::Tolerance<float>());
441+
EXPECT_NEAR(sum.at(0, 2), 6.0f, math::Tolerance<float>());
442+
EXPECT_NEAR(difference.at(1, 1), 4.0f, math::Tolerance<float>());
443+
EXPECT_NEAR(scaled.at(2, 2), 4.5f, math::Tolerance<float>());
444+
EXPECT_NEAR(block.at(1, 0), 3.0f, math::Tolerance<float>());
445+
EXPECT_NEAR(lastColumn.at(0, 0), 1.5f, math::Tolerance<float>());
446+
}
447+
430448
TEST_F(MatrixFloatShapeTest, non_square_shapes_transpose_and_index)
431449
{
432450
math::Matrix<float, 1, 4> row{ { 1.0f, 2.0f, 3.0f, 4.0f } };

0 commit comments

Comments
 (0)