diff --git a/ydb/core/mind/hive/hive.h b/ydb/core/mind/hive/hive.h index 8a585cac2ab6..e1ecff02ea48 100644 --- a/ydb/core/mind/hive/hive.h +++ b/ydb/core/mind/hive/hive.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include @@ -201,20 +202,33 @@ TResourceNormalizedValues NormalizeRawValues(const TResourceRawValues& values, c NMetrics::EResource GetDominantResourceType(const TResourceRawValues& values, const TResourceRawValues& maximum); NMetrics::EResource GetDominantResourceType(const TResourceNormalizedValues& normValues); +// https://en.wikipedia.org/wiki/Kahan_summation_algorithm +template +std::ranges::range_value_t StableSum(const TRange& values) { + using TValue = std::ranges::range_value_t; + TValue sum{}; + TValue correction{}; + for (const auto& x : values) { + TValue y = x - correction; + TValue tmp = sum + y; + correction = (tmp - sum) - y; + sum = tmp; + } + return sum; +} + template inline std::tuple GetStDev(const TVector>& values) { std::tuple sum; if (values.empty()) return sum; - for (const auto& v : values) { - sum = sum + v; - } + sum = StableSum(values); auto mean = sum / values.size(); - sum = std::tuple(); - for (const auto& v : values) { - auto diff = v - mean; - sum = sum + diff * diff; - } + auto quadraticDev = [&] (const std::tuple& value) { + auto diff = value - mean; + return diff * diff; + }; + sum = StableSum(values | std::views::transform(quadraticDev)); auto div = sum / values.size(); auto st_dev = sqrt(div); return tuple_cast::cast(st_dev); diff --git a/ydb/core/mind/hive/hive_impl_ut.cpp b/ydb/core/mind/hive/hive_impl_ut.cpp index e3ba0468f58c..a2a734886b37 100644 --- a/ydb/core/mind/hive/hive_impl_ut.cpp +++ b/ydb/core/mind/hive/hive_impl_ut.cpp @@ -193,4 +193,22 @@ Y_UNIT_TEST_SUITE(THiveImplTest) { Ctest << "HIVE_TABLET_BALANCE_STRATEGY_RANDOM" << Endl; CheckSpeedAndDistribution(allTablets, BalanceTablets, EResourceToBalance::Memory); } + + Y_UNIT_TEST(TestStDev) { + using TSingleResource = std::tuple; + + TVector values(100, 50.0 / 1'000'000); + values.front() = 51.0 / 1'000'000; + + double stDev1 = std::get<0>(GetStDev(values)); + + std::swap(values.front(), values.back()); + + double stDev2 = std::get<0>(GetStDev(values)); + + double expectedStDev = sqrt(0.9703) / 1'000'000; + + UNIT_ASSERT_DOUBLES_EQUAL(expectedStDev, stDev1, 1e-6); + UNIT_ASSERT_VALUES_EQUAL(stDev1, stDev2); + } }