diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1f53fce2..369ef896 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -70,6 +70,13 @@ elseif (IMAGE_LIBRARY STREQUAL STB AND NOT STB_FOUND)
endif ()
message(STATUS "IMAGE_LIBRARY set to ${IMAGE_LIBRARY}")
+# Benchmark
+option(BUILD_BENCHMARKS "Build benchmarks" OFF)
+if (BUILD_BENCHMARKS)
+ find_package(benchmark REQUIRED)
+ message(STATUS "Building benchmarks")
+endif()
+
add_subdirectory(src)
install(DIRECTORY approot DESTINATION share/lms)
diff --git a/Dockerfile-build-alpine b/Dockerfile-build-alpine
index aef34b78..02c18010 100644
--- a/Dockerfile-build-alpine
+++ b/Dockerfile-build-alpine
@@ -15,6 +15,7 @@ ARG LMS_BUILD_PACKAGES=" \
gcc \
g++ \
musl-dev \
+ benchmark-dev \
boost-dev \
ffmpeg-dev \
libarchive-dev \
@@ -35,7 +36,7 @@ ARG LMS_BUILD_TYPE="Release"
RUN \
DIR=/tmp/lms/build && mkdir -p ${DIR} && cd ${DIR} && \
xx-info is-cross && export BUILD_TESTS=OFF || export BUILD_TESTS=ON && \
- PKG_CONFIG_PATH=/$(xx-info)/usr/lib/pkgconfig cmake /tmp/lms/ -DCMAKE_INCLUDE_PATH=${PREFIX}/include -DCMAKE_BUILD_TYPE=${LMS_BUILD_TYPE} $(xx-clang --print-cmake-defines) -DCMAKE_PREFIX_PATH=/$(xx-info)/usr/lib/cmake -DBUILD_TESTING=${BUILD_TESTS} && \
+ PKG_CONFIG_PATH=/$(xx-info)/usr/lib/pkgconfig cmake /tmp/lms/ -DCMAKE_INCLUDE_PATH=${PREFIX}/include -DCMAKE_BUILD_TYPE=${LMS_BUILD_TYPE} $(xx-clang --print-cmake-defines) -DCMAKE_PREFIX_PATH=/$(xx-info)/usr/lib/cmake -DBUILD_TESTING=${BUILD_TESTS} -DBUILD_BENCHMARKS=ON && \
VERBOSE=1 make -j$(nproc) && \
xx-verify src/lms/lms && \
(xx-info is-cross || make test)
diff --git a/Dockerfile-build-arch b/Dockerfile-build-arch
index 14eeac08..82cc3ee0 100644
--- a/Dockerfile-build-arch
+++ b/Dockerfile-build-arch
@@ -1,6 +1,7 @@
FROM archlinux:latest
ARG BUILD_PACKAGES="\
+ benchmark \
clang \
cmake \
boost \
@@ -22,6 +23,6 @@ COPY . /tmp/lms/
ARG LMS_BUILD_TYPE="Release"
RUN \
DIR=/tmp/lms/build && mkdir -p ${DIR} && cd ${DIR} && \
- cmake /tmp/lms/ -DCMAKE_BUILD_TYPE=${LMS_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX=/usr && \
+ cmake /tmp/lms/ -DCMAKE_BUILD_TYPE=${LMS_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_BENCHMARKS=ON && \
VERBOSE=1 make -j$(nproc) && \
make test
diff --git a/src/libs/som/CMakeLists.txt b/src/libs/som/CMakeLists.txt
index 938e835a..87a2d53f 100644
--- a/src/libs/som/CMakeLists.txt
+++ b/src/libs/som/CMakeLists.txt
@@ -22,3 +22,7 @@ install(TARGETS lmssom DESTINATION lib)
if(BUILD_TESTING)
add_subdirectory(test)
endif()
+
+if (BUILD_BENCHMARKS)
+ add_subdirectory(bench)
+endif()
diff --git a/src/libs/som/bench/CMakeLists.txt b/src/libs/som/bench/CMakeLists.txt
new file mode 100644
index 00000000..ccbb5bf8
--- /dev/null
+++ b/src/libs/som/bench/CMakeLists.txt
@@ -0,0 +1,9 @@
+
+add_executable(bench-som
+ SomBench.cpp
+ )
+
+target_link_libraries(bench-som PRIVATE
+ lmssom
+ benchmark
+ )
diff --git a/src/libs/som/bench/SomBench.cpp b/src/libs/som/bench/SomBench.cpp
new file mode 100644
index 00000000..6e1829bb
--- /dev/null
+++ b/src/libs/som/bench/SomBench.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 Emeric Poupon
+ *
+ * This file is part of LMS.
+ *
+ * LMS is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LMS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with LMS. If not, see .
+ */
+
+#include
+#include
+
+#include "som/Network.hpp"
+
+using namespace SOM;
+
+// Benchmark function
+static void BM_Matrix(benchmark::State& state)
+{
+ std::minstd_rand randomEngine{ 42 };
+ std::uniform_int_distribution distrib{ 0, 1000 };
+
+ Matrix matrix{ static_cast(state.range(0)), static_cast(state.range(0)) };
+
+ for (Coordinate x {}; x < matrix.getWidth(); ++x )
+ {
+ for (Coordinate y {}; y < matrix.getHeight(); ++y )
+ matrix.get({ x, y }) = distrib(randomEngine);
+ }
+
+ for (auto _ : state)
+ {
+ // Code inside this loop is measured repeatedly
+ const Position pos{ matrix.getPositionMinElement([](int a, int b) { return a < b; }) };
+ benchmark::DoNotOptimize(pos);
+ }
+
+ // Perform cleanup here if needed
+}
+
+// Register the benchmark with custom range
+BENCHMARK(BM_Matrix)->Arg(3)->Arg(6)->Arg(12)->Arg(24);
+
+BENCHMARK_MAIN();
\ No newline at end of file
diff --git a/src/libs/som/include/som/Matrix.hpp b/src/libs/som/include/som/Matrix.hpp
index 4a855a72..ceb0ed70 100644
--- a/src/libs/som/include/som/Matrix.hpp
+++ b/src/libs/som/include/som/Matrix.hpp
@@ -26,108 +26,104 @@
namespace SOM
{
+ using Coordinate = unsigned;
-using Coordinate = unsigned;
-using Norm = InputVector::value_type;
+ struct Position
+ {
+ Coordinate x;
+ Coordinate y;
-struct Position
-{
- Coordinate x;
- Coordinate y;
+ bool operator<(const Position& other) const
+ {
+ if (x == other.x)
+ return y < other.y;
+ else
+ return x < other.x;
+ }
- bool operator<(const Position& other) const
- {
- if (x == other.x)
- return y < other.y;
- else
- return x < other.x;
- }
+ bool operator==(const Position& other) const
+ {
+ return x == other.x && y == other.y;
+ }
+ };
- bool operator==(const Position& other) const
- {
- return x == other.x && y == other.y;
- }
-};
+ template
+ class Matrix
+ {
+ public:
+ Matrix() = default;
-template
-class Matrix
-{
- public:
- Matrix() = default;
+ Matrix(Coordinate width, Coordinate height)
+ : _width{ width }
+ , _height{ height }
+ {
+ _values.resize(static_cast(_width) * static_cast(_height));
+ }
- Matrix(Coordinate width, Coordinate height)
- : _width {width}
- , _height {height}
- {
- _values.resize(static_cast(_width) * static_cast(_height));
- }
+ template
+ Matrix(Coordinate width, Coordinate height, CtrArgs&& ... args)
+ : _width{ width }
+ , _height{ height }
+ {
+ _values.resize(static_cast(_width) * static_cast(_height), T{ std::forward(args)... });
+ }
- template
- Matrix(Coordinate width, Coordinate height, CtArgs... args)
- : _width {width}
- , _height {height}
- {
- _values.resize(static_cast(_width) * static_cast(_height), T{args...});
- }
+ void clear()
+ {
+ _values.clear();
+ }
- void clear()
- {
- std::vector values(static_cast(_width) * static_cast(_height));
- _values.swap(values);
- }
+ Coordinate getHeight() const { return _height; }
+ Coordinate getWidth() const { return _width; }
- Coordinate getHeight() const { return _height; }
- Coordinate getWidth() const { return _width; }
+ T& get(const Position& position)
+ {
+ assert(position.x < _width);
+ assert(position.y < _height);
+ return _values[position.x + _width * position.y];
+ }
- T& get(const Position& position)
- {
- assert(position.x < _width);
- assert(position.y < _height);
- return _values[position.x + _width*position.y];
- }
+ const T& get(const Position& position) const
+ {
+ assert(position.x < _width);
+ assert(position.y < _height);
+ return _values[position.x + _width * position.y];
+ }
- const T& get(const Position& position) const
- {
- assert(position.x < _width);
- assert(position.y < _height);
- return _values[position.x + _width*position.y];
- }
+ T& operator[](const Position& position) { return get(position); }
+ const T& operator[](const Position& position) const { return get(position); }
- T& operator[](const Position& position) { return get(position); }
- const T& operator[](const Position& position) const { return get(position); }
+ template
+ Position getPositionMinElement(Func func) const
+ {
+ assert(!_values.empty());
- template
- Position getPositionMinElement(Func func) const
- {
- assert(!_values.empty());
+ const auto it{ std::min_element(_values.begin(), _values.end(), std::move(func)) };
+ const auto index{ static_cast(std::distance(_values.begin(), it)) };
- auto it {std::min_element(_values.begin(), _values.end(), std::move(func))};
- auto index {static_cast(std::distance(_values.begin(), it))};
+ return Position{ index % _height, index / _height };
+ }
- return {index % _height, index / _height};
- }
-
- private:
- Coordinate _width {};
- Coordinate _height {};
- std::vector _values;
-};
+ private:
+ Coordinate _width{};
+ Coordinate _height{};
+ std::vector _values;
+ };
} // ns SOM
-namespace std {
-
-template<>
-class hash
+namespace std
{
- public:
- size_t operator()(const SOM::Position& s) const
- {
- size_t h1 = std::hash()(s.x);
- size_t h2 = std::hash()(s.y);
- return h1 ^ (h2 << 1);
- }
-};
-
+ template<>
+ class hash
+ {
+ public:
+ size_t operator()(const SOM::Position& s) const
+ {
+ size_t h1 = std::hash()(s.x);
+ size_t h2 = std::hash()(s.y);
+ return h1 ^ (h2 << 1);
+ }
+ };
} // ns std
diff --git a/src/libs/som/include/som/Network.hpp b/src/libs/som/include/som/Network.hpp
index b46abb09..bd0cc032 100644
--- a/src/libs/som/include/som/Network.hpp
+++ b/src/libs/som/include/som/Network.hpp
@@ -30,78 +30,78 @@
namespace SOM
{
-using LearningFactor = InputVector::value_type;
+ using LearningFactor = InputVector::value_type;
+ using Norm = InputVector::value_type;
-void checkSameDimensions(const InputVector& a, const InputVector& b);
-void checkSameDimensions(const InputVector& a, std::size_t inputDimCount);
-std::ostream& operator<<(std::ostream& os, const InputVector& a);
+ void checkSameDimensions(const InputVector& a, const InputVector& b);
+ void checkSameDimensions(const InputVector& a, std::size_t inputDimCount);
+ std::ostream& operator<<(std::ostream& os, const InputVector& a);
+ class Network
+ {
+ public:
+ // Init a network with random values
+ Network(Coordinate width, Coordinate height, std::size_t inputDimCount);
-class Network
-{
- public:
- // Init a network with random values
- Network(Coordinate width, Coordinate height, std::size_t inputDimCount);
+ Coordinate getWidth() const { return _refVectors.getWidth(); }
+ Coordinate getHeight() const { return _refVectors.getHeight(); }
+ std::size_t getInputDimCount() const { return _inputDimCount; }
+ const InputVector& getDataWeights() const { return _weights; }
- Coordinate getWidth() const { return _refVectors.getWidth(); }
- Coordinate getHeight() const { return _refVectors.getHeight(); }
- std::size_t getInputDimCount() const { return _inputDimCount; }
- const InputVector& getDataWeights() const { return _weights; }
+ // Set weight for each dimension (default is 1 for each weight)
+ void setDataWeights(const InputVector& weights);
- // Set weight for each dimension (default is 1 for each weight)
- void setDataWeights(const InputVector& weights);
+ // use this to manually construct a network without training
+ void setRefVector(const Position& position, const InputVector& data);
- // use this to manually construct a network without training
- void setRefVector(const Position& position, const InputVector& data);
+ // data must be normalized
+ struct CurrentIteration
+ {
+ std::size_t idIteration;
+ std::size_t iterationCount;
+ };
+ using ProgressCallback = std::function;
+ using RequestStopCallback = std::function;
+ void train(const std::vector& dataSamples, std::size_t nbIterations, ProgressCallback = ProgressCallback{}, RequestStopCallback = RequestStopCallback{});
- // data must be normalized
- struct CurrentIteration
- {
- std::size_t idIteration;
- std::size_t iterationCount;
- };
- using ProgressCallback = std::function;
- using RequestStopCallback = std::function;
- void train(const std::vector& dataSamples, std::size_t nbIterations, ProgressCallback = ProgressCallback{}, RequestStopCallback = RequestStopCallback{});
+ const InputVector& getRefVector(const Position& position) const;
+ Position getClosestRefVectorPosition(const InputVector& data) const;
+ std::optional getClosestRefVectorPosition(const InputVector& data, InputVector::Distance maxDistance) const;
- const InputVector& getRefVector(const Position& position) const;
- Position getClosestRefVectorPosition(const InputVector& data) const;
- std::optional getClosestRefVectorPosition(const InputVector& data, InputVector::Distance maxDistance) const;
+ std::optional getClosestRefVectorPosition(const std::vector& refVectorsPosition, InputVector::Distance maxDistance) const;
- std::optional getClosestRefVectorPosition(const std::vector& refVectorsPosition, InputVector::Distance maxDistance) const;
+ InputVector::Distance getRefVectorsDistance(const Position& position1, const Position& position2) const;
- InputVector::Distance getRefVectorsDistance(const Position& position1, const Position& position2) const;
+ InputVector::Distance computeRefVectorsDistanceMean() const;
+ InputVector::Distance computeRefVectorsDistanceMedian() const;
- InputVector::Distance computeRefVectorsDistanceMean() const;
- InputVector::Distance computeRefVectorsDistanceMedian() const;
+ void dump(std::ostream& os) const;
- void dump(std::ostream& os) const;
+ // For each ref vector, update formula is:
+ // i is the current iteration
+ // refVector(i+1) = refVector(i) + LearningFactor(i) * NeighbourhoodFunc(i) * (MatchingRefVector - refVector)
- // For each ref vector, update formula is:
- // i is the current iteration
- // refVector(i+1) = refVector(i) + LearningFactor(i) * NeighbourhoodFunc(i) * (MatchingRefVector - refVector)
+ using DistanceFunc = std::function;
+ void setDistanceFunc(DistanceFunc distanceFunc);
+ DistanceFunc getDistanceFunc() { return _distanceFunc; }
- using DistanceFunc = std::function;
- void setDistanceFunc(DistanceFunc distanceFunc);
- DistanceFunc getDistanceFunc() { return _distanceFunc; }
+ using LearningFactorFunc = std::function;
+ void setLearningFactorFunc(LearningFactorFunc learningFactorFunc);
- using LearningFactorFunc = std::function;
- void setLearningFactorFunc(LearningFactorFunc learningFactorFunc);
+ using NeighbourhoodFunc = std::function;
+ void setNeighbourhoodFunc(NeighbourhoodFunc neighbourhoodFunc);
- using NeighbourhoodFunc = std::function;
- void setNeighbourhoodFunc(NeighbourhoodFunc neighbourhoodFunc);
+ private:
- private:
+ void updateRefVectors(const Position& closestRefVectorPosition, const InputVector& input, LearningFactor learningFactor, const CurrentIteration& iteration);
- void updateRefVectors(const Position& closestRefVectorPosition, const InputVector& input, LearningFactor learningFactor, const CurrentIteration& iteration);
+ std::size_t _inputDimCount{};
+ InputVector _weights; // weight for each dimension
+ Matrix _refVectors;
- std::size_t _inputDimCount {};
- InputVector _weights; // weight for each dimension
- Matrix _refVectors;
-
- DistanceFunc _distanceFunc;
- LearningFactorFunc _learningFactorFunc;
- NeighbourhoodFunc _neighbourhoodFunc;
-};
+ DistanceFunc _distanceFunc;
+ LearningFactorFunc _learningFactorFunc;
+ NeighbourhoodFunc _neighbourhoodFunc;
+ };
} // namespace SOM