diff --git a/doc/release-notes.rst b/doc/release-notes.rst index 9ec9b23604..758fd83526 100644 --- a/doc/release-notes.rst +++ b/doc/release-notes.rst @@ -6,6 +6,10 @@ Release notes This release is scheduled for end February 2024. You can already check out a `preview `_. +.. rubric:: Production planning + +- | Reduced memory consumption and improved performance. + .. rubric:: Demand forecasting - | When changing the forecast method in the forecast editor or inventory planning screen, diff --git a/include/frepple/model.h b/include/frepple/model.h index 74055130ac..66c98ebf31 100644 --- a/include/frepple/model.h +++ b/include/frepple/model.h @@ -9832,19 +9832,18 @@ class PeggingIterator : public NonCopyable, public Object { short level; Duration gap; - // Constructor + state(){}; + state(const OperationPlan* op, double q, double o, short l, Duration g) : opplan(op), quantity(q), offset(o), level(l), gap(g){}; - state(state&& other) + state(const state& other) : opplan(other.opplan), quantity(other.quantity), offset(other.offset), level(other.level), gap(other.gap){}; - state(const state& o) = delete; - state& operator=(const state& o) = delete; // Comparison operator bool operator<(const state& other) const { @@ -9854,15 +9853,15 @@ class PeggingIterator : public NonCopyable, public Object { return other.opplan->getStart() < opplan->getStart(); } }; - typedef vector statestack; /* Auxilary function to make recursive code possible. */ void followPegging(const OperationPlan*, double, double, short); - /* Store a list of all operations still to peg. */ - statestack states; + static thread_local MemoryPool peggingpool; - deque states_sorted; + /* Store a list of all operations still to peg. */ + MemoryPool::MemoryObjectList states; + MemoryPool::MemoryObjectList states_sorted; /* Follow the pegging upstream or downstream. */ bool downstream; diff --git a/include/frepple/utils.h b/include/frepple/utils.h index 4040c5bcc4..bcefab5298 100644 --- a/include/frepple/utils.h +++ b/include/frepple/utils.h @@ -194,6 +194,10 @@ template class MetaFieldShort; template class MetaFieldCommand; +template +class MemoryPage; +template +class MemoryPagePool; // Include the list of predefined tags #include "frepple/tags.h" @@ -7237,6 +7241,292 @@ class RecentlyUsed { } }; +/* Memory allocation for small objects in pages. + * Properties: + * - The pool object needs to be declared as thread_local to assure things work + * in a multithreaded environment. Each thread maintains its own pool of + * objects. + * - Each memory page allocates 2MB of memory. + * - Pages in the pool are automatically added when needed. + * - Pages in the pool are automatically destructed when the thread exits. + * We assume that all pool objects are then no longer referenced and can be + * deleted. + */ +template +class MemoryPool { + private: + class MemoryObject { + public: + T val; + MemoryObject* prev; + MemoryObject* next; + }; + + public: + class MemoryObjectList { + private: + inline void append(MemoryObject* n) { + n->next = nullptr; + n->prev = last; + if (last) + last->next = n; + else + first = n; + last = n; + } + + public: + MemoryObject* first = nullptr; + MemoryObject* last = nullptr; + MemoryPool& pool; + + MemoryObjectList(MemoryPool& m) : pool(m) {} + + MemoryObjectList(const MemoryObjectList& other) : pool(other.pool) { + for (auto i = other.begin(); i != other.end(); ++i) insert(&*i); + } + + MemoryObjectList& operator=(const MemoryObjectList& other) { + while (first) { + auto tmp = first; + first = first->next; + pool.addToFree(tmp); + } + last = nullptr; + pool = other.pool; + for (auto i = other.begin(); i != other.end(); ++i) insert(&*i); + return *this; + } + + ~MemoryObjectList() { + while (first) { + auto tmp = first; + first = first->next; + pool.addToFree(tmp); + } + } + + T& back() const { return last->val; } + + T& front() const { return first->val; } + + bool empty() const { return first == nullptr; } + + void pop_front() { + if (first) { + auto tmp = first; + first = first->next; + if (first) + first->prev = nullptr; + else + last = nullptr; + pool.addToFree(tmp); + } + } + + void pop_back() { + if (last) { + auto tmp = last; + last = last->prev; + if (last) + last->next = nullptr; + else + first = nullptr; + pool.addToFree(tmp); + } + } + + void sort() { + // Bubble sort + bool ok; + do { + ok = true; + for (auto p = first; p && p->next; p = p->next) { + if (*p->next < *p) { + swap(p->val, p->next->val); + ok = false; + }; + } + } while (!ok); + } + + inline void insert(const T* t) { + auto n = pool.alloc(); + new (&(n->val)) T(*t); + append(n); + } + + template + inline void insert(T1& t1) { + auto n = pool.alloc(); + new (&(n->val)) T(t1); + append(n); + } + + template + inline void insert(T1 t1, T2 t2) { + auto n = pool.alloc(); + new (&(n->val)) T(t1, t2); + append(n); + } + + template + inline void insert(T1 t1, T2 t2, T3 t3) { + auto n = pool.alloc(); + new (&(n->val)) T(t1, t2, t3); + append(n); + } + + template + inline void insert(T1 t1, T2 t2, T3 t3, T4 t4) { + auto n = pool.alloc(); + new (&(n->val)) T(t1, t2, t3, t4); + append(n); + } + + template + inline void insert(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5) { + auto n = pool.alloc(); + new (&(n->val)) T(t1, t2, t3, t4, t5); + append(n); + } + + template + inline void insert(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6) { + auto n = pool.alloc(); + new (&(n->val)) T(t1, t2, t3, t4, t5, t6); + append(n); + } + + // void erase(T&) { + // for (auto p = first; p; p = p->next) + // if (p->msr == k) { + // // Unlink from the list + // if (p->prev) + // p->prev->next = p->next; + // else + // first = p->next; + // if (p->next) + // p->next->prev = p->prev; + // else + // last = p->prev; + + // // Add to free list + // p->addToFree(); + // return; + // } + //} + + // void MeasureList::erase(MeasureValue* p) { + // // Unlink from the list + // if (p->prev) + // p->prev->next = p->next; + // else + // first = p->next; + // if (p->next) + // p->next->prev = p->prev; + // else + // last = p->prev; + + // // Add to free list + // p->addToFree(); + // } + + class iterator { + private: + MemoryObject* ptr = nullptr; + + public: + iterator(MemoryObject* i) : ptr(i) {} + + iterator& operator++() { + if (ptr) ptr = ptr->next; + return *this; + } + + bool operator!=(const iterator& t) const { return ptr != t.ptr; } + + bool operator==(const iterator& t) const { return ptr == t.ptr; } + + T& operator*() { return ptr->val; } + + T* operator->() { return &(ptr->val); } + }; + + iterator begin() const { return iterator(first); } + + iterator end() const { return iterator(nullptr); } + }; + + private: + class MemoryPage { + public: + static const int DATA_PER_PAGE = 2 * 1024 * 1024 / sizeof(MemoryObject); + MemoryPage* next = nullptr; + MemoryPage* prev = nullptr; + MemoryObject data[DATA_PER_PAGE]; + + MemoryPage(MemoryPool& pool) : next(nullptr) { + // Insert into page list + if (pool.lastpage) { + prev = pool.lastpage; + pool.lastpage->next = this; + } else { + pool.firstpage = this; + prev = nullptr; + } + pool.lastpage = this; + + // Extend the list of free pairs + for (auto& v : data) pool.addToFree(&v); + } + }; + + MemoryPage* firstpage = nullptr; + MemoryPage* lastpage = nullptr; + MemoryObject* firstfree = nullptr; + MemoryObject* lastfree = nullptr; + + void addToFree(MemoryObject* o) { + o->prev = lastfree; + o->next = nullptr; + if (lastfree) + lastfree->next = o; + else + firstfree = o; + lastfree = o; + } + + MemoryObject* alloc() { + // Get a free pair + if (!firstfree) { + new MemoryPage(*this); + if (!firstfree) throw RuntimeException("No free memory"); + } + auto n = firstfree; + firstfree = firstfree->next; + if (firstfree) + firstfree->prev = nullptr; + else + lastfree = nullptr; + return n; + } + + public: + MemoryPool() {} + + ~MemoryPool() { + unsigned int cnt = 0; + while (firstpage) { + ++cnt; + auto tmp = firstpage; + firstpage = firstpage->next; + delete tmp; + } + } +}; + } // namespace utils } // namespace frepple diff --git a/src/model/pegging.cpp b/src/model/pegging.cpp index e2624856d1..5fad0e93b9 100644 --- a/src/model/pegging.cpp +++ b/src/model/pegging.cpp @@ -31,6 +31,8 @@ namespace frepple { const MetaCategory* PeggingIterator::metadata; const MetaCategory* PeggingDemandIterator::metadata; +thread_local MemoryPool PeggingIterator::peggingpool; + int PeggingIterator::initialize() { // Initialize the pegging metadata PeggingIterator::metadata = @@ -70,16 +72,15 @@ PeggingIterator& PeggingIterator::operator=(const PeggingIterator& c) { first = c.first; second_pass = c.second_pass; maxlevel = c.maxlevel; - for (auto& i : c.states) - states.emplace_back(i.opplan, i.quantity, i.offset, i.level, i.gap); - for (auto i = c.states_sorted.begin(); i != c.states_sorted.end(); ++i) - states_sorted.emplace_back(i->opplan, i->quantity, i->offset, i->level, - i->gap); + states = c.states; + states_sorted = c.states_sorted; return *this; } PeggingIterator::PeggingIterator(const Demand* d, short maxLvl) - : downstream(false), + : states(PeggingIterator::peggingpool), + states_sorted(PeggingIterator::peggingpool), + downstream(false), firstIteration(true), first(false), second_pass(false), @@ -108,8 +109,8 @@ PeggingIterator::PeggingIterator(const Demand* d, short maxLvl) } if (!found) // New element in sorted stack - states_sorted.emplace_back(curtop.opplan, curtop.quantity, curtop.offset, - curtop.level, curtop.gap); + states_sorted.insert(curtop.opplan, curtop.quantity, curtop.offset, + curtop.level, curtop.gap); if (downstream) ++*this; @@ -123,7 +124,9 @@ PeggingIterator::PeggingIterator(const Demand* d, short maxLvl) PeggingIterator::PeggingIterator(const OperationPlan* opplan, bool b, short maxlevel) - : downstream(b), + : states(PeggingIterator::peggingpool), + states_sorted(PeggingIterator::peggingpool), + downstream(b), firstIteration(true), first(false), second_pass(false), @@ -139,7 +142,9 @@ PeggingIterator::PeggingIterator(const OperationPlan* opplan, bool b, } PeggingIterator::PeggingIterator(const FlowPlan* fp, bool b) - : downstream(b), + : states(PeggingIterator::peggingpool), + states_sorted(PeggingIterator::peggingpool), + downstream(b), firstIteration(true), first(false), second_pass(false), @@ -155,7 +160,9 @@ PeggingIterator::PeggingIterator(const FlowPlan* fp, bool b) } PeggingIterator::PeggingIterator(LoadPlan* lp, bool b) - : downstream(b), + : states(PeggingIterator::peggingpool), + states_sorted(PeggingIterator::peggingpool), + downstream(b), firstIteration(true), first(false), second_pass(false), @@ -352,9 +359,9 @@ void PeggingIterator::updateStack(const OperationPlan* op, double qty, double o, if (qty < ROUNDING_ERROR) return; // Check for loops in the pegging - for (auto& e : states) { - if (e.opplan == op && abs(e.quantity - qty) < ROUNDING_ERROR && - abs(e.offset - o) < ROUNDING_ERROR) // We've been here before... + for (auto e = states.begin(); e != states.end(); ++e) { + if (e->opplan == op && abs(e->quantity - qty) < ROUNDING_ERROR && + abs(e->offset - o) < ROUNDING_ERROR) // We've been here before... return; } @@ -369,7 +376,7 @@ void PeggingIterator::updateStack(const OperationPlan* op, double qty, double o, first = false; } else // We need to create a new element on the stack - states.emplace_back(op, qty, o, lvl, gap); + states.insert(op, qty, o, lvl, gap); } PeggingDemandIterator::PeggingDemandIterator(const OperationPlan* opplan) {