644 lines
18 KiB
C++
644 lines
18 KiB
C++
/**
|
|
* PANDA 3D SOFTWARE
|
|
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
|
*
|
|
* All use of this software is subject to the terms of the revised BSD
|
|
* license. You should have received a copy of this license along
|
|
* with this source code in a file named "LICENSE."
|
|
*
|
|
* @file adaptiveLru.cxx
|
|
* @author drose
|
|
* @date 2008-09-03
|
|
*/
|
|
|
|
#include "adaptiveLru.h"
|
|
#include "config_gobj.h"
|
|
#include "clockObject.h"
|
|
#include "indent.h"
|
|
|
|
static const int HIGH_PRIORITY_SCALE = 4;
|
|
static const int LOW_PRIORITY_RANGE = 25;
|
|
|
|
/**
|
|
*
|
|
*/
|
|
AdaptiveLru::
|
|
AdaptiveLru(const string &name, size_t max_size) :
|
|
Namable(name)
|
|
{
|
|
_total_size = 0;
|
|
_max_size = max_size;
|
|
|
|
_current_frame_identifier = 0;
|
|
_weight = adaptive_lru_weight;
|
|
_max_updates_per_frame = adaptive_lru_max_updates_per_frame;
|
|
|
|
// Initialize our list heads to empty.
|
|
_static_list._next = &_static_list;
|
|
_static_list._prev = &_static_list;
|
|
|
|
int index;
|
|
for (index = 0; index < LPP_TotalPriorities; ++index) {
|
|
_page_array[index]._next = &_page_array[index];
|
|
_page_array[index]._prev = &_page_array[index];
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
AdaptiveLru::
|
|
~AdaptiveLru() {
|
|
#ifndef NDEBUG
|
|
// We're shutting down. Force-remove everything remaining, but don't
|
|
// explicitly evict it (that would force vertex buffers to write themselves
|
|
// to disk unnecessarily).
|
|
|
|
while (_static_list._next != &_static_list) {
|
|
nassertv(_static_list._next != (LinkedListNode *)NULL);
|
|
AdaptiveLruPage *page = (AdaptiveLruPage *)(AdaptiveLruPageStaticList *)_static_list._next;
|
|
|
|
page->_lru = NULL;
|
|
((AdaptiveLruPageDynamicList *)page)->remove_from_list();
|
|
((AdaptiveLruPageStaticList *)page)->remove_from_list();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* This only updates a number of pages up to the specified maximum_updates.
|
|
* Assumes the lock is held.
|
|
*/
|
|
void AdaptiveLru::
|
|
do_partial_lru_update(int num_updates) {
|
|
// Iterate sequentially through the static list of pages. As we process
|
|
// each page, pop it and push it back on the tail. Stop when we have
|
|
// processed num_updates, or come back to the starting one.
|
|
|
|
AdaptiveLruPageStaticList *start_node = (AdaptiveLruPageStaticList *)_static_list._next;
|
|
if (start_node == &_static_list) {
|
|
// List is empty.
|
|
return;
|
|
}
|
|
|
|
AdaptiveLruPageStaticList *node = start_node;
|
|
do {
|
|
AdaptiveLruPageStaticList *next = (AdaptiveLruPageStaticList *)node->_next;
|
|
if (--num_updates <= 0) {
|
|
return;
|
|
}
|
|
|
|
update_page((AdaptiveLruPage *)node);
|
|
node->remove_from_list();
|
|
node->insert_before(&_static_list);
|
|
node = next;
|
|
} while (node != start_node && node != &_static_list);
|
|
}
|
|
|
|
/**
|
|
* This updates the page's average utilization. Priority LPP_New is
|
|
* considered to be average usage of 1.0 (which means the page is used once
|
|
* per frame on average). Priorities < LPP_New are for pages used more than
|
|
* once per frame and Priorities > LPP_New are for pages used less than once
|
|
* per frame.
|
|
*
|
|
* Assumes the lock is held.
|
|
*/
|
|
void AdaptiveLru::
|
|
update_page(AdaptiveLruPage *page) {
|
|
int target_priority = page->_priority;
|
|
unsigned int lifetime_frames = _current_frame_identifier - page->_first_frame_identifier;
|
|
if (lifetime_frames > 0) {
|
|
if (page->_update_frame_identifier) {
|
|
unsigned int update_frames;
|
|
|
|
update_frames = (_current_frame_identifier - page->_update_frame_identifier);
|
|
if (update_frames > 0) {
|
|
if (page->_update_total_usage > 0) {
|
|
PN_stdfloat update_average_frame_utilization =
|
|
(PN_stdfloat) (page->_update_total_usage) / (PN_stdfloat)update_frames;
|
|
|
|
page->_average_frame_utilization =
|
|
calculate_exponential_moving_average(update_average_frame_utilization,
|
|
page->_average_frame_utilization);
|
|
} else {
|
|
page->_average_frame_utilization *= 1.0f - _weight;
|
|
}
|
|
|
|
target_priority = page->_priority;
|
|
if (page->_average_frame_utilization >= 1.0f) {
|
|
int integer_average_frame_utilization;
|
|
|
|
integer_average_frame_utilization =
|
|
(int) ((page->_average_frame_utilization - 1.0f) *
|
|
(PN_stdfloat) HIGH_PRIORITY_SCALE);
|
|
if (integer_average_frame_utilization >= LPP_New) {
|
|
integer_average_frame_utilization = LPP_New;
|
|
}
|
|
integer_average_frame_utilization = LPP_New -
|
|
integer_average_frame_utilization;
|
|
target_priority = integer_average_frame_utilization;
|
|
} else {
|
|
int integer_average_frame_utilization;
|
|
|
|
integer_average_frame_utilization = (int)
|
|
(page->_average_frame_utilization *
|
|
(PN_stdfloat) LOW_PRIORITY_RANGE);
|
|
integer_average_frame_utilization = LOW_PRIORITY_RANGE -
|
|
integer_average_frame_utilization;
|
|
target_priority = LPP_New + integer_average_frame_utilization;
|
|
}
|
|
}
|
|
}
|
|
|
|
page->_update_frame_identifier = _current_frame_identifier;
|
|
page->_update_total_usage = 0;
|
|
}
|
|
|
|
if (target_priority != page->_priority) {
|
|
page->_priority = min(max(target_priority, 0), LPP_TotalPriorities - 1);
|
|
((AdaptiveLruPageDynamicList *)page)->remove_from_list();
|
|
((AdaptiveLruPageDynamicList *)page)->insert_before(&_page_array[page->_priority]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds the page to the LRU for the first time, or marks it recently-accessed
|
|
* if it has already been added.
|
|
*
|
|
* If lru is NULL, it means to remove this page from its LRU.
|
|
*/
|
|
void AdaptiveLruPage::
|
|
enqueue_lru(AdaptiveLru *lru) {
|
|
if (lru != _lru && _lru != (AdaptiveLru *)NULL) {
|
|
// It was previously on a different LRU. Remove it first.
|
|
_lru->do_remove_page(this);
|
|
_lru = NULL;
|
|
}
|
|
|
|
if (lru == _lru) {
|
|
if (_lru != (AdaptiveLru *)NULL) {
|
|
// It's already on this LRU. Access it.
|
|
_lru->do_access_page(this);
|
|
}
|
|
} else {
|
|
nassertv(lru != (AdaptiveLru *)NULL);
|
|
// Add it to a new LRU.
|
|
_lru = lru;
|
|
|
|
_priority = AdaptiveLru::LPP_New;
|
|
_first_frame_identifier = _lru->_current_frame_identifier;
|
|
_current_frame_identifier = _lru->_current_frame_identifier;
|
|
_lru->do_add_page(this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the total size of the pages that were enqueued since the last call
|
|
* to begin_epoch().
|
|
*/
|
|
size_t AdaptiveLru::
|
|
count_active_size() const {
|
|
size_t counted_size = 0;
|
|
|
|
AdaptiveLruPageStaticList *node = (AdaptiveLruPageStaticList *)_static_list._next;
|
|
while (node != &_static_list) {
|
|
AdaptiveLruPage *page = (AdaptiveLruPage *)node;
|
|
if (page->_current_frame_identifier + 1 >= _current_frame_identifier) {
|
|
counted_size += page->_lru_size;
|
|
}
|
|
node = (AdaptiveLruPageStaticList *)node->_next;
|
|
}
|
|
|
|
return counted_size;
|
|
}
|
|
|
|
/**
|
|
* Marks the end of the previous epoch and the beginning of the next one.
|
|
* This will evict any objects that are pending eviction, and also update any
|
|
* internal bookkeeping.
|
|
*/
|
|
void AdaptiveLru::
|
|
begin_epoch() {
|
|
LightMutexHolder holder(_lock);
|
|
do_partial_lru_update(_max_updates_per_frame);
|
|
if (_total_size > _max_size) {
|
|
do_evict_to(_max_size, false);
|
|
}
|
|
|
|
_current_frame_identifier = ClockObject::get_global_clock()->get_frame_count();
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void AdaptiveLru::
|
|
output(ostream &out) const {
|
|
LightMutexHolder holder(_lock);
|
|
out << "AdaptiveLru " << get_name()
|
|
<< ", " << _total_size << " of " << _max_size;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void AdaptiveLru::
|
|
write(ostream &out, int indent_level) const {
|
|
indent(out, indent_level) << *this << ":\n";
|
|
|
|
// We write out the list backwards. Things we write out first are the
|
|
// freshest in the LRU. Things at the end of the list will be the next to
|
|
// be evicted.
|
|
|
|
LightMutexHolder holder(_lock);
|
|
|
|
int index;
|
|
for (index = 0; index < LPP_TotalPriorities; ++index) {
|
|
AdaptiveLruPageDynamicList *node = (AdaptiveLruPageDynamicList *)_page_array[index]._prev;
|
|
if (node != &_page_array[index]) {
|
|
indent(out, indent_level + 2) << "Priority " << index << ":\n";
|
|
while (node != &_page_array[index]) {
|
|
AdaptiveLruPage *page = (AdaptiveLruPage *)node;
|
|
indent(out, indent_level + 4) << *page;
|
|
|
|
if (page->_current_frame_identifier + 1 >= _current_frame_identifier) {
|
|
out << " (active)";
|
|
}
|
|
out << "\n";
|
|
|
|
node = (AdaptiveLruPageDynamicList *)node->_prev;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
((AdaptiveLru *)this)->do_validate();
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Adds a new page the the LRU.
|
|
*/
|
|
void AdaptiveLru::
|
|
do_add_page(AdaptiveLruPage *page) {
|
|
nassertv(page != (AdaptiveLruPage *)NULL && page->_lru == this);
|
|
LightMutexHolder holder(_lock);
|
|
|
|
_total_size += page->_lru_size;
|
|
((AdaptiveLruPageDynamicList *)page)->insert_before(&_page_array[page->_priority]);
|
|
((AdaptiveLruPageStaticList *)page)->insert_before(&_static_list);
|
|
}
|
|
|
|
/**
|
|
* Removes a page from the LRU.
|
|
*/
|
|
void AdaptiveLru::
|
|
do_remove_page(AdaptiveLruPage *page) {
|
|
nassertv(page != (AdaptiveLruPage *)NULL && page->_lru == this);
|
|
LightMutexHolder holder(_lock);
|
|
|
|
_total_size -= page->_lru_size;
|
|
((AdaptiveLruPageDynamicList *)page)->remove_from_list();
|
|
((AdaptiveLruPageStaticList *)page)->remove_from_list();
|
|
}
|
|
|
|
/**
|
|
* Marks a page accessed.
|
|
*/
|
|
void AdaptiveLru::
|
|
do_access_page(AdaptiveLruPage *page) {
|
|
nassertv(page != (AdaptiveLruPage *)NULL && page->_lru == this);
|
|
LightMutexHolder holder(_lock);
|
|
|
|
if (page->_current_frame_identifier == _current_frame_identifier) {
|
|
// This is the second or more time this page is accessed this frame.
|
|
++(page->_current_frame_usage);
|
|
|
|
} else {
|
|
// This page has not yet been accessed this frame. Update it.
|
|
page->_current_frame_identifier = _current_frame_identifier;
|
|
page->_last_frame_usage = page->_current_frame_usage;
|
|
page->_current_frame_usage = 1;
|
|
}
|
|
|
|
// Move it to the tail of its priority list.
|
|
((AdaptiveLruPageDynamicList *)page)->remove_from_list();
|
|
((AdaptiveLruPageDynamicList *)page)->insert_before(&_page_array[page->_priority]);
|
|
|
|
++(page->_update_total_usage);
|
|
}
|
|
|
|
/**
|
|
* Evicts pages until the LRU is within the indicated size. Assumes the lock
|
|
* is already held. If hard_evict is false, does not evict "active" pages
|
|
* that were added within this epoch.
|
|
*/
|
|
void AdaptiveLru::
|
|
do_evict_to(size_t target_size, bool hard_evict) {
|
|
int attempts;
|
|
|
|
attempts = 0;
|
|
do {
|
|
// page out lower priority pages first
|
|
int index;
|
|
for (index = LPP_TotalPriorities - 1; index >= 0; index--) {
|
|
|
|
// Store the current end of the list. If pages re-enqueue themselves
|
|
// during this traversal, we don't want to visit them twice.
|
|
AdaptiveLruPageDynamicList *end = (AdaptiveLruPageDynamicList *)_page_array[index]._prev;
|
|
|
|
AdaptiveLruPageDynamicList *node = (AdaptiveLruPageDynamicList *)_page_array[index]._next;
|
|
|
|
while (node != &_page_array[index]) {
|
|
AdaptiveLruPageDynamicList *next = (AdaptiveLruPageDynamicList *)node->_next;
|
|
AdaptiveLruPage *page = (AdaptiveLruPage *)node;
|
|
|
|
if (attempts == 0 &&
|
|
(page->_current_frame_identifier + 1 >= _current_frame_identifier)) {
|
|
// avoid swapping out pages used in the current and last frame on
|
|
// the first attempt
|
|
|
|
} else {
|
|
// We must release the lock while we call evict_lru().
|
|
_lock.unlock();
|
|
page->evict_lru();
|
|
_lock.lock();
|
|
|
|
if (_total_size <= target_size) {
|
|
// We've evicted enough to satisfy our target.
|
|
return;
|
|
}
|
|
}
|
|
if (node == end) {
|
|
// We've reached the former end of the list. Stop here; everything
|
|
// after has been re-queued.
|
|
break;
|
|
}
|
|
node = next;
|
|
}
|
|
}
|
|
attempts++;
|
|
} while (hard_evict && attempts < 2);
|
|
}
|
|
|
|
/**
|
|
* Checks that the LRU is internally consistent. Assume the lock is already
|
|
* held.
|
|
*/
|
|
bool AdaptiveLru::
|
|
do_validate() {
|
|
bool okflag = true;
|
|
pset<AdaptiveLruPage *> pages;
|
|
|
|
// First, walk through the dynamic pages.
|
|
size_t counted_size = 0;
|
|
int index;
|
|
for (index = 0; index < LPP_TotalPriorities; ++index) {
|
|
AdaptiveLruPageDynamicList *node = (AdaptiveLruPageDynamicList *)_page_array[index]._next;
|
|
while (node != &_page_array[index]) {
|
|
AdaptiveLruPage *page = (AdaptiveLruPage *)node;
|
|
counted_size += page->_lru_size;
|
|
if (page->_priority != index) {
|
|
nout << "page " << page << " has priority " << page->_priority
|
|
<< " but is in queue " << index << "\n";
|
|
okflag = false;
|
|
}
|
|
|
|
bool inserted_ok = pages.insert(page).second;
|
|
if (!inserted_ok) {
|
|
nout << "page " << page << " appears more than once in the dynamic index\n";
|
|
okflag = false;
|
|
}
|
|
node = (AdaptiveLruPageDynamicList *)node->_next;
|
|
}
|
|
}
|
|
|
|
if (counted_size != _total_size) {
|
|
nout << "count " << counted_size << " bytes in dynamic index, but have " << _total_size << " on record\n";
|
|
okflag = false;
|
|
}
|
|
|
|
// Now, walk through the static pages.
|
|
counted_size = 0;
|
|
AdaptiveLruPageStaticList *node = (AdaptiveLruPageStaticList *)_static_list._next;
|
|
while (node != &_static_list) {
|
|
AdaptiveLruPage *page = (AdaptiveLruPage *)node;
|
|
counted_size += page->_lru_size;
|
|
|
|
if (pages.find(page) == pages.end()) {
|
|
nout << "page " << page << " appears in dynamic index, but not in static index (or multiple times in static index)\n";
|
|
okflag = false;
|
|
} else {
|
|
pages.erase(page);
|
|
}
|
|
node = (AdaptiveLruPageStaticList *)node->_next;
|
|
}
|
|
|
|
if (counted_size != _total_size) {
|
|
nout << "count " << counted_size << " bytes in static index, but have " << _total_size << " on record\n";
|
|
okflag = false;
|
|
}
|
|
|
|
return okflag;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
AdaptiveLruPage::
|
|
AdaptiveLruPage(size_t lru_size) :
|
|
_lru(NULL),
|
|
_lru_size(lru_size),
|
|
_priority(0),
|
|
_first_frame_identifier(0),
|
|
_current_frame_identifier(0),
|
|
_update_frame_identifier(0),
|
|
_current_frame_usage(0),
|
|
_last_frame_usage(0),
|
|
_total_usage(0),
|
|
_update_total_usage(0),
|
|
_average_frame_utilization(1.0f)
|
|
{
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
AdaptiveLruPage::
|
|
AdaptiveLruPage(const AdaptiveLruPage ©) :
|
|
_lru(NULL),
|
|
_lru_size(copy._lru_size),
|
|
_priority(0),
|
|
_first_frame_identifier(0),
|
|
_current_frame_identifier(0),
|
|
_update_frame_identifier(0),
|
|
_current_frame_usage(0),
|
|
_last_frame_usage(0),
|
|
_total_usage(0),
|
|
_update_total_usage(0),
|
|
_average_frame_utilization(1.0f)
|
|
{
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void AdaptiveLruPage::
|
|
operator = (const AdaptiveLruPage ©) {
|
|
set_lru_size(copy.get_lru_size());
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
AdaptiveLruPage::
|
|
~AdaptiveLruPage() {
|
|
if (_lru != NULL) {
|
|
dequeue_lru();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Evicts the page from the LRU. Called internally when the LRU determines
|
|
* that it is full. May also be called externally when necessary to
|
|
* explicitly evict the page.
|
|
*
|
|
* It is legal for this method to either evict the page as requested, do
|
|
* nothing (in which case the eviction will be requested again at the next
|
|
* epoch), or requeue itself on the tail of the queue (in which case the
|
|
* eviction will be requested again much later).
|
|
*/
|
|
void AdaptiveLruPage::
|
|
evict_lru() {
|
|
dequeue_lru();
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void AdaptiveLruPage::
|
|
output(ostream &out) const {
|
|
out << "page " << this << ", " << _lru_size;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void AdaptiveLruPage::
|
|
write(ostream &out, int indent_level) const {
|
|
indent(out, indent_level) << *this << "\n";
|
|
}
|
|
|
|
/**
|
|
* Returns the number of frames since the page was first added to its LRU.
|
|
* Returns 0 if it does not have an LRU.
|
|
*/
|
|
unsigned int AdaptiveLruPage::
|
|
get_num_frames() const {
|
|
if (_lru == (AdaptiveLru *)NULL) {
|
|
return 0;
|
|
}
|
|
return _lru->_current_frame_identifier - _first_frame_identifier;
|
|
}
|
|
|
|
/**
|
|
* Returns the number of frames since the page was last accessed on its LRU.
|
|
* Returns 0 if it does not have an LRU.
|
|
*/
|
|
unsigned int AdaptiveLruPage::
|
|
get_num_inactive_frames() const {
|
|
if (_lru == (AdaptiveLru *)NULL) {
|
|
return 0;
|
|
}
|
|
return _lru->_current_frame_identifier - _current_frame_identifier;
|
|
}
|
|
|
|
|
|
#if 0
|
|
|
|
/**
|
|
* Unit test function for Lru.
|
|
*/
|
|
void
|
|
test_adaptive_lru() {
|
|
int maximum_memory = 3000000;
|
|
AdaptiveLru *lru = new AdaptiveLru("test", maximum_memory);
|
|
|
|
AdaptiveLruPage *lru_page_0;
|
|
AdaptiveLruPage *lru_page_1;
|
|
AdaptiveLruPage *lru_page_2;
|
|
AdaptiveLruPage *lru_page_3;
|
|
AdaptiveLruPage *lru_page_4;
|
|
AdaptiveLruPage *lru_page_5;
|
|
|
|
lru_page_0 = new AdaptiveLruPage(1000000);
|
|
cerr << "created lru_page_0: " << lru_page_0 << "\n";
|
|
lru_page_0->enqueue_lru(lru);
|
|
|
|
lru_page_1 = new AdaptiveLruPage(1000000);
|
|
cerr << "created lru_page_1: " << lru_page_1 << "\n";
|
|
lru_page_1->enqueue_lru(lru);
|
|
|
|
lru_page_2 = new AdaptiveLruPage(1000000);
|
|
cerr << "created lru_page_2: " << lru_page_2 << "\n";
|
|
lru_page_2->enqueue_lru(lru);
|
|
|
|
lru_page_3 = new AdaptiveLruPage(1000000);
|
|
cerr << "created lru_page_3: " << lru_page_3 << "\n";
|
|
lru_page_3->enqueue_lru(lru);
|
|
|
|
lru_page_4 = new AdaptiveLruPage(1000000);
|
|
cerr << "created lru_page_4: " << lru_page_4 << "\n";
|
|
lru_page_4->enqueue_lru(lru);
|
|
|
|
lru_page_5 = new AdaptiveLruPage(1000000);
|
|
cerr << "created lru_page_5: " << lru_page_5 << "\n";
|
|
lru_page_5->enqueue_lru(lru);
|
|
|
|
int total_frames = 300;
|
|
int index;
|
|
for (index = 0; index < total_frames; index++) {
|
|
cerr << "FRAME " << index << "\n";
|
|
|
|
lru->begin_epoch();
|
|
|
|
if (index <= 5) {
|
|
lru_page_0->mark_used_lru(lru);
|
|
}
|
|
|
|
lru_page_1->mark_used_lru(lru);
|
|
lru_page_1->mark_used_lru(lru);
|
|
|
|
if (index & 0x01) {
|
|
lru_page_2->mark_used_lru(lru);
|
|
}
|
|
|
|
if ((index % 10) == 0) {
|
|
lru_page_3->mark_used_lru(lru);
|
|
}
|
|
|
|
if (index >= 100) {
|
|
lru_page_4->mark_used_lru(lru);
|
|
}
|
|
|
|
if (index >= 200) {
|
|
lru_page_5->mark_used_lru(lru);
|
|
}
|
|
|
|
if (!lru->validate()) {
|
|
cerr << "Failed validation\n";
|
|
break;
|
|
}
|
|
}
|
|
|
|
delete lru;
|
|
delete lru_page_0;
|
|
delete lru_page_1;
|
|
delete lru_page_2;
|
|
delete lru_page_3;
|
|
delete lru_page_4;
|
|
delete lru_page_5;
|
|
}
|
|
|
|
#endif // test_adaptive_lru
|