lib/core/unittest/CMemoryUsageTest.cc (1,058 lines of code) (raw):
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the following additional limitation. Functionality enabled by the
* files subject to the Elastic License 2.0 may only be used in production when
* invoked by an Elasticsearch process with a license key installed that permits
* use of machine learning features. You may not use this file except in
* compliance with the Elastic License 2.0 and the foregoing additional
* limitation.
*/
#include <core/CAlignment.h>
#include <core/CHashing.h>
#include <core/CLogger.h>
#include <core/CMemoryDefStd.h>
#include <core/CSmallVector.h>
#include <test/CRandomNumbers.h>
#include <boost/circular_buffer.hpp>
#include <boost/container/flat_map.hpp>
#include <boost/container/flat_set.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/unordered_map.hpp>
#include <any>
#include <cstdlib>
#include <deque>
#include <limits>
#include <list>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <type_traits>
BOOST_AUTO_TEST_SUITE(CMemoryUsageTest)
using namespace ml;
namespace {
// Subset of model_t equivalent duplicated here to avoid a dependency
// with the model library
enum EFeature {
E_IndividualHighMeanByPerson,
E_IndividualCountByBucketAndPerson,
E_IndividualHighCountsByBucketAndPerson
};
using TIntVec = std::vector<int>;
using TStrVec = std::vector<std::string>;
struct SPod {
double s_V1;
double s_V2;
int s_V3;
};
struct SFoo {
static constexpr bool dynamicSizeAlwaysZero() { return true; }
explicit SFoo(std::size_t key = 0) : s_Key(key) {}
bool operator<(const SFoo& rhs) const { return s_Key < rhs.s_Key; }
bool operator==(const SFoo& rhs) const { return s_Key == rhs.s_Key; }
std::size_t s_Key;
double s_State[100];
};
struct SFooWithMemoryUsage {
explicit SFooWithMemoryUsage(std::size_t key = 0) : s_Key(key) {}
bool operator<(const SFooWithMemoryUsage& rhs) const {
return s_Key < rhs.s_Key;
}
bool operator==(const SFooWithMemoryUsage& rhs) const {
return s_Key == rhs.s_Key;
}
std::size_t memoryUsage() const { return 0; }
void debugMemoryUsage(const core::CMemoryUsage::TMemoryUsagePtr& mem) const {
mem->setName("SFooWithMemoryUsage", 0);
}
std::size_t s_Key;
double s_State[100];
};
struct SFooWrapper {
std::size_t memoryUsage() const {
std::size_t mem = core::memory::dynamicSize(s_Foo);
return mem;
}
SFooWithMemoryUsage s_Foo;
};
struct SBar {
using TFooVec = std::vector<SFoo>;
explicit SBar(std::size_t key = 0) : s_Key(key), s_State() {}
bool operator<(const SBar& rhs) const { return s_Key < rhs.s_Key; }
bool operator==(const SBar& rhs) const { return s_Key == rhs.s_Key; }
std::size_t memoryUsage() const {
return sizeof(SFoo) * s_State.capacity();
}
std::size_t s_Key;
TFooVec s_State;
};
struct SBarDebug {
using TFooVec = std::vector<SFoo>;
explicit SBarDebug(std::size_t key = 0) : s_Key(key), s_State() {}
bool operator<(const SBarDebug& rhs) const { return s_Key < rhs.s_Key; }
bool operator==(const SBarDebug& rhs) const { return s_Key == rhs.s_Key; }
std::size_t memoryUsage() const {
return sizeof(SFoo) * s_State.capacity();
}
void debugMemoryUsage(const core::CMemoryUsage::TMemoryUsagePtr& mem) const {
mem->setName("SBarDebug", 0);
core::memory_debug::dynamicSize("s_State", s_State, mem);
}
std::size_t s_Key;
TFooVec s_State;
};
struct SBarVectorDebug {
using TFooVec = std::vector<SFooWithMemoryUsage>;
explicit SBarVectorDebug(std::size_t key = 0) : s_Key(key), s_State() {}
bool operator<(const SBarVectorDebug& rhs) const {
return s_Key < rhs.s_Key;
}
bool operator==(const SBarVectorDebug& rhs) const {
return s_Key == rhs.s_Key;
}
std::size_t memoryUsage() const {
return core::memory::dynamicSize(s_State);
}
void debugMemoryUsage(const core::CMemoryUsage::TMemoryUsagePtr& mem) const {
mem->setName("SBarVectorDebug", 0);
core::memory_debug::dynamicSize("s_State", s_State, mem);
}
std::size_t s_Key;
TFooVec s_State;
};
struct SHash {
std::size_t operator()(const SFoo& foo) const { return foo.s_Key; }
std::size_t operator()(const SFooWithMemoryUsage& foo) const {
return foo.s_Key;
}
std::size_t operator()(const SBar& bar) const { return bar.s_Key; }
};
class CBase {
public:
explicit CBase(std::size_t i) : m_Vec(i, 0) {}
virtual ~CBase() = default;
virtual std::size_t memoryUsage() const {
return core::memory::dynamicSize(m_Vec);
}
virtual void debugMemoryUsage(const core::CMemoryUsage::TMemoryUsagePtr& mem) const {
mem->setName("CBase", 0);
core::memory_debug::dynamicSize("m_Vec", m_Vec, mem);
}
virtual std::size_t staticSize() const { return sizeof(*this); }
const std::uint64_t* fixed() const { return m_Fixed; } // suppress warning
private:
std::uint64_t m_Fixed[5];
TIntVec m_Vec;
};
class CDerived : public CBase {
public:
explicit CDerived(std::size_t i)
: CBase(i), m_Strings(i, "This is a secret string") {}
~CDerived() override = default;
std::size_t memoryUsage() const override {
std::size_t mem = core::memory::dynamicSize(m_Strings);
mem += this->CBase::memoryUsage();
return mem;
}
void debugMemoryUsage(const core::CMemoryUsage::TMemoryUsagePtr& mem) const override {
mem->setName("CDerived", 0);
core::memory_debug::dynamicSize("m_Strings", m_Strings, mem);
this->CBase::debugMemoryUsage(mem->addChild());
}
std::size_t staticSize() const override { return sizeof(*this); }
const std::uint64_t* fixed() const { return m_Fixed; } // suppress warning
private:
std::uint64_t m_Fixed[50];
TStrVec m_Strings;
};
//! A basic allocator that tracks memory usage
template<typename T>
class CTrackingAllocator : public std::allocator<T> {
public:
using value_type = T;
using pointer = value_type*;
using const_pointer = const value_type*;
using reference = value_type&;
using const_reference = const value_type&;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using allocator_type = std::allocator<T>;
using traits_type = std::allocator_traits<allocator_type>;
public:
// convert an allocator<T> to allocator<U>
template<typename U>
struct rebind {
using other = CTrackingAllocator<U>;
};
public:
CTrackingAllocator() = default;
CTrackingAllocator(const CTrackingAllocator&) = default;
template<typename U>
CTrackingAllocator(const CTrackingAllocator<U>&) {}
// address
inline pointer address(reference r) { return &r; }
inline const_pointer address(const_reference r) { return &r; }
// memory allocation
inline pointer allocate(size_type cnt, typename traits_type::const_pointer = nullptr) {
ms_Allocated += cnt;
return reinterpret_cast<pointer>(::operator new(cnt * sizeof(T)));
}
inline void deallocate(pointer p, size_type cnt) {
ms_Allocated -= cnt;
::operator delete(p);
}
// size
inline size_type max_size() const {
return std::numeric_limits<size_type>::max() / sizeof(T);
}
static std::size_t usage() { return ms_Allocated; }
// construction/destruction
inline void construct(pointer p, const T& t) { new (p) T(t); }
inline void destroy(pointer p) { p->~T(); }
inline bool operator==(const CTrackingAllocator&) const { return true; }
inline bool operator!=(const CTrackingAllocator& a) const {
return !operator==(a);
}
private:
static std::size_t ms_Allocated;
};
template<typename T>
std::size_t CTrackingAllocator<T>::ms_Allocated = 0;
}
BOOST_AUTO_TEST_CASE(testUsage) {
using TDoubleUPtr = std::unique_ptr<double>;
using TDoubleVec = std::vector<double>;
using TDoubleVecVec = std::vector<TDoubleVec>;
using TDoubleVecMultiset = std::multiset<TDoubleVec>;
using TDoubleDoubleVecMap = std::map<double, TDoubleVec>;
using TDoubleDoubleVecMultimap = std::multimap<double, TDoubleVec>;
using TFooVec = std::vector<SFoo>;
using TFooWithMemoryVec = std::vector<SFooWithMemoryUsage>;
using TFooList = std::list<SFoo>;
using TFooWithMemoryList = std::list<SFooWithMemoryUsage>;
using TFooDeque = std::deque<SFoo>;
using TFooWithMemoryDeque = std::deque<SFooWithMemoryUsage>;
using TFooCircBuf = boost::circular_buffer<SFoo>;
using TFooWithMemoryCircBuf = boost::circular_buffer<SFooWithMemoryUsage>;
using TFooFooMap = std::map<SFoo, SFoo>;
using TFooWithMemoryFooWithMemoryMap = std::map<SFooWithMemoryUsage, SFooWithMemoryUsage>;
using TFooFooUMap = boost::unordered_map<SFoo, SFoo, SHash>;
using TFooFSet = boost::container::flat_set<SFoo>;
using TFooWithMemoryFooWithMemoryUMap =
boost::unordered_map<SFooWithMemoryUsage, SFooWithMemoryUsage, SHash>;
using TBarVec = std::vector<SBar>;
using TBarBarMap = std::map<SBar, SBar>;
using TBarBarUMap = boost::unordered_map<SBar, SBar, SHash>;
using TBarBarFMap = boost::container::flat_map<SBar, SBar>;
using TBarPtr = std::shared_ptr<SBar>;
using TBasePtr = std::shared_ptr<CBase>;
using TDerivedVec = std::vector<CDerived>;
using TBasePtrVec = std::vector<TBasePtr>;
using TAnyVec = std::vector<std::any>;
// Check std::unique_ptr behaves as expected.
{
BOOST_REQUIRE_EQUAL(0, core::memory::dynamicSize(TDoubleUPtr{}));
BOOST_REQUIRE_EQUAL(sizeof(double),
core::memory::dynamicSize(std::make_unique<double>(1.0)));
}
// Check that containers of containers work as expected.
{
TDoubleVecVec v1{{}, {}, {}};
TDoubleVecVec v2{{1.0}, {2.0, 2.0}, {3.0, 3.0, 3.0}};
std::size_t actualMemoryUsage{core::memory::dynamicSize(v2)};
std::size_t expectedMemoryUsage{
core::memory::dynamicSize(v1) + core::memory::dynamicSize(v2[0]) +
core::memory::dynamicSize(v2[1]) + core::memory::dynamicSize(v2[2])};
LOG_DEBUG(<< "*** TDoubleVecVec ***");
LOG_DEBUG(<< "expected = " << expectedMemoryUsage);
LOG_DEBUG(<< "actual = " << actualMemoryUsage);
BOOST_REQUIRE_EQUAL(expectedMemoryUsage, actualMemoryUsage);
}
{
TDoubleVecMultiset s1{{}, {}, {}};
TDoubleVec v1{1.0};
TDoubleVec v2{2.0, 2.0};
TDoubleVec v3{3.0, 3.0, 3.0};
TDoubleVecMultiset s2{v1, v2, v3};
std::size_t actualMemoryUsage{core::memory::dynamicSize(s2)};
std::size_t expectedMemoryUsage{
core::memory::dynamicSize(s1) + core::memory::dynamicSize(v1) +
core::memory::dynamicSize(v2) + core::memory::dynamicSize(v3)};
LOG_DEBUG(<< "*** TDoubleVecMultiset ***");
LOG_DEBUG(<< "expected = " << expectedMemoryUsage);
LOG_DEBUG(<< "actual = " << actualMemoryUsage);
BOOST_REQUIRE_EQUAL(expectedMemoryUsage, actualMemoryUsage);
}
{
TDoubleDoubleVecMap m1{{1.0, {}}, {2.0, {}}, {3.0, {}}};
TDoubleVec v1{1.0};
TDoubleVec v2{2.0, 2.0};
TDoubleVec v3{3.0, 3.0, 3.0};
TDoubleDoubleVecMap m2{{1.0, v1}, {2.0, v2}, {3.0, v3}};
std::size_t actualMemoryUsage{core::memory::dynamicSize(m2)};
std::size_t expectedMemoryUsage{
core::memory::dynamicSize(m1) + core::memory::dynamicSize(v1) +
core::memory::dynamicSize(v2) + core::memory::dynamicSize(v3)};
LOG_DEBUG(<< "*** TDoubleDoubleVecMap ***");
LOG_DEBUG(<< "expected = " << expectedMemoryUsage);
LOG_DEBUG(<< "actual = " << actualMemoryUsage);
BOOST_REQUIRE_EQUAL(expectedMemoryUsage, actualMemoryUsage);
}
{
TDoubleDoubleVecMultimap m1{{1.0, {}}, {2.0, {}}, {3.0, {}}};
TDoubleVec v1{1.0};
TDoubleVec v2{2.0, 2.0};
TDoubleVec v3{3.0, 3.0, 3.0};
TDoubleDoubleVecMultimap m2{{1.0, v1}, {2.0, v2}, {3.0, v3}};
std::size_t actualMemoryUsage{core::memory::dynamicSize(m2)};
std::size_t expectedMemoryUsage{
core::memory::dynamicSize(m1) + core::memory::dynamicSize(v1) +
core::memory::dynamicSize(v2) + core::memory::dynamicSize(v3)};
LOG_DEBUG(<< "*** TDoubleDoubleVecMap ***");
LOG_DEBUG(<< "expected = " << expectedMemoryUsage);
LOG_DEBUG(<< "actual = " << actualMemoryUsage);
BOOST_REQUIRE_EQUAL(expectedMemoryUsage, actualMemoryUsage);
}
// We want various invariants to hold for dynamic size:
// 1) The dynamic size is not affected by adding a memoryUsage
// to a class definition.
// 2) If a member is stored by value don't double count its
// memory.
// 3) The dynamic size of an object is not affected by whether
// it is stored in a container or not.
{
TFooVec foos(10);
TFooWithMemoryVec foosWithMemory(10);
LOG_DEBUG(<< "*** TFooVec ***");
LOG_DEBUG(<< "dynamicSize(foos) = " << core::memory::dynamicSize(foos));
LOG_DEBUG(<< "dynamicSize(foosWithMemory) = "
<< core::memory::dynamicSize(foosWithMemory));
BOOST_REQUIRE_EQUAL(core::memory::dynamicSize(foos),
core::memory::dynamicSize(foosWithMemory));
}
{
TFooList foos(10);
TFooWithMemoryList foosWithMemory(10);
LOG_DEBUG(<< "*** TFooList ***");
LOG_DEBUG(<< "dynamicSize(foos) = " << core::memory::dynamicSize(foos));
LOG_DEBUG(<< "dynamicSize(foosWithMemory) = "
<< core::memory::dynamicSize(foosWithMemory));
BOOST_REQUIRE_EQUAL(core::memory::dynamicSize(foos),
core::memory::dynamicSize(foosWithMemory));
}
{
TFooDeque foos(10);
TFooWithMemoryDeque foosWithMemory(10);
LOG_DEBUG(<< "*** TFooDeque ***");
LOG_DEBUG(<< "dynamicSize(foos) = " << core::memory::dynamicSize(foos));
LOG_DEBUG(<< "dynamicSize(foosWithMemory) = "
<< core::memory::dynamicSize(foosWithMemory));
BOOST_REQUIRE_EQUAL(core::memory::dynamicSize(foos),
core::memory::dynamicSize(foosWithMemory));
}
{
TFooCircBuf foos(10);
foos.resize(5);
TFooWithMemoryCircBuf foosWithMemory(10);
foosWithMemory.resize(5);
LOG_DEBUG(<< "*** TFooCircBuf ***");
LOG_DEBUG(<< "dynamicSize(foos) = " << core::memory::dynamicSize(foos));
LOG_DEBUG(<< "dynamicSize(foosWithMemory) = "
<< core::memory::dynamicSize(foosWithMemory));
BOOST_REQUIRE_EQUAL(core::memory::dynamicSize(foos),
core::memory::dynamicSize(foosWithMemory));
}
{
TFooFooMap foos;
TFooWithMemoryFooWithMemoryMap foosWithMemory;
for (auto key : {0, 1, 2, 3, 4, 5}) {
foos[SFoo(key)] = SFoo(key);
foosWithMemory[SFooWithMemoryUsage(key)] = SFooWithMemoryUsage(key);
}
LOG_DEBUG(<< "*** TFooFooMap ***");
LOG_DEBUG(<< "dynamicSize(foos) = " << core::memory::dynamicSize(foos));
LOG_DEBUG(<< "dynamicSize(foosWithMemory) = "
<< core::memory::dynamicSize(foosWithMemory));
BOOST_REQUIRE_EQUAL(core::memory::dynamicSize(foos),
core::memory::dynamicSize(foosWithMemory));
}
{
TFooFooUMap foos;
TFooWithMemoryFooWithMemoryUMap foosWithMemory;
for (auto key : {0, 1, 2, 3, 4, 5}) {
foos[SFoo(key)] = SFoo(key);
foosWithMemory[SFooWithMemoryUsage(key)] = SFooWithMemoryUsage(key);
}
LOG_DEBUG(<< "*** TFooFooUMap ***");
LOG_DEBUG(<< "dynamicSize(foos) = " << core::memory::dynamicSize(foos));
LOG_DEBUG(<< "dynamicSize(foosWithMemory) = "
<< core::memory::dynamicSize(foosWithMemory));
BOOST_REQUIRE_EQUAL(core::memory::dynamicSize(foos),
core::memory::dynamicSize(foosWithMemory));
}
{
TFooFSet foos;
for (auto key : {0, 1, 2, 3, 4, 5}) {
foos.insert(SFoo(key));
}
LOG_DEBUG(<< "*** TFooFSet ***");
LOG_DEBUG(<< "dynamicSize(foos) = " << core::memory::dynamicSize(foos));
BOOST_REQUIRE_EQUAL(core::memory::dynamicSize(foos),
foos.capacity() * sizeof(SFoo));
}
{
LOG_DEBUG(<< "*** SFooWrapper ***");
SFooWithMemoryUsage foo;
SFooWrapper wrapper;
LOG_DEBUG(<< "memoryUsage foo = " << foo.memoryUsage());
LOG_DEBUG(<< "memoryUsage wrapper = " << wrapper.memoryUsage());
BOOST_REQUIRE_EQUAL(foo.memoryUsage(), wrapper.memoryUsage());
}
{
TBarVec bars1;
bars1.reserve(10);
bars1.push_back(SBar());
bars1.push_back(SBar());
bars1[0].s_State.resize(1);
bars1[1].s_State.resize(2);
TBarVec bars2;
bars2.reserve(10);
bars2.push_back(SBar());
bars2.push_back(SBar());
TFooVec state21;
state21.resize(1);
TFooVec state22;
state22.resize(2);
LOG_DEBUG(<< "*** TBarVec ***");
LOG_DEBUG(<< "dynamic size = " << core::memory::dynamicSize(bars1));
LOG_DEBUG(<< "expected dynamic size = "
<< core::memory::dynamicSize(bars2) + core::memory::dynamicSize(state21) +
core::memory::dynamicSize(state22));
BOOST_REQUIRE_EQUAL(core::memory::dynamicSize(bars1),
core::memory::dynamicSize(bars2) +
core::memory::dynamicSize(state21) +
core::memory::dynamicSize(state22));
}
{
SBar key;
key.s_State.resize(3);
SBar value;
value.s_State.resize(2);
TBarBarMap bars1;
bars1[key] = value;
TBarBarMap bars2;
bars2[SBar()] = SBar();
LOG_DEBUG(<< "*** TBarBarMap ***");
LOG_DEBUG(<< "dynamic size = " << core::memory::dynamicSize(bars1));
LOG_DEBUG(<< "expected dynamic size = "
<< core::memory::dynamicSize(bars2) + core::memory::dynamicSize(key) +
core::memory::dynamicSize(value));
BOOST_REQUIRE_EQUAL(core::memory::dynamicSize(bars1),
core::memory::dynamicSize(bars2) +
core::memory::dynamicSize(key) +
core::memory::dynamicSize(value));
}
{
SBar key;
key.s_State.resize(3);
SBar value;
value.s_State.resize(2);
TBarBarUMap bars1;
bars1[key] = value;
TBarBarUMap bars2;
bars2[SBar()] = SBar();
LOG_DEBUG(<< "*** TBarBarUMap ***");
LOG_DEBUG(<< "dynamic size = " << core::memory::dynamicSize(bars1));
LOG_DEBUG(<< "expected dynamic size = "
<< core::memory::dynamicSize(bars2) + core::memory::dynamicSize(key) +
core::memory::dynamicSize(value));
BOOST_REQUIRE_EQUAL(core::memory::dynamicSize(bars1),
core::memory::dynamicSize(bars2) +
core::memory::dynamicSize(key) +
core::memory::dynamicSize(value));
}
{
SBar key;
key.s_State.resize(3);
SBar value;
value.s_State.resize(2);
TBarBarFMap bars1;
bars1.reserve(4);
BOOST_TEST_REQUIRE(core::memory::dynamicSize(bars1) > 4 * sizeof(SBar));
bars1[key] = value;
TBarBarFMap bars2;
bars2.reserve(4);
bars2[SBar()] = SBar();
LOG_DEBUG(<< "*** TBarBarFMap ***");
LOG_DEBUG(<< "dynamic size = " << core::memory::dynamicSize(bars1));
LOG_DEBUG(<< "expected dynamic size = "
<< core::memory::dynamicSize(bars2) + core::memory::dynamicSize(key) +
core::memory::dynamicSize(value));
BOOST_REQUIRE_EQUAL(core::memory::dynamicSize(bars1),
core::memory::dynamicSize(bars2) +
core::memory::dynamicSize(key) +
core::memory::dynamicSize(value));
}
{
SBar value;
value.s_State.resize(3);
TBarPtr pointer(new SBar(value));
LOG_DEBUG(<< "*** TBarPtr ***");
LOG_DEBUG(<< "dynamic size = " << core::memory::dynamicSize(pointer));
LOG_DEBUG(<< "expected dynamic size = "
<< sizeof(SBar) + sizeof(SFoo) * value.s_State.capacity());
BOOST_REQUIRE_EQUAL(core::memory::dynamicSize(pointer),
sizeof(long) + sizeof(SBar) +
sizeof(SFoo) * value.s_State.capacity());
}
{
LOG_DEBUG(<< "*** std::any ***");
TDoubleVec a(10);
TFooVec b(20);
TAnyVec variables(1); // Empty any at index 0
variables.push_back(a);
variables.push_back(b);
LOG_DEBUG(<< "wrong dynamic size = " << core::memory::dynamicSize(variables));
BOOST_REQUIRE_EQUAL(variables.capacity() * sizeof(std::any),
core::memory::dynamicSize(variables));
auto& visitor = core::memory::anyVisitor();
visitor.registerCallback<TDoubleVec>();
visitor.registerCallback<TFooVec>();
LOG_DEBUG(<< "dynamic size = " << core::memory::dynamicSize(variables));
LOG_DEBUG(<< "expected dynamic size = "
<< variables.capacity() * sizeof(std::any) + sizeof(a) +
core::memory::dynamicSize(a) + sizeof(b) +
core::memory::dynamicSize(b));
BOOST_REQUIRE_EQUAL(variables.capacity() * sizeof(std::any) +
sizeof(a) + core::memory::dynamicSize(a) +
sizeof(b) + core::memory::dynamicSize(b),
core::memory::dynamicSize(variables));
auto& debugVisitor = core::memory_debug::anyVisitor();
debugVisitor.registerCallback<TDoubleVec>();
debugVisitor.registerCallback<TFooVec>();
auto mem = std::make_shared<core::CMemoryUsage>();
core::memory_debug::dynamicSize("", variables, mem);
BOOST_REQUIRE_EQUAL(mem->usage(), core::memory::dynamicSize(variables));
std::ostringstream ss;
mem->print(ss);
LOG_DEBUG(<< ss.str());
}
{
CBase* base = new CBase(10);
CBase* derived = new CDerived(10);
{
auto mem = std::make_shared<core::CMemoryUsage>();
core::memory_debug::dynamicSize("", *base, mem);
BOOST_REQUIRE_EQUAL(mem->usage(), core::memory::dynamicSize(*base));
std::ostringstream ss;
mem->print(ss);
LOG_TRACE(<< ss.str());
}
{
auto mem = std::make_shared<core::CMemoryUsage>();
core::memory_debug::dynamicSize("", *derived, mem);
BOOST_REQUIRE_EQUAL(mem->usage(), core::memory::dynamicSize(*derived));
std::ostringstream ss;
mem->print(ss);
LOG_TRACE(<< ss.str());
}
BOOST_TEST_REQUIRE(core::memory::dynamicSize(*base) <
core::memory::dynamicSize(*derived));
TBasePtr sharedBase(new CBase(10));
TBasePtr sharedDerived(new CDerived(10));
{
auto mem = std::make_shared<core::CMemoryUsage>();
core::memory_debug::dynamicSize("", sharedBase, mem);
BOOST_REQUIRE_EQUAL(mem->usage(), core::memory::dynamicSize(sharedBase));
std::ostringstream ss;
mem->print(ss);
LOG_TRACE(<< ss.str());
}
{
auto mem = std::make_shared<core::CMemoryUsage>();
core::memory_debug::dynamicSize("", sharedDerived, mem);
BOOST_REQUIRE_EQUAL(mem->usage(), core::memory::dynamicSize(sharedDerived));
std::ostringstream ss;
mem->print(ss);
LOG_TRACE(<< ss.str());
}
// boost:reference_wrapper should give zero
std::reference_wrapper<CBase> baseRef(std::ref(*base));
BOOST_REQUIRE_EQUAL(0, core::memory::dynamicSize(baseRef));
{
auto mem = std::make_shared<core::CMemoryUsage>();
core::memory_debug::dynamicSize("", baseRef, mem);
BOOST_REQUIRE_EQUAL(mem->usage(), core::memory::dynamicSize(baseRef));
std::ostringstream ss;
mem->print(ss);
LOG_TRACE(<< ss.str());
}
}
{
CBase base(5);
BOOST_REQUIRE_EQUAL(base.memoryUsage(), core::memory::dynamicSize(base));
CBase* basePtr = new CBase(5);
BOOST_REQUIRE_EQUAL(basePtr->memoryUsage() + sizeof(*basePtr),
core::memory::dynamicSize(basePtr));
CDerived derived(6);
BOOST_REQUIRE_EQUAL(derived.memoryUsage(), core::memory::dynamicSize(derived));
CDerived* derivedPtr = new CDerived(5);
BOOST_REQUIRE_EQUAL(derivedPtr->memoryUsage() + sizeof(*derivedPtr),
core::memory::dynamicSize(derivedPtr));
CBase* basederivedPtr = new CDerived(5);
BOOST_REQUIRE_EQUAL(basederivedPtr->memoryUsage() + sizeof(CDerived),
core::memory::dynamicSize(basederivedPtr));
TBasePtr sPtr(new CDerived(6));
BOOST_REQUIRE_EQUAL(sPtr->memoryUsage() + sizeof(long) + sizeof(CDerived),
core::memory::dynamicSize(sPtr));
}
{
TDerivedVec vec;
vec.reserve(6);
vec.push_back(CDerived(1));
vec.push_back(CDerived(3));
vec.push_back(CDerived(5));
vec.push_back(CDerived(7));
vec.push_back(CDerived(9));
vec.push_back(CDerived(12));
std::size_t total = core::memory::dynamicSize(vec);
std::size_t calc = vec.capacity() * sizeof(CDerived);
for (std::size_t i = 0; i < vec.size(); ++i) {
calc += vec[i].memoryUsage();
}
BOOST_REQUIRE_EQUAL(calc, total);
}
{
TBasePtrVec vec;
vec.push_back(TBasePtr(new CBase(2)));
vec.push_back(TBasePtr(new CBase(2)));
vec.push_back(TBasePtr(new CBase(2)));
vec.push_back(TBasePtr(new CBase(2)));
vec.push_back(TBasePtr(new CBase(2)));
vec.push_back(TBasePtr(new CBase(2)));
vec.push_back(TBasePtr(new CDerived(44)));
std::size_t total = core::memory::dynamicSize(vec);
std::size_t calc = vec.capacity() * sizeof(TBasePtr);
for (std::size_t i = 0; i < 6; ++i) {
calc += sizeof(long);
calc += static_cast<CBase*>(vec[i].get())->memoryUsage();
calc += sizeof(CBase);
}
calc += sizeof(long);
calc += static_cast<CDerived*>(vec[6].get())->memoryUsage();
calc += sizeof(CDerived);
BOOST_REQUIRE_EQUAL(calc, total);
}
}
BOOST_AUTO_TEST_CASE(testDebug) {
using TBarVec = std::vector<SBar>;
using TBarVecPtr = std::shared_ptr<TBarVec>;
// Check that we can get debug info out of classes with vectors of varying size
{
SBar sbar;
SBarDebug sbarDebug;
SBarVectorDebug sbarVectorDebug;
for (unsigned i = 0; i < 9; ++i) {
sbar.s_State.push_back(SFoo(i));
sbarDebug.s_State.push_back(SFoo(i));
sbarVectorDebug.s_State.push_back(SFooWithMemoryUsage(i));
LOG_TRACE(<< "SFooWithMemoryUsage usage: "
<< sbarVectorDebug.s_State.back().memoryUsage());
}
BOOST_REQUIRE_EQUAL(sbar.memoryUsage(), sbarDebug.memoryUsage());
BOOST_REQUIRE_EQUAL(sbar.memoryUsage(), sbarVectorDebug.memoryUsage());
{
auto mem = std::make_shared<core::CMemoryUsage>();
sbarDebug.debugMemoryUsage(mem);
BOOST_REQUIRE_EQUAL(sbarDebug.memoryUsage(), mem->usage());
std::ostringstream ss;
mem->print(ss);
LOG_TRACE(<< "SBarDebug: " + ss.str());
}
{
auto mem = std::make_shared<core::CMemoryUsage>();
sbarVectorDebug.debugMemoryUsage(mem);
std::ostringstream ss;
mem->print(ss);
LOG_TRACE(<< "SBarVectorDebug: " + ss.str());
LOG_TRACE(<< "memoryUsage: " << sbarVectorDebug.memoryUsage()
<< ", debugUsage: " << mem->usage());
BOOST_REQUIRE_EQUAL(sbarVectorDebug.memoryUsage(), mem->usage());
}
}
{
TBarVecPtr t(new TBarVec());
t->push_back(SBar(0));
t->push_back(SBar(1));
t->push_back(SBar(2));
t->push_back(SBar(3));
t->push_back(SBar(4));
core::CMemoryUsage memoryUsage;
memoryUsage.setName("test", 0);
core::memory_debug::dynamicSize("TBarVecPtr", t, memoryUsage.addChild());
std::ostringstream ss;
memoryUsage.print(ss);
LOG_TRACE(<< "TBarVecPtr usage: " << core::memory::dynamicSize(t)
<< ", debug: " << memoryUsage.usage());
LOG_TRACE(<< ss.str());
BOOST_REQUIRE_EQUAL(core::memory::dynamicSize(t), memoryUsage.usage());
}
{
using TFeatureBarVecPtrPr = std::pair<EFeature, TBarVecPtr>;
using TFeatureBarVecPtrPrVec = std::vector<TFeatureBarVecPtrPr>;
TFeatureBarVecPtrPrVec t;
TBarVecPtr vec(new TBarVec());
vec->push_back(SBar(0));
vec->push_back(SBar(1));
vec->push_back(SBar(2));
vec->push_back(SBar(3));
vec->push_back(SBar(4));
t.push_back(TFeatureBarVecPtrPr(E_IndividualHighMeanByPerson, vec));
TBarVecPtr vec2(new TBarVec());
vec2->push_back(SBar(22));
vec2->push_back(SBar(33));
t.push_back(TFeatureBarVecPtrPr(E_IndividualCountByBucketAndPerson, vec));
t.push_back(TFeatureBarVecPtrPr(E_IndividualHighCountsByBucketAndPerson,
TBarVecPtr()));
core::CMemoryUsage memoryUsage;
memoryUsage.setName("test", 0);
core::memory_debug::dynamicSize("TFeatureBarVecPtrPrVec", t, memoryUsage.addChild());
std::ostringstream ss;
memoryUsage.print(ss);
LOG_TRACE(<< "TFeatureBarVecPtrPrVec usage: " << core::memory::dynamicSize(t)
<< ", debug: " << memoryUsage.usage());
LOG_TRACE(<< ss.str());
BOOST_REQUIRE_EQUAL(core::memory::dynamicSize(t), memoryUsage.usage());
}
}
BOOST_AUTO_TEST_CASE(testDynamicSizeAlwaysZero) {
bool test = core::memory_detail::SDynamicSizeAlwaysZero<int>::value();
BOOST_REQUIRE_EQUAL(true, test);
test = core::memory_detail::SDynamicSizeAlwaysZero<double>::value();
BOOST_REQUIRE_EQUAL(true, test);
test = core::memory_detail::SDynamicSizeAlwaysZero<SPod>::value();
BOOST_REQUIRE_EQUAL(true, test);
test = core::memory_detail::SDynamicSizeAlwaysZero<std::optional<double>>::value();
BOOST_REQUIRE_EQUAL(true, test);
test = core::memory_detail::SDynamicSizeAlwaysZero<std::optional<SPod>>::value();
BOOST_REQUIRE_EQUAL(true, test);
test = core::memory_detail::SDynamicSizeAlwaysZero<std::pair<int, int>>::value();
BOOST_REQUIRE_EQUAL(true, test);
test = std::is_pod<SFoo>::value;
BOOST_REQUIRE_EQUAL(false, test);
test = core::memory_detail::SDynamicSizeAlwaysZero<SFoo>::value();
BOOST_REQUIRE_EQUAL(true, test);
test = core::memory_detail::SDynamicSizeAlwaysZero<std::pair<std::optional<double>, SFoo>>::value();
BOOST_REQUIRE_EQUAL(true, test);
test = core::memory_detail::SDynamicSizeAlwaysZero<core::CHashing::CUniversalHash::CUInt32Hash>::value();
BOOST_REQUIRE_EQUAL(true, test);
test = core::memory_detail::SDynamicSizeAlwaysZero<core::CHashing::CUniversalHash::CUInt32UnrestrictedHash>::value();
BOOST_REQUIRE_EQUAL(true, test);
test = core::memory_detail::SDynamicSizeAlwaysZero<std::pair<double, SFooWithMemoryUsage>>::value();
BOOST_REQUIRE_EQUAL(false, test);
test = core::memory_detail::SDynamicSizeAlwaysZero<SFooWithMemoryUsage>::value();
BOOST_REQUIRE_EQUAL(false, test);
test = core::memory_detail::SDynamicSizeAlwaysZero<SFooWrapper>::value();
BOOST_REQUIRE_EQUAL(false, test);
}
BOOST_AUTO_TEST_CASE(testCompress) {
{
// Check that non-repeated entries are not removed
core::CMemoryUsage mem;
mem.setName("root", 1);
mem.addChild()->setName("child1", 22);
mem.addChild()->setName("child2", 23);
mem.addChild()->setName("child3", 24);
mem.addChild()->setName("child4", 25);
mem.addItem("item1", 91);
mem.addItem("item2", 92);
mem.addItem("item3", 93);
mem.addItem("item4", 94);
mem.addItem("item5", 95);
mem.addItem("item6", 96);
BOOST_REQUIRE_EQUAL(656, mem.usage());
std::string before;
{
std::ostringstream ss;
mem.print(ss);
before = ss.str();
}
mem.compress();
BOOST_REQUIRE_EQUAL(656, mem.usage());
std::string after;
{
std::ostringstream ss;
mem.print(ss);
after = ss.str();
}
BOOST_REQUIRE_EQUAL(before, after);
}
{
// Check that repeated entries are removed
core::CMemoryUsage mem;
mem.setName("root", 1);
mem.addChild()->setName("muffin", 4);
mem.addChild()->setName("child", 3);
auto child = mem.addChild();
child->setName("child", 5);
child->addChild()->setName("grandchild", 100);
mem.addChild()->setName("child", 7);
mem.addChild()->setName("child", 9);
mem.addChild()->setName("child", 11);
mem.addChild()->setName("puffin", 2);
mem.addChild()->setName("child", 13);
mem.addChild()->setName("child", 15);
mem.addChild()->setName("child", 17);
mem.addChild()->setName("child", 19);
mem.addChild()->setName("child", 21);
BOOST_REQUIRE_EQUAL(227, mem.usage());
mem.compress();
BOOST_REQUIRE_EQUAL(227, mem.usage());
std::string after;
{
std::ostringstream ss;
mem.print(ss);
after = ss.str();
}
std::string expected("{\"root\":{\"memory\":1},\"subItems\":[{\"muffin\":"
"{\"memory\":4}},{\"child [*10]\":{\"memory\":220}},{\"puffin\":"
"{\"memory\":2}}]}\n");
LOG_DEBUG(<< after);
BOOST_REQUIRE_EQUAL(expected, after);
}
}
// This "test" highlights the way the std::string class behaves on each
// platform we support. Experience shows that methods like reserve(),
// clear() and operator=() don't always work the way the books suggest...
//
// There are no assertions, but the idea is that a developer should go
// through the output after switching to a new standard library
// implementation to ensure that the quirks of std::string are in that
// implementation are understood.
BOOST_AUTO_TEST_CASE(testStringBehaviour, *boost::unit_test::disabled()) {
LOG_INFO(<< "Size of std::string is " << sizeof(std::string));
std::string empty1;
std::string empty2;
LOG_INFO(<< "Two independently constructed empty strings have data at "
<< static_cast<const void*>(empty1.data()) << " and "
<< static_cast<const void*>(empty2.data()) << " and capacity "
<< empty1.capacity());
if (empty1.data() == empty2.data()) {
LOG_INFO(<< "All strings constructed empty probably share the same "
"representation on this platform");
}
std::string something1("something");
std::string something2(something1);
std::string something3;
something3 = something2;
LOG_INFO(<< "Non-empty string has data at "
<< static_cast<const void*>(something1.data()) << " length "
<< something1.length() << " and capacity " << something1.capacity());
LOG_INFO(<< "Copy constructed string has data at "
<< static_cast<const void*>(something2.data()) << " length "
<< something2.length() << " and capacity " << something2.capacity());
if (something2.data() == something1.data()) {
LOG_INFO(<< "Copy constructor probably has a copy-on-write "
"implementation on this platform");
}
LOG_INFO(<< "Assigned string has data at "
<< static_cast<const void*>(something3.data()) << " length "
<< something3.length() << " and capacity " << something3.capacity());
if (something3.data() == something2.data()) {
LOG_INFO(<< "Assignment operator probably has a copy-on-write "
"implementation on this platform");
}
something1.clear();
LOG_INFO(<< "Cleared string that was copied to two others has data at "
<< static_cast<const void*>(something1.data()) << " length "
<< something1.length() << " and capacity " << something1.capacity());
if (something1.data() == empty1.data()) {
LOG_INFO(<< "Cleared strings revert to shared empty representation on "
"this platform");
}
something2 = empty2;
LOG_INFO(<< "String that was copied to another then assigned an empty string "
"has data at "
<< static_cast<const void*>(something2.data()) << " length "
<< something2.length() << " and capacity " << something2.capacity());
if (something2.data() == empty1.data()) {
LOG_INFO(<< "Strings that have an empty constructed string assigned to "
"them share the same representation as other empty "
"constructed strings on this platform");
}
std::string uncopied("uncopied");
LOG_INFO(<< "Non-empty uncopied string has data at "
<< static_cast<const void*>(uncopied.data()) << " length "
<< uncopied.length() << " and capacity " << uncopied.capacity());
uncopied.clear();
LOG_INFO(<< "Cleared uncopied string has data at "
<< static_cast<const void*>(uncopied.data()) << " length "
<< uncopied.length() << " and capacity " << uncopied.capacity());
std::string startSmall("small");
LOG_INFO(<< "Non-empty small string unchanged since construction has data at "
<< static_cast<const void*>(startSmall.data()) << " length "
<< startSmall.length() << " and capacity " << startSmall.capacity());
startSmall.reserve(100);
size_t capacity100(startSmall.capacity());
LOG_INFO(<< "Small string after reserving 100 bytes has data at "
<< static_cast<const void*>(startSmall.data()) << " length "
<< startSmall.length() << " and capacity " << startSmall.capacity());
startSmall.reserve(10);
LOG_INFO(<< "Small string after reserving 10 bytes has data at "
<< static_cast<const void*>(startSmall.data()) << " length "
<< startSmall.length() << " and capacity " << startSmall.capacity());
if (startSmall.capacity() < capacity100) {
LOG_INFO(<< "On this platform reservations can reduce string capacity");
}
// We have to test clearing with a size/capacity that won't get confused by
// the short string optimisation (if it's being used)
std::string startLong("this_string_is_longer_than_one_that_will_take_advantage_of_the_small_string_optimisation");
LOG_INFO(<< "Long string after initial construction has data at "
<< static_cast<const void*>(startLong.data()) << " length "
<< startLong.length() << " and capacity " << startLong.capacity());
startLong.reserve(10000);
size_t capacity10000(startLong.capacity());
LOG_INFO(<< "Long string after reserving 10000 bytes has data at "
<< static_cast<const void*>(startLong.data()) << " length "
<< startLong.length() << " and capacity " << startLong.capacity());
startLong.clear();
LOG_INFO(<< "Long string after clearing has data at "
<< static_cast<const void*>(startLong.data()) << " length "
<< startLong.length() << " and capacity " << startLong.capacity());
if (startLong.capacity() < capacity10000) {
LOG_INFO(<< "On this platform clearing can reduce string capacity");
}
using TSizeVec = std::vector<size_t>;
std::string grower;
TSizeVec capacities(1, grower.capacity());
for (size_t count = 0; count < 50000; ++count) {
grower += 'x';
if (grower.capacity() != capacities.back()) {
capacities.push_back(grower.capacity());
}
}
LOG_INFO(<< "Capacities during growth from 0 to 50000 characters are: " << capacities);
std::string toBeShrunk(100, 'a');
toBeShrunk = "a lot smaller than it was";
size_t preShrinkCapacity(toBeShrunk.capacity());
LOG_INFO(<< "String to be shrunk has starting size " << toBeShrunk.size()
<< " and capacity " << preShrinkCapacity);
std::string(toBeShrunk).swap(toBeShrunk);
size_t postShrinkCapacity(toBeShrunk.capacity());
LOG_INFO(<< "String to be shrunk has post-shrink size " << toBeShrunk.size()
<< " and capacity " << postShrinkCapacity);
LOG_INFO(<< "The swap() trick to reduce capacity "
<< ((postShrinkCapacity < preShrinkCapacity) ? "works" : "DOESN'T WORK!"));
}
BOOST_AUTO_TEST_CASE(testStringMemory) {
using TAllocator = CTrackingAllocator<char>;
using TString = std::basic_string<char, std::char_traits<char>, TAllocator>;
for (std::size_t i = 0; i < 1500; ++i) {
BOOST_REQUIRE_EQUAL(0, TAllocator::usage());
TString trackingString;
std::string normalString;
for (std::size_t j = 0; j < i; ++j) {
trackingString.push_back(static_cast<char>('a' + j));
normalString.push_back(static_cast<char>('a' + j));
}
LOG_TRACE(<< "String size " << core::memory::dynamicSize(normalString)
<< ", allocated " << TAllocator::usage());
BOOST_REQUIRE_EQUAL(core::memory::dynamicSize(normalString), TAllocator::usage());
}
}
BOOST_AUTO_TEST_CASE(testStringClear) {
using TAllocator = CTrackingAllocator<char>;
using TString = std::basic_string<char, std::char_traits<char>, TAllocator>;
TString empty;
TString something1("something");
TString something2(something1);
TString something3;
something3 = something1;
std::size_t usage3Copies = TAllocator::usage();
something1.clear();
// If the following assertion fails after a standard library upgrade then
// the logic in include/core/CMemory.h needs changing as well as this test
BOOST_REQUIRE_EQUAL(usage3Copies, TAllocator::usage());
}
BOOST_AUTO_TEST_CASE(testSharedPointer) {
using TIntVecPtr = std::shared_ptr<TIntVec>;
using TIntVecPtrVec = std::vector<TIntVecPtr>;
using TStrPtr = std::shared_ptr<std::string>;
using TStrPtrVec = std::vector<TStrPtr>;
TStrPtrVec strings;
TIntVecPtrVec vec1;
TIntVecPtrVec vec2;
vec1.push_back(TIntVecPtr(new TIntVec(20, 555)));
vec1.push_back(TIntVecPtr(new TIntVec(30, 44)));
vec1.push_back(vec1[0]);
vec1.push_back(TIntVecPtr(new TIntVec(40, 22)));
vec1.push_back(vec1[1]);
vec2.push_back(vec1[3]);
vec2.push_back(vec1[1]);
vec2.push_back(vec1[0]);
vec2.push_back(vec2[1]);
vec2.push_back(vec2[2]);
LOG_DEBUG(<< "shared_ptr size: " << sizeof(TIntVecPtr));
LOG_DEBUG(<< "IntVec size: " << sizeof(TIntVec));
LOG_DEBUG(<< "int size: " << sizeof(int));
LOG_DEBUG(<< "vec1 size: " << core::memory::dynamicSize(vec1));
LOG_DEBUG(<< "vec2 size: " << core::memory::dynamicSize(vec2));
// shared_ptr size is 16
// intvec size is 24
// int size is 4
// x1 vector size: 24 + 20 * 4 = 104
// x2 vector size: 24 + 30 * 4 = 144
// x3 vector size: 24 + 40 * 4 = 184
// What we should have on 64-bit OS X:
// vec1: 8 (capacity) * 16 (shared_ptr element size) + 104 + 144 + 184
// vec2: 8 (capacity) * 16 (shared_ptr element size)
// = 688
std::size_t expectedSize =
vec1.capacity() * sizeof(TIntVecPtr) + vec2.capacity() * sizeof(TIntVecPtr) +
3 * (sizeof(long) + sizeof(TIntVec)) +
(vec1[0]->capacity() + vec1[1]->capacity() + vec1[3]->capacity()) * sizeof(int);
LOG_DEBUG(<< "Expected: " << expectedSize << ", actual: "
<< (core::memory::dynamicSize(vec1) + core::memory::dynamicSize(vec2)));
BOOST_REQUIRE_EQUAL(expectedSize, core::memory::dynamicSize(vec1) +
core::memory::dynamicSize(vec2));
TStrPtrVec svec1;
svec1.push_back(TStrPtr(new std::string("This is a string")));
svec1.push_back(TStrPtr(new std::string(
"Here is some more string data, a little longer than the previous one")));
svec1.push_back(TStrPtr(new std::string("An uninteresting string, this one!")));
TStrPtrVec svec2;
svec2.push_back(TStrPtr());
svec2.push_back(TStrPtr());
svec2.push_back(TStrPtr());
long stringSizeBefore = core::memory::dynamicSize(svec1) +
core::memory::dynamicSize(svec2);
svec2[0] = svec1[2];
svec2[1] = svec1[0];
svec2[2] = svec1[1];
long stringSizeAfter = core::memory::dynamicSize(svec1) +
core::memory::dynamicSize(svec2);
BOOST_REQUIRE_EQUAL(core::memory::dynamicSize(svec1), core::memory::dynamicSize(svec2));
// Allow for integer rounding off by 1 for each string
BOOST_TEST_REQUIRE(std::abs(stringSizeBefore - stringSizeAfter) < 4);
}
BOOST_AUTO_TEST_CASE(testRawPointer) {
std::string* strPtr = nullptr;
BOOST_REQUIRE_EQUAL(0, core::memory::dynamicSize(strPtr));
std::string foo = "abcdefghijklmnopqrstuvwxyz";
std::size_t fooMem = core::memory::dynamicSize(foo);
// We will not normally have a raw pointer on stack memory,
// but we do so here for testing purposes.
strPtr = &foo;
BOOST_REQUIRE_EQUAL(fooMem + sizeof(std::string), core::memory::dynamicSize(strPtr));
}
BOOST_AUTO_TEST_CASE(testSmallVector) {
using TSizeVec = std::vector<std::size_t>;
using TDouble1Vec = core::CSmallVector<double, 2>;
using TDouble6Vec = core::CSmallVector<double, 6>;
using TDouble9Vec = core::CSmallVector<double, 8>;
test::CRandomNumbers test;
TSizeVec sizes;
test.generateUniformSamples(0, 12, 100, sizes);
for (auto size : sizes) {
TDouble1Vec vec1(size);
TDouble6Vec vec2(size);
TDouble9Vec vec3(size);
TSizeVec memory{core::memory::dynamicSize(vec1), core::memory::dynamicSize(vec2),
core::memory::dynamicSize(vec3)};
// These assertions hold because the vectors never shrink
if (size <= 2) {
BOOST_TEST_REQUIRE(memory[0] == 0);
}
BOOST_REQUIRE(memory[0] == 0 || memory[0] == vec1.capacity() * sizeof(double));
if (size <= 6) {
BOOST_TEST_REQUIRE(memory[1] == 0);
}
BOOST_REQUIRE(memory[1] == 0 || memory[1] == vec2.capacity() * sizeof(double));
if (size <= 8) {
BOOST_TEST_REQUIRE(memory[2] == 0);
}
BOOST_REQUIRE(memory[2] == 0 || memory[2] == vec3.capacity() * sizeof(double));
}
// Test growing and shrinking
TDouble6Vec growShrink;
std::size_t extraMem{core::memory::dynamicSize(growShrink)};
BOOST_REQUIRE_EQUAL(0, extraMem);
growShrink.resize(6);
extraMem = core::memory::dynamicSize(growShrink);
BOOST_REQUIRE_EQUAL(0, extraMem);
growShrink.resize(10);
extraMem = core::memory::dynamicSize(growShrink);
BOOST_TEST_REQUIRE(extraMem > 0);
growShrink.clear();
extraMem = core::memory::dynamicSize(growShrink);
BOOST_TEST_REQUIRE(extraMem > 0);
growShrink.shrink_to_fit();
extraMem = core::memory::dynamicSize(growShrink);
BOOST_REQUIRE_EQUAL(0, extraMem);
growShrink.push_back(1.7);
extraMem = core::memory::dynamicSize(growShrink);
// Interestingly we used to assert extraMem > 0 here as it used to be the case
// that once a boost::small_vector had switched
// off of internal storage it would NEVER go back to internal storage.
// Arguably that was a bug, and this assertion started failing after
// upgrading Boost to 1.86.0, meaning that boost::small_vector has been improved.
BOOST_TEST_REQUIRE(extraMem >= 0); // Change to `==` once upgraded to Boost 1.86 on all platforms
}
BOOST_AUTO_TEST_CASE(testAlignedVector) {
using TDoubleVec = std::vector<double>;
using TAlignedDoubleVec = std::vector<double, core::CAlignedAllocator<double>>;
TDoubleVec vector{10.0, 11.0, 12.0, 13.0, 14.0,
15.0, 16.0, 17.0, 18.0, 19.0};
TAlignedDoubleVec alignedVector{10.0, 11.0, 12.0, 13.0, 14.0,
15.0, 16.0, 17.0, 18.0, 19.0};
LOG_DEBUG(<< "TDoubleVec usage = " << core::memory::dynamicSize(vector));
LOG_DEBUG(<< "TAlignedDoubleVec usage = " << core::memory::dynamicSize(alignedVector));
BOOST_REQUIRE_EQUAL(core::memory::dynamicSize(vector),
core::memory::dynamicSize(alignedVector));
core::CMemoryUsage memoryUsage;
memoryUsage.setName("test", 0);
core::memory_debug::dynamicSize("TAlignedDoubleVec", vector, memoryUsage.addChild());
std::ostringstream ss;
memoryUsage.print(ss);
LOG_DEBUG(<< "TAlignedDoubleVec usage debug = " << ss.str());
BOOST_REQUIRE_EQUAL(core::memory::dynamicSize(vector), memoryUsage.usage());
}
BOOST_AUTO_TEST_SUITE_END()