From 67771fb75c23282e3375757bc9dc55d2ddc3a1b2 Mon Sep 17 00:00:00 2001 From: David Rose Date: Thu, 7 Jun 2007 21:47:37 +0000 Subject: [PATCH] better algorithm for finding available pages --- panda/src/gobj/geomVertexArrayData.cxx | 8 +- panda/src/gobj/simpleAllocator.I | 144 +++++++++++++++++++----- panda/src/gobj/simpleAllocator.cxx | 138 ++++++++++++++--------- panda/src/gobj/simpleAllocator.h | 31 ++++-- panda/src/gobj/vertexDataBook.I | 23 ++-- panda/src/gobj/vertexDataBook.cxx | 128 +++++++++++----------- panda/src/gobj/vertexDataBook.h | 15 ++- panda/src/gobj/vertexDataBuffer.I | 2 +- panda/src/gobj/vertexDataPage.I | 41 ++++++- panda/src/gobj/vertexDataPage.cxx | 146 ++++++++++++++++++------- panda/src/gobj/vertexDataPage.h | 18 ++- panda/src/gobj/vertexDataSaveFile.cxx | 4 +- 12 files changed, 480 insertions(+), 218 deletions(-) diff --git a/panda/src/gobj/geomVertexArrayData.cxx b/panda/src/gobj/geomVertexArrayData.cxx index 7eafd0e131..af2b9fdeff 100644 --- a/panda/src/gobj/geomVertexArrayData.cxx +++ b/panda/src/gobj/geomVertexArrayData.cxx @@ -723,9 +723,11 @@ copy_data_from(const GeomVertexArrayDataHandle *other) { other->mark_used(); _cdata->_buffer.unclean_realloc(other->_cdata->_buffer.get_size()); - memcpy(_cdata->_buffer.get_write_pointer(), - other->_cdata->_buffer.get_read_pointer(true), - other->_cdata->_buffer.get_size()); + + unsigned char *dest = _cdata->_buffer.get_write_pointer(); + const unsigned char *source = other->_cdata->_buffer.get_read_pointer(true); + size_t size = other->_cdata->_buffer.get_size(); + memcpy(dest, source, size); _cdata->_modified = Geom::get_next_modified(); diff --git a/panda/src/gobj/simpleAllocator.I b/panda/src/gobj/simpleAllocator.I index 29e5592074..3bfb6ea777 100644 --- a/panda/src/gobj/simpleAllocator.I +++ b/panda/src/gobj/simpleAllocator.I @@ -23,14 +23,30 @@ // Description: //////////////////////////////////////////////////////////////////// INLINE SimpleAllocator:: -SimpleAllocator(size_t max_size) : +SimpleAllocator(size_t max_size, Mutex &lock) : LinkedListNode(true), _total_size(0), _max_size(max_size), - _contiguous(max_size) + _contiguous(max_size), + _lock(lock) { } +//////////////////////////////////////////////////////////////////// +// Function: SimpleAllocator::alloc +// Access: Published +// Description: Allocates a new block. Returns NULL if a block of the +// requested size cannot be allocated. +// +// To free the allocated block, call block->free(), or +// simply delete the block pointer. +//////////////////////////////////////////////////////////////////// +SimpleAllocatorBlock *SimpleAllocator:: +alloc(size_t size) { + MutexHolder holder(_lock); + return do_alloc(size); +} + //////////////////////////////////////////////////////////////////// // Function: SimpleAllocator::is_empty // Access: Published @@ -39,7 +55,8 @@ SimpleAllocator(size_t max_size) : //////////////////////////////////////////////////////////////////// INLINE bool SimpleAllocator:: is_empty() const { - return (_next == this); + MutexHolder holder(_lock); + return do_is_empty(); } //////////////////////////////////////////////////////////////////// @@ -49,6 +66,7 @@ is_empty() const { //////////////////////////////////////////////////////////////////// INLINE size_t SimpleAllocator:: get_total_size() const { + MutexHolder holder(_lock); return _total_size; } @@ -59,6 +77,7 @@ get_total_size() const { //////////////////////////////////////////////////////////////////// INLINE size_t SimpleAllocator:: get_max_size() const { + MutexHolder holder(_lock); return _max_size; } @@ -71,6 +90,7 @@ get_max_size() const { //////////////////////////////////////////////////////////////////// INLINE void SimpleAllocator:: set_max_size(size_t max_size) { + MutexHolder holder(_lock); _max_size = max_size; } @@ -86,6 +106,7 @@ set_max_size(size_t max_size) { //////////////////////////////////////////////////////////////////// INLINE size_t SimpleAllocator:: get_contiguous() const { + MutexHolder holder(_lock); return _contiguous; } @@ -97,15 +118,31 @@ get_contiguous() const { //////////////////////////////////////////////////////////////////// INLINE SimpleAllocatorBlock *SimpleAllocator:: get_first_block() const { + MutexHolder holder(_lock); return (_next == this) ? (SimpleAllocatorBlock *)NULL : (SimpleAllocatorBlock *)_next; } //////////////////////////////////////////////////////////////////// -// Function: SimpleAllocator::increase_contiguous +// Function: SimpleAllocator::do_is_empty +// Access: Protected +// Description: Returns true if there are no blocks allocated on this +// page, or false if there is at least one. +// +// Assumes the lock is already held. +//////////////////////////////////////////////////////////////////// +INLINE bool SimpleAllocator:: +do_is_empty() const { + return (_next == this); +} + +//////////////////////////////////////////////////////////////////// +// Function: SimpleAllocator::mark_contiguous // Access: Protected // Description: Some space has been made available following the // indicated block. Increase the contiguous space // accordingly. +// +// Assumes the lock is already held. //////////////////////////////////////////////////////////////////// INLINE void SimpleAllocator:: mark_contiguous(const LinkedListNode *block) { @@ -119,10 +156,11 @@ mark_contiguous(const LinkedListNode *block) { space = ((SimpleAllocatorBlock *)_next)->get_start(); } } else { - space = ((SimpleAllocatorBlock *)block)->get_max_size() - ((SimpleAllocatorBlock *)block)->get_size(); + space = ((SimpleAllocatorBlock *)block)->do_get_max_size() - ((SimpleAllocatorBlock *)block)->get_size(); } if (space > _contiguous) { _contiguous = space; + changed_contiguous(); } } @@ -160,11 +198,8 @@ INLINE SimpleAllocatorBlock:: INLINE void SimpleAllocatorBlock:: free() { if (_allocator != (SimpleAllocator *)NULL) { - _allocator->_total_size -= _size; - LinkedListNode *prev = _prev; - remove_from_list(); - _allocator->mark_contiguous(prev); - _allocator = NULL; + MutexHolder holder(_allocator->_lock); + do_free(); } } @@ -223,14 +258,8 @@ is_free() const { INLINE size_t SimpleAllocatorBlock:: get_max_size() const { nassertr(_allocator != (SimpleAllocator *)NULL, 0); - - size_t end; - if (_next == _allocator) { - end = _allocator->get_max_size(); - } else { - end = ((SimpleAllocatorBlock *)_next)->_start; - } - return end - _start; + MutexHolder holder(_allocator->_lock); + return do_get_max_size(); } //////////////////////////////////////////////////////////////////// @@ -242,7 +271,73 @@ get_max_size() const { //////////////////////////////////////////////////////////////////// INLINE bool SimpleAllocatorBlock:: realloc(size_t size) { - if (size > get_max_size()) { + nassertr(_allocator != (SimpleAllocator *)NULL, false); + MutexHolder holder(_allocator->_lock); + return do_realloc(size); +} + +//////////////////////////////////////////////////////////////////// +// Function: SimpleAllocatorBlock::get_next_block +// Access: Published +// Description: Returns a pointer to the next allocated block in the +// chain, or NULL if there are no more allocated blocks. +//////////////////////////////////////////////////////////////////// +INLINE SimpleAllocatorBlock *SimpleAllocatorBlock:: +get_next_block() const { + nassertr(_allocator != (SimpleAllocator *)NULL, NULL); + MutexHolder holder(_allocator->_lock); + return (_next == _allocator) ? (SimpleAllocatorBlock *)NULL : (SimpleAllocatorBlock *)_next; +} + +//////////////////////////////////////////////////////////////////// +// Function: SimpleAllocatorBlock::do_free +// Access: Protected +// Description: Releases the allocated space. +// +// Assumes the lock is already held. +//////////////////////////////////////////////////////////////////// +INLINE void SimpleAllocatorBlock:: +do_free() { + nassertv(_allocator != (SimpleAllocator *)NULL); + + _allocator->_total_size -= _size; + LinkedListNode *prev = _prev; + remove_from_list(); + _allocator->mark_contiguous(prev); + _allocator = NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: SimpleAllocatorBlock::do_get_max_size +// Access: Protected +// Description: Returns the maximum size this block can be +// reallocated to, as limited by the following block. +// +// Assumes the lock is already held. +//////////////////////////////////////////////////////////////////// +INLINE size_t SimpleAllocatorBlock:: +do_get_max_size() const { + size_t end; + if (_next == _allocator) { + end = _allocator->_max_size; + } else { + end = ((SimpleAllocatorBlock *)_next)->_start; + } + return end - _start; +} + +//////////////////////////////////////////////////////////////////// +// Function: SimpleAllocatorBlock::do_realloc +// Access: Protected +// Description: Changes the size of this block to the specified size. +// Returns true if the change is accepted, false if +// there was not enough room. +// +// Assumes the lock is already held. +//////////////////////////////////////////////////////////////////// +INLINE bool SimpleAllocatorBlock:: +do_realloc(size_t size) { + if (size > do_get_max_size()) { return false; } @@ -259,14 +354,3 @@ realloc(size_t size) { } return true; } - -//////////////////////////////////////////////////////////////////// -// Function: SimpleAllocatorBlock::get_next_block -// Access: Published -// Description: Returns a pointer to the next allocated block in the -// chain, or NULL if there are no more allocated blocks. -//////////////////////////////////////////////////////////////////// -INLINE SimpleAllocatorBlock *SimpleAllocatorBlock:: -get_next_block() const { - return (_next == _allocator) ? (SimpleAllocatorBlock *)NULL : (SimpleAllocatorBlock *)_next; -} diff --git a/panda/src/gobj/simpleAllocator.cxx b/panda/src/gobj/simpleAllocator.cxx index 9ddcaf1c57..76e7e6893b 100644 --- a/panda/src/gobj/simpleAllocator.cxx +++ b/panda/src/gobj/simpleAllocator.cxx @@ -26,24 +26,60 @@ SimpleAllocator:: ~SimpleAllocator() { // We're shutting down. Force-free everything remaining. - while (_next != (LinkedListNode *)this) { - nassertv(_next != (LinkedListNode *)NULL); - cerr << "force-deleting " << _next << "\n"; - ((SimpleAllocatorBlock *)_next)->free(); + if (_next != (LinkedListNode *)this) { + MutexHolder holder(_lock); + while (_next != (LinkedListNode *)this) { + nassertv(_next != (LinkedListNode *)NULL); + ((SimpleAllocatorBlock *)_next)->do_free(); + } } } //////////////////////////////////////////////////////////////////// -// Function: SimpleAllocator::alloc +// Function: SimpleAllocator::output // Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +void SimpleAllocator:: +output(ostream &out) const { + MutexHolder holder(_lock); + out << "SimpleAllocator, " << _total_size << " of " << _max_size + << " allocated"; +} + +//////////////////////////////////////////////////////////////////// +// Function: SimpleAllocator::write +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +void SimpleAllocator:: +write(ostream &out) const { + MutexHolder holder(_lock); + out << "SimpleAllocator, " << _total_size << " of " << _max_size + << " allocated"; + + SimpleAllocatorBlock *block = (SimpleAllocatorBlock *)_next; + while (block->_next != this) { + SimpleAllocatorBlock *next = (SimpleAllocatorBlock *)block->_next; + + out << " " << *block << "\n"; + block = next; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: SimpleAllocator::do_alloc +// Access: Protected // Description: Allocates a new block. Returns NULL if a block of the // requested size cannot be allocated. // // To free the allocated block, call block->free(), or // simply delete the block pointer. +// +// Assumes the lock is already held. //////////////////////////////////////////////////////////////////// SimpleAllocatorBlock *SimpleAllocator:: -alloc(size_t size) { +do_alloc(size_t size) { if (size > _contiguous) { // Don't even bother. return NULL; @@ -70,6 +106,14 @@ alloc(size_t size) { new_block->insert_before(next); _total_size += size; + + if (_max_size - _total_size < _contiguous) { + // Since we only have (_max_size - _total_size) bytes + // remaining, it follows that our largest contiguous block + // must be no larger than this. + _contiguous = _max_size - _total_size; + changed_contiguous(); + } return new_block; } if (free_size > best) { @@ -89,6 +133,14 @@ alloc(size_t size) { new_block->insert_before(this); _total_size += size; + + if (_max_size - _total_size < _contiguous) { + // Since we only have (_max_size - _total_size) bytes + // remaining, it follows that our largest contiguous block + // must be no larger than this. + _contiguous = _max_size - _total_size; + changed_contiguous(); + } return new_block; } @@ -98,56 +150,15 @@ alloc(size_t size) { // Now that we've walked through the entire list of blocks, we // really do know accurately what the largest contiguous block is. - _contiguous = best; + if (_contiguous != best) { + _contiguous = best; + changed_contiguous(); + } // No room for this block. return NULL; } -//////////////////////////////////////////////////////////////////// -// Function: SimpleAllocator::output -// Access: Published -// Description: -//////////////////////////////////////////////////////////////////// -void SimpleAllocator:: -output(ostream &out) const { - out << "SimpleAllocator, " << _total_size << " of " << _max_size - << " allocated"; -} - -//////////////////////////////////////////////////////////////////// -// Function: SimpleAllocatorBlock::output -// Access: Published -// Description: -//////////////////////////////////////////////////////////////////// -void SimpleAllocatorBlock:: -output(ostream &out) const { - if (_allocator == (SimpleAllocator *)NULL) { - out << "free block\n"; - } else { - out << "block of size " << _size << " at " << _start; - } -} - -//////////////////////////////////////////////////////////////////// -// Function: SimpleAllocator::write -// Access: Published -// Description: -//////////////////////////////////////////////////////////////////// -void SimpleAllocator:: -write(ostream &out) const { - out << *this << ":\n"; - - SimpleAllocatorBlock *block = (SimpleAllocatorBlock *)_next; - while (block->_next != this) { - SimpleAllocatorBlock *next = (SimpleAllocatorBlock *)block->_next; - - out << " " << *block << "\n"; - block = next; - } -} - - //////////////////////////////////////////////////////////////////// // Function: SimpleAllocator::make_block // Access: Protected, Virtual @@ -159,3 +170,28 @@ make_block(size_t start, size_t size) { return new SimpleAllocatorBlock(this, start, size); } +//////////////////////////////////////////////////////////////////// +// Function: SimpleAllocator::changed_contiguous +// Access: Protected, Virtual +// Description: This callback function is made whenever the estimate +// of contiguous available space changes, either through +// an alloc or free. The lock will be held. +//////////////////////////////////////////////////////////////////// +void SimpleAllocator:: +changed_contiguous() { +} + +//////////////////////////////////////////////////////////////////// +// Function: SimpleAllocatorBlock::output +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +void SimpleAllocatorBlock:: +output(ostream &out) const { + if (_allocator == (SimpleAllocator *)NULL) { + out << "free block\n"; + } else { + MutexHolder holder(_allocator->_lock); + out << "block of size " << _size << " at " << _start; + } +} diff --git a/panda/src/gobj/simpleAllocator.h b/panda/src/gobj/simpleAllocator.h index 6181db5f79..e1353cb3f8 100644 --- a/panda/src/gobj/simpleAllocator.h +++ b/panda/src/gobj/simpleAllocator.h @@ -21,6 +21,8 @@ #include "pandabase.h" #include "linkedListNode.h" +#include "pmutex.h" +#include "mutexHolder.h" class SimpleAllocatorBlock; @@ -31,17 +33,13 @@ class SimpleAllocatorBlock; // integers within a specified upper limit; it uses a // simple first-fit algorithm to find the next available // space. -// -// Note that this class is not inherently thread-safe; -// derived classes are responsible for protecting any -// calls into it within mutexes, if necessary. //////////////////////////////////////////////////////////////////// class EXPCL_PANDA SimpleAllocator : public LinkedListNode { PUBLISHED: - INLINE SimpleAllocator(size_t max_size); + INLINE SimpleAllocator(size_t max_size, Mutex &lock); virtual ~SimpleAllocator(); - SimpleAllocatorBlock *alloc(size_t size); + INLINE SimpleAllocatorBlock *alloc(size_t size); INLINE bool is_empty() const; INLINE size_t get_total_size() const; @@ -55,10 +53,14 @@ PUBLISHED: void write(ostream &out) const; protected: + SimpleAllocatorBlock *do_alloc(size_t size); + INLINE bool do_is_empty() const; + virtual SimpleAllocatorBlock *make_block(size_t start, size_t size); INLINE void mark_contiguous(const LinkedListNode *block); + virtual void changed_contiguous(); -private: +protected: // This is implemented as a linked-list chain of allocated blocks. // Free blocks are implicit. Blocks are kept in sorted order from // beginning to end. Allocating a block means creating a new entry @@ -76,6 +78,16 @@ private: // it will not be smaller. size_t _contiguous; + // This mutex protects all operations within this class. The caller + // must pass the reference to a mutex in to the constructor, and the + // caller remains responsible for owning the mutex. This allows the + // mutex to be shared where appropriate. + + // A derived class may also use it to protect itself as well, but + // take care to call do_alloc() instead of alloc() etc. as + // necessary. + Mutex &_lock; + friend class SimpleAllocatorBlock; }; @@ -106,6 +118,11 @@ PUBLISHED: void output(ostream &out) const; +protected: + INLINE void do_free(); + INLINE size_t do_get_max_size() const; + INLINE bool do_realloc(size_t size); + private: SimpleAllocator *_allocator; size_t _start; diff --git a/panda/src/gobj/vertexDataBook.I b/panda/src/gobj/vertexDataBook.I index 5989d42d96..9014c767d0 100644 --- a/panda/src/gobj/vertexDataBook.I +++ b/panda/src/gobj/vertexDataBook.I @@ -17,6 +17,18 @@ //////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////// +// Function: VertexDataBook::alloc +// Access: Published +// Description: Allocates and returns a new VertexDataBuffer of the +// requested size. +//////////////////////////////////////////////////////////////////// +INLINE VertexDataBlock *VertexDataBook:: +alloc(size_t size) { + MutexHolder holder(_lock); + return do_alloc(size); +} + //////////////////////////////////////////////////////////////////// // Function: VertexDataBook::get_num_pages // Access: Published @@ -27,17 +39,6 @@ get_num_pages() const { return _pages.size(); } -//////////////////////////////////////////////////////////////////// -// Function: VertexDataBook::get_page -// Access: Published -// Description: Returns the nth page created for the book. -//////////////////////////////////////////////////////////////////// -INLINE VertexDataPage *VertexDataBook:: -get_page(int n) const { - nassertr(n >= 0 && n < (int)_pages.size(), NULL); - return _pages[n]; -} - //////////////////////////////////////////////////////////////////// // Function: VertexDataBook::create_new_page // Access: Private diff --git a/panda/src/gobj/vertexDataBook.cxx b/panda/src/gobj/vertexDataBook.cxx index 491b1e2c16..ff0beaafc0 100644 --- a/panda/src/gobj/vertexDataBook.cxx +++ b/panda/src/gobj/vertexDataBook.cxx @@ -17,7 +17,7 @@ //////////////////////////////////////////////////////////////////// #include "vertexDataBook.h" -#include "mutexHolder.h" +#include "reMutexHolder.h" //////////////////////////////////////////////////////////////////// // Function: VertexDataBook::Constructor @@ -26,7 +26,6 @@ //////////////////////////////////////////////////////////////////// VertexDataBook:: VertexDataBook(size_t block_size) : _block_size(block_size) { - _next_pi = 0; } //////////////////////////////////////////////////////////////////// @@ -38,71 +37,6 @@ VertexDataBook:: ~VertexDataBook() { } -//////////////////////////////////////////////////////////////////// -// Function: VertexDataBook::alloc -// Access: Published -// Description: Allocates and returns a new VertexDataBuffer of the -// requested size. -//////////////////////////////////////////////////////////////////// -VertexDataBlock *VertexDataBook:: -alloc(size_t size) { - MutexHolder holder(_lock); - - // First, try to allocate from the last page that worked; then - // continue to the end of the list. We consider only pages that are - // currently resident (or that are empty), to minimize unnecessary - // swapping. - size_t pi = _next_pi; - while (pi < _pages.size()) { - if (_pages[pi]->get_ram_class() == VertexDataPage::RC_resident || - _pages[pi]->is_empty()) { - VertexDataBlock *block = _pages[pi]->alloc(size); - if (block != (VertexDataBlock *)NULL) { - _next_pi = pi; - return block; - } - if (_pages[pi]->is_empty()) { - // This page is empty, but must have been too small. Create a - // new page in its place. - delete _pages[pi]; - _pages[pi] = create_new_page(size); - VertexDataBlock *block = _pages[pi]->alloc(size); - return block; - } - } - ++pi; - } - - // Then, go back to the beginning and try those pages. - pi = 0; - _next_pi = min(_next_pi, _pages.size()); - while (pi < _next_pi) { - if (_pages[pi]->get_ram_class() == VertexDataPage::RC_resident || - _pages[pi]->is_empty()) { - VertexDataBlock *block = _pages[pi]->alloc(size); - if (block != (VertexDataBlock *)NULL) { - _next_pi = pi; - return block; - } - if (_pages[pi]->is_empty()) { - // This page is empty, but must have been too small. Create a - // new page in its place. - delete _pages[pi]; - _pages[pi] = create_new_page(size); - return _pages[pi]->alloc(size); - } - } - ++pi; - } - - // No page was good enough. Create a new page. Make it at least - // large enough to hold this requested block. - VertexDataPage *page = create_new_page(size); - _pages.push_back(page); - VertexDataBlock *block = page->alloc(size); - return block; -} - //////////////////////////////////////////////////////////////////// // Function: VertexDataBook::count_total_page_size // Access: Published @@ -111,6 +45,8 @@ alloc(size_t size) { //////////////////////////////////////////////////////////////////// size_t VertexDataBook:: count_total_page_size() const { + MutexHolder holder(_lock); + size_t total = 0; Pages::const_iterator pi; for (pi = _pages.begin(); pi != _pages.end(); ++pi) { @@ -128,6 +64,8 @@ count_total_page_size() const { //////////////////////////////////////////////////////////////////// size_t VertexDataBook:: count_total_page_size(VertexDataPage::RamClass ram_class) const { + MutexHolder holder(_lock); + size_t total = 0; Pages::const_iterator pi; for (pi = _pages.begin(); pi != _pages.end(); ++pi) { @@ -146,6 +84,8 @@ count_total_page_size(VertexDataPage::RamClass ram_class) const { //////////////////////////////////////////////////////////////////// size_t VertexDataBook:: count_allocated_size() const { + MutexHolder holder(_lock); + size_t total = 0; Pages::const_iterator pi; for (pi = _pages.begin(); pi != _pages.end(); ++pi) { @@ -163,6 +103,8 @@ count_allocated_size() const { //////////////////////////////////////////////////////////////////// size_t VertexDataBook:: count_allocated_size(VertexDataPage::RamClass ram_class) const { + MutexHolder holder(_lock); + size_t total = 0; Pages::const_iterator pi; for (pi = _pages.begin(); pi != _pages.end(); ++pi) { @@ -183,9 +125,61 @@ count_allocated_size(VertexDataPage::RamClass ram_class) const { //////////////////////////////////////////////////////////////////// void VertexDataBook:: save_to_disk() { + MutexHolder holder(_lock); + Pages::iterator pi; for (pi = _pages.begin(); pi != _pages.end(); ++pi) { (*pi)->save_to_disk(); } } + +//////////////////////////////////////////////////////////////////// +// Function: VertexDataBook::do_alloc +// Access: Private +// Description: Allocates and returns a new VertexDataBuffer of the +// requested size. +// +// Assumes the lock is already held. +//////////////////////////////////////////////////////////////////// +VertexDataBlock *VertexDataBook:: +do_alloc(size_t size) { + // Look for an empty page of the appropriate size. The _pages set + // is sorted so that the pages with the smallest available blocks + // are at the front. + + // Create a dummy page to use to search the set. + VertexDataPage size_page(size); + Pages::iterator pi = _pages.lower_bound(&size_page); + + // Now we can start from the first element of the set that is + // possibly large enough to contain this block, and work up from + // there. + while (pi != _pages.end()) { + Pages::iterator pnext = pi; + ++pnext; + + VertexDataPage *page = (*pi); + + // Allocating a block may change the page's available contiguous + // size, and thereby change its position in the set, invalidating + // the iterator pi. This is why we've already computed pnext. + VertexDataBlock *block = page->do_alloc(size); + + if (block != (VertexDataBlock *)NULL) { + // This page worked. + return block; + } + + // Try the next page. + pi = pnext; + } + + // No page was good enough. Create a new page. Make it at least + // large enough to hold this requested block. + VertexDataPage *page = create_new_page(size); + _pages.insert(page); + + VertexDataBlock *block = page->do_alloc(size); + return block; +} diff --git a/panda/src/gobj/vertexDataBook.h b/panda/src/gobj/vertexDataBook.h index 95a43515e2..5f6fe76e03 100644 --- a/panda/src/gobj/vertexDataBook.h +++ b/panda/src/gobj/vertexDataBook.h @@ -23,6 +23,8 @@ #include "pmutex.h" #include "mutexHolder.h" #include "vertexDataPage.h" +#include "indirectLess.h" +#include "plist.h" class VertexDataBlock; @@ -36,10 +38,9 @@ PUBLISHED: VertexDataBook(size_t block_size); ~VertexDataBook(); - VertexDataBlock *alloc(size_t size); + INLINE VertexDataBlock *alloc(size_t size); INLINE int get_num_pages() const; - INLINE VertexDataPage *get_page(int n) const; size_t count_total_page_size() const; size_t count_total_page_size(VertexDataPage::RamClass ram_class) const; @@ -48,15 +49,21 @@ PUBLISHED: void save_to_disk(); +public: + void reorder_page(VertexDataPage *page); + private: INLINE VertexDataPage *create_new_page(size_t size); + VertexDataBlock *do_alloc(size_t size); private: size_t _block_size; - typedef pvector Pages; + + typedef pset > Pages; Pages _pages; - size_t _next_pi; + Mutex _lock; + friend class VertexDataPage; }; #include "vertexDataBook.I" diff --git a/panda/src/gobj/vertexDataBuffer.I b/panda/src/gobj/vertexDataBuffer.I index 994dfcb135..fc546858a4 100644 --- a/panda/src/gobj/vertexDataBuffer.I +++ b/panda/src/gobj/vertexDataBuffer.I @@ -141,7 +141,7 @@ clean_realloc(size_t size) { INLINE void VertexDataBuffer:: unclean_realloc(size_t size) { MutexHolder holder(_lock); - do_clean_realloc(size); + do_unclean_realloc(size); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/gobj/vertexDataPage.I b/panda/src/gobj/vertexDataPage.I index c5f373fbba..0267d5a124 100644 --- a/panda/src/gobj/vertexDataPage.I +++ b/panda/src/gobj/vertexDataPage.I @@ -59,6 +59,21 @@ request_resident() { } } +//////////////////////////////////////////////////////////////////// +// Function: VertexDataPage::alloc +// Access: Published +// Description: Allocates a new block. Returns NULL if a block of the +// requested size cannot be allocated. +// +// To free the allocated block, call block->free(), or +// simply delete the block pointer. +//////////////////////////////////////////////////////////////////// +INLINE VertexDataBlock *VertexDataPage:: +alloc(size_t size) { + MutexHolder holder(_lock); + return do_alloc(size); +} + //////////////////////////////////////////////////////////////////// // Function: VertexDataPage::get_first_block // Access: Published @@ -164,7 +179,7 @@ has_thread() { INLINE unsigned char *VertexDataPage:: get_page_data(bool force) { MutexHolder holder(_lock); - if (_ram_class != RC_resident) { + if (_ram_class != RC_resident || _pending_ram_class != RC_resident) { if (force) { make_resident_now(); } else { @@ -180,6 +195,26 @@ get_page_data(bool force) { return _page_data; } +//////////////////////////////////////////////////////////////////// +// Function: VertexDataPage::operator +// Access: Public +// Description: This comparison method is used to order pages within +// a book. +//////////////////////////////////////////////////////////////////// +INLINE bool VertexDataPage:: +operator < (const VertexDataPage &other) const { + // We sort pages so that the pages with the smallest number of + // available contiguous bytes come up first. We store our best + // estimate of continguous bytes here. + if (_book_size != other._book_size) { + return _book_size < other._book_size; + } + + // For pages of equal size, we sort based on pointers, to make it + // easy to quickly find a specific page. + return this < &other; +} + //////////////////////////////////////////////////////////////////// // Function: VertexDataPage::set_ram_class // Access: Private @@ -190,6 +225,10 @@ INLINE void VertexDataPage:: set_ram_class(RamClass rclass) { _ram_class = rclass; mark_used_lru(_global_lru[rclass]); + + // Changing the ram class might make our effective available space 0 + // and thereby change the placement within the book. + adjust_book_size(); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/gobj/vertexDataPage.cxx b/panda/src/gobj/vertexDataPage.cxx index b944bcdd75..067b83a7a9 100644 --- a/panda/src/gobj/vertexDataPage.cxx +++ b/panda/src/gobj/vertexDataPage.cxx @@ -71,6 +71,10 @@ SimpleLru *VertexDataPage::_global_lru[RC_end_of_list] = { VertexDataSaveFile *VertexDataPage::_save_file; +// This mutex is (mostly) unused. We just need a Mutex to pass to the +// Book Constructor, below. +Mutex VertexDataPage::_unused_mutex; + PStatCollector VertexDataPage::_vdata_compress_pcollector("*:Vertex Data:Compress"); PStatCollector VertexDataPage::_vdata_decompress_pcollector("*:Vertex Data:Decompress"); PStatCollector VertexDataPage::_vdata_save_pcollector("*:Vertex Data:Save"); @@ -79,19 +83,39 @@ PStatCollector VertexDataPage::_thread_wait_pcollector("*:Wait:Idle"); TypeHandle VertexDataPage::_type_handle; +//////////////////////////////////////////////////////////////////// +// Function: VertexDataPage::Book Constructor +// Access: Public +// Description: This constructor is used only by VertexDataBook, to +// create a mostly-empty object that can be used to +// search for a particular page size in the set. +//////////////////////////////////////////////////////////////////// +VertexDataPage:: +VertexDataPage(size_t book_size) : + SimpleAllocator(book_size, _unused_mutex), + SimpleLruPage(book_size), + _book_size(book_size), + _book(NULL) +{ + _page_data = NULL; + _size = 0; + _uncompressed_size = 0; + _ram_class = RC_resident; + _pending_ram_class = RC_resident; +} + //////////////////////////////////////////////////////////////////// // Function: VertexDataPage::Constructor -// Access: Published +// Access: Public // Description: //////////////////////////////////////////////////////////////////// VertexDataPage:: VertexDataPage(VertexDataBook *book, size_t page_size) : - SimpleAllocator(page_size), + SimpleAllocator(page_size, book->_lock), SimpleLruPage(page_size), + _book_size(page_size), _book(book) { - nassertv(page_size == get_max_size()); - get_class_type().inc_memory_usage(TypeHandle::MC_array, (int)page_size); _page_data = new unsigned char[page_size]; _size = page_size; @@ -103,12 +127,15 @@ VertexDataPage(VertexDataBook *book, size_t page_size) : //////////////////////////////////////////////////////////////////// // Function: VertexDataPage::Destructor -// Access: Published +// Access: Public // Description: //////////////////////////////////////////////////////////////////// VertexDataPage:: ~VertexDataPage() { - MutexHolder holder(_lock); + + // Since the only way to delete a page is via the + // changed_contiguous() method, the lock will already be held. + // MutexHolder holder(_lock); { MutexHolder holder2(_tlock); @@ -125,29 +152,6 @@ VertexDataPage:: } } -//////////////////////////////////////////////////////////////////// -// Function: VertexDataPage::alloc -// Access: Published -// Description: Allocates a new block. Returns NULL if a block of the -// requested size cannot be allocated. -// -// To free the allocated block, call block->free(), or -// simply delete the block pointer. -//////////////////////////////////////////////////////////////////// -VertexDataBlock *VertexDataPage:: -alloc(size_t size) { - MutexHolder holder(_lock); - VertexDataBlock *block = (VertexDataBlock *)SimpleAllocator::alloc(size); - - if (block != (VertexDataBlock *)NULL && _ram_class != RC_disk) { - // When we allocate a new block within a resident page, we have to - // clear the disk cache (since we have just invalidated it). - _saved_block.clear(); - } - - return block; -} - //////////////////////////////////////////////////////////////////// // Function: VertexDataPage::stop_thread // Access: Published, Static @@ -182,6 +186,27 @@ make_block(size_t start, size_t size) { return new VertexDataBlock(this, start, size); } +//////////////////////////////////////////////////////////////////// +// Function: VertexDataPage::changed_contiguous +// Access: Protected, Virtual +// Description: This callback function is made whenever the estimate +// of contiguous available space changes, either through +// an alloc or free. The lock will be held. +//////////////////////////////////////////////////////////////////// +void VertexDataPage:: +changed_contiguous() { + if (do_is_empty()) { + // If the page is now empty, delete it. + VertexDataBook::Pages::iterator pi = _book->_pages.find(this); + nassertv(pi != _book->_pages.end()); + _book->_pages.erase(pi); + delete this; + return; + } + + adjust_book_size(); +} + //////////////////////////////////////////////////////////////////// // Function: VertexDataPage::evict_lru // Access: Public, Virtual @@ -223,6 +248,30 @@ evict_lru() { } } +//////////////////////////////////////////////////////////////////// +// Function: VertexDataPage::do_alloc +// Access: Private +// Description: Allocates a new block. Returns NULL if a block of the +// requested size cannot be allocated. +// +// To free the allocated block, call block->free(), or +// simply delete the block pointer. +// +// Assumes the lock is already held. +//////////////////////////////////////////////////////////////////// +VertexDataBlock *VertexDataPage:: +do_alloc(size_t size) { + VertexDataBlock *block = (VertexDataBlock *)SimpleAllocator::do_alloc(size); + + if (block != (VertexDataBlock *)NULL && _ram_class != RC_disk) { + // When we allocate a new block within a resident page, we have to + // clear the disk cache (since we have just invalidated it). + _saved_block.clear(); + } + + return block; +} + //////////////////////////////////////////////////////////////////// // Function: VertexDataPage::make_resident_now // Access: Private @@ -287,9 +336,8 @@ make_resident() { delete[] _page_data; _page_data = new_data; _size = _uncompressed_size; - - #endif + set_lru_size(_size); set_ram_class(RC_resident); } @@ -471,6 +519,33 @@ do_restore_from_disk() { } } +//////////////////////////////////////////////////////////////////// +// Function: VertexDataPage::adjust_book_size +// Access: Private +// Description: Called when the "book size"--the size of the page as +// recorded in its book's table--has changed for some +// reason. Assumes the lock is held. +//////////////////////////////////////////////////////////////////// +void VertexDataPage:: +adjust_book_size() { + size_t new_size = _contiguous; + if (_ram_class != RC_resident) { + // Let's not attempt to allocate new buffers from non-resident + // pages. + new_size = 0; + } + + if (new_size != _book_size) { + VertexDataBook::Pages::iterator pi = _book->_pages.find(this); + nassertv(pi != _book->_pages.end()); + _book->_pages.erase(pi); + + _book_size = new_size; + bool inserted = _book->_pages.insert(this).second; + nassertv(inserted); + } +} + //////////////////////////////////////////////////////////////////// // Function: VertexDataPage::request_ram_class // Access: Private @@ -483,12 +558,6 @@ do_restore_from_disk() { //////////////////////////////////////////////////////////////////// void VertexDataPage:: request_ram_class(RamClass ram_class) { - if (ram_class == _ram_class) { - gobj_cat.warning() - << "Page " << this << " already has ram class " << ram_class << "\n"; - return; - } - if (!vertex_data_threaded_paging || !Thread::is_threading_supported()) { // No threads. Do it immediately. switch (ram_class) { @@ -613,6 +682,9 @@ remove_page(VertexDataPage *page) { } page->_pending_ram_class = page->_ram_class; + + // Put the page back on its proper LRU. + page->mark_used_lru(_global_lru[page->_ram_class]); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/gobj/vertexDataPage.h b/panda/src/gobj/vertexDataPage.h index e38b6d715d..6018ef6d4c 100644 --- a/panda/src/gobj/vertexDataPage.h +++ b/panda/src/gobj/vertexDataPage.h @@ -42,10 +42,11 @@ class VertexDataBlock; //////////////////////////////////////////////////////////////////// class EXPCL_PANDA VertexDataPage : public SimpleAllocator, public SimpleLruPage { public: + VertexDataPage(size_t book_size); VertexDataPage(VertexDataBook *book, size_t page_size); -PUBLISHED: ~VertexDataPage(); +PUBLISHED: // These are used to indicate the current residency state of the // page, which may or may not have been temporarily evicted to // satisfy memory requirements. @@ -61,7 +62,7 @@ PUBLISHED: INLINE RamClass get_pending_ram_class() const; INLINE void request_resident(); - VertexDataBlock *alloc(size_t size); + INLINE VertexDataBlock *alloc(size_t size); INLINE VertexDataBlock *get_first_block() const; INLINE VertexDataBook *get_book() const; @@ -77,14 +78,18 @@ PUBLISHED: public: INLINE unsigned char *get_page_data(bool force); + INLINE bool operator < (const VertexDataPage &other) const; protected: virtual SimpleAllocatorBlock *make_block(size_t start, size_t size); + virtual void changed_contiguous(); virtual void evict_lru(); private: class PageThread; + VertexDataBlock *do_alloc(size_t size); + void make_resident_now(); void make_resident(); void make_compressed(); @@ -93,7 +98,8 @@ private: bool do_save_to_disk(); void do_restore_from_disk(); - PageThread *get_thread(); + void adjust_book_size(); + void request_ram_class(RamClass ram_class); INLINE void set_ram_class(RamClass ram_class); static void make_save_file(); @@ -126,8 +132,9 @@ private: size_t _size, _uncompressed_size; RamClass _ram_class; PT(VertexDataSaveBlock) _saved_block; + size_t _book_size; - Mutex _lock; // Protects above members + //Mutex _lock; // Inherited from SimpleAllocator. Protects above members. RamClass _pending_ram_class; // Protected by _tlock. @@ -141,6 +148,8 @@ private: static VertexDataSaveFile *_save_file; + static Mutex _unused_mutex; + static PStatCollector _vdata_compress_pcollector; static PStatCollector _vdata_decompress_pcollector; static PStatCollector _vdata_save_pcollector; @@ -159,6 +168,7 @@ private: static TypeHandle _type_handle; friend class PageThread; + friend class VertexDataBook; }; #include "vertexDataPage.I" diff --git a/panda/src/gobj/vertexDataSaveFile.cxx b/panda/src/gobj/vertexDataSaveFile.cxx index 91a60aa9ee..148a58f110 100644 --- a/panda/src/gobj/vertexDataSaveFile.cxx +++ b/panda/src/gobj/vertexDataSaveFile.cxx @@ -33,7 +33,7 @@ VertexDataSaveFile:: VertexDataSaveFile(const Filename &directory, const string &prefix, size_t max_size) : - SimpleAllocator(max_size) + SimpleAllocator(max_size, _lock) { Filename dir; if (directory.empty()) { @@ -183,7 +183,7 @@ write_data(const unsigned char *data, size_t size, bool compressed) { return NULL; } - PT(VertexDataSaveBlock) block = (VertexDataSaveBlock *)SimpleAllocator::alloc(size); + PT(VertexDataSaveBlock) block = (VertexDataSaveBlock *)SimpleAllocator::do_alloc(size); if (block != (VertexDataSaveBlock *)NULL) { block->set_compressed(compressed);