From 19b0856dacdd32424d6af28a65f91ee2e9bd79db Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 7 Jan 2024 15:49:30 +0100 Subject: [PATCH 1/3] Added google benchmark support + small test --- CMakeLists.txt | 10 ++ src/libs/som/CMakeLists.txt | 4 + src/libs/som/bench/CMakeLists.txt | 9 ++ src/libs/som/bench/SomBench.cpp | 54 +++++++++ src/libs/som/include/som/Matrix.hpp | 164 +++++++++++++-------------- src/libs/som/include/som/Network.hpp | 108 +++++++++--------- 6 files changed, 211 insertions(+), 138 deletions(-) create mode 100644 src/libs/som/bench/CMakeLists.txt create mode 100644 src/libs/som/bench/SomBench.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a48f53d8..80856a51 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ pkg_check_modules(LIBAV IMPORTED_TARGET libavcodec libavutil libavformat) pkg_check_modules(Archive REQUIRED IMPORTED_TARGET libarchive) find_package(PAM) find_package(STB) +find_package(benchmark) # WT if (NOT Wt_FOUND) @@ -70,6 +71,15 @@ elseif (IMAGE_LIBRARY STREQUAL STB AND NOT STB_FOUND) endif () message(STATUS "IMAGE_LIBRARY set to ${IMAGE_LIBRARY}") +# Benchmark +if (benchmark_FOUND) + set(BUILD_BENCHMARKS ON) + message(STATUS "Google Benchmark found. Building benchmarks") +else () + set(BUILD_BENCHMARKS OFF) + message(STATUS "Google Benchmark not found. Not building benchmarks") +endif() + add_subdirectory(src) install(DIRECTORY approot DESTINATION share/lms) 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 From 2214d75419bd7f868d1b5231dd92d60979a98fa1 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 3 Feb 2024 00:08:01 +0100 Subject: [PATCH 2/3] Made benchmarks an option --- CMakeLists.txt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 80856a51..a0108fc1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,6 @@ pkg_check_modules(LIBAV IMPORTED_TARGET libavcodec libavutil libavformat) pkg_check_modules(Archive REQUIRED IMPORTED_TARGET libarchive) find_package(PAM) find_package(STB) -find_package(benchmark) # WT if (NOT Wt_FOUND) @@ -72,12 +71,10 @@ endif () message(STATUS "IMAGE_LIBRARY set to ${IMAGE_LIBRARY}") # Benchmark -if (benchmark_FOUND) - set(BUILD_BENCHMARKS ON) - message(STATUS "Google Benchmark found. Building benchmarks") -else () - set(BUILD_BENCHMARKS OFF) - message(STATUS "Google Benchmark not found. Not building benchmarks") +option(BUILD_BENCHMARKS "Build benchmarks" OFF) +if (BUILD_BENCHMARKS) + find_package(benchmark REQUIRED) + message(STATUS "Building benchmarks") endif() add_subdirectory(src) From 676600e92da6e8f9d405fa8961a08e0cd0a13b44 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 3 Feb 2024 14:51:39 +0100 Subject: [PATCH 3/3] Build benchmarks on arch/alpine --- Dockerfile-build-alpine | 3 ++- Dockerfile-build-arch | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) 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