Improve the evicting algorithm

The miss counter was taken away. Now the algorithm relies on the
distance between the tiles and the viewport.

Also the visibleRect() and setVisibleRect() methods were removed from
TilesManager since we now pass this information to
TilesManager::cleanupPixmapMemory()
This commit is contained in:
Mailson Menezes 2012-11-08 10:58:08 -03:00
parent c13ad1afc3
commit da54ffdd8e
5 changed files with 108 additions and 87 deletions

View file

@ -227,11 +227,21 @@ void DocumentPrivate::cleanupPixmapMemory( qulonglong memoryToFree )
{
if ( memoryToFree > 0 )
{
// Number of one of the visible pages (to be used by tiles cleanup)
int visiblePageNumber = -1;
QMap< int, VisiblePageRect * > visibleRects;
QVector< Okular::VisiblePageRect * >::const_iterator vIt = m_pageRects.constBegin(), vEnd = m_pageRects.constEnd();
for ( ; vIt != vEnd; ++vIt )
{
visiblePageNumber = (*vIt)->pageNumber;
visibleRects.insert( (*vIt)->pageNumber, (*vIt) );
}
// Free memory starting from pages that are farthest from the current one
int pagesFreed = 0;
while ( memoryToFree > 0 )
{
AllocatedPixmap * p = searchLowestPriorityUnloadablePixmap( true );
AllocatedPixmap * p = searchLowestPriorityPixmap( true, true );
if ( !p ) // No pixmap to remove
break;
@ -254,34 +264,51 @@ void DocumentPrivate::cleanupPixmapMemory( qulonglong memoryToFree )
// If we're still on low memory, try to free individual tiles based on
// a ranking algorithm
QLinkedList< AllocatedPixmap * >::iterator pIt = m_allocatedPixmaps.begin();
QLinkedList< AllocatedPixmap * >::iterator pEnd = m_allocatedPixmaps.end();
while ( pIt != pEnd && memoryToFree > 0 )
// Store pages that weren't completely removed
QLinkedList< AllocatedPixmap * > pixmapsToKeep;
while ( memoryToFree > 0 )
{
AllocatedPixmap * p = *pIt;
AllocatedPixmap * p = searchLowestPriorityPixmap( false, true );
if ( !p ) // No pixmap to remove
break;
TilesManager *tilesManager = m_pagesVector.at( p->page )->tilesManager( p->id );
if ( tilesManager && tilesManager->totalMemory() > 0 )
{
tilesManager->cleanupPixmapMemory( memoryToFree );
m_allocatedPixmapsTotalMemory -= p->memory;
int memoryDiff = -p->memory;
p->memory = tilesManager->totalMemory();
memoryDiff += p->memory;
memoryToFree = qMax( 0, memoryDiff );
m_allocatedPixmapsTotalMemory += p->memory;
}
int memoryDiff = p->memory;
NormalizedRect currentViewport;
if ( visibleRects.contains( p->page ) )
currentViewport = visibleRects[ p->page ]->rect;
++pIt;
// Cleanup non visible tiles based on its dirty state and distance from the viewport
tilesManager->cleanupPixmapMemory( memoryToFree, currentViewport, visiblePageNumber );
p->memory = tilesManager->totalMemory();
memoryDiff -= p->memory;
memoryToFree = memoryToFree - memoryDiff;
m_allocatedPixmapsTotalMemory -= memoryDiff;
if ( p->memory > 0 )
pixmapsToKeep.append( p );
else
delete p;
}
else
pixmapsToKeep.append( p );
}
m_allocatedPixmaps += pixmapsToKeep;
//p--rintf("freeMemory A:[%d -%d = %d] \n", m_allocatedPixmaps.count() + pagesFreed, pagesFreed, m_allocatedPixmaps.count() );
}
}
/* Returns the next pixmap to evict from cache, or NULL if no suitable pixmap
* is found. If thenRemoveIt is set, the pixmap is removed from
* m_allocatedPixmaps before returning it */
AllocatedPixmap * DocumentPrivate::searchLowestPriorityUnloadablePixmap( bool thenRemoveIt )
* if found. If unloadableOnly is set, only unloadable pixmaps are returned. If
* thenRemoveIt is set, the pixmap is removed from m_allocatedPixmaps before
* returning it
*/
AllocatedPixmap * DocumentPrivate::searchLowestPriorityPixmap( bool unloadableOnly, bool thenRemoveIt )
{
QLinkedList< AllocatedPixmap * >::iterator pIt = m_allocatedPixmaps.begin();
QLinkedList< AllocatedPixmap * >::iterator pEnd = m_allocatedPixmaps.end();
@ -294,7 +321,7 @@ AllocatedPixmap * DocumentPrivate::searchLowestPriorityUnloadablePixmap( bool th
{
const AllocatedPixmap * p = *pIt;
const int distance = qAbs( p->page - currentViewportPage );
if ( maxDistance < distance && m_observers.value( p->id )->canUnloadPixmap( p->page ) )
if ( maxDistance < distance && ( !unloadableOnly || m_observers.value( p->id )->canUnloadPixmap( p->page ) ) )
{
maxDistance = distance;
farthestPixmap = pIt;
@ -1007,7 +1034,7 @@ void DocumentPrivate::sendGeneratorRequest()
int maxDistance = INT_MAX; // Default: No maximum
if ( memoryToFree )
{
AllocatedPixmap *pixmapToReplace = searchLowestPriorityUnloadablePixmap();
AllocatedPixmap *pixmapToReplace = searchLowestPriorityPixmap( true );
if ( pixmapToReplace )
maxDistance = qAbs( pixmapToReplace->page - currentViewportPage );
}
@ -1056,7 +1083,7 @@ void DocumentPrivate::sendGeneratorRequest()
const QPixmap *pixmap = r->page()->_o_nearestPixmap( r->id(), r->width(), r->height() );
if ( pixmap )
{
tilesManager = new TilesManager( pixmap->width(), pixmap->height(), r->page()->rotation() );
tilesManager = new TilesManager( r->pageNumber(), pixmap->width(), pixmap->height(), r->page()->rotation() );
tilesManager->setPixmap( pixmap, NormalizedRect( 0, 0, 1, 1 ) );
tilesManager->setWidth( r->width() );
tilesManager->setHeight( r->height() );
@ -1064,7 +1091,7 @@ void DocumentPrivate::sendGeneratorRequest()
else
{
// create new tiles manager
tilesManager = new TilesManager( r->width(), r->height(), r->page()->rotation() );
tilesManager = new TilesManager( r->pageNumber(), r->width(), r->height(), r->page()->rotation() );
}
tilesManager->setRequest( r->normalizedRect(), r->width(), r->height() );
r->page()->deletePixmap( r->id() );
@ -1274,7 +1301,19 @@ void DocumentPrivate::refreshPixmaps( int pageNumber )
PixmapRequest * p = new PixmapRequest( tmIt.key(), pageNumber, tilesManager->width(), tilesManager->height(), 1, true );
NormalizedRect tilesRect;
QList<Tile> tiles = tilesManager->tilesAt( tilesManager->visibleRect() );
// Get the visible page rect
NormalizedRect visibleRect;
QVector< Okular::VisiblePageRect * >::const_iterator vIt = m_pageRects.constBegin(), vEnd = m_pageRects.constEnd();
for ( ; vIt != vEnd; ++vIt )
{
if ( (*vIt)->pageNumber == pageNumber )
{
visibleRect = (*vIt)->rect;
break;
}
}
QList<Tile> tiles = tilesManager->tilesAt( visibleRect );
QList<Tile>::const_iterator tIt = tiles.constBegin(), tEnd = tiles.constEnd();
while ( tIt != tEnd )
{

View file

@ -98,7 +98,7 @@ class DocumentPrivate
qulonglong calculateMemoryToFree();
void cleanupPixmapMemory();
void cleanupPixmapMemory( qulonglong memoryToFree );
AllocatedPixmap * searchLowestPriorityUnloadablePixmap( bool thenRemoveIt = false );
AllocatedPixmap * searchLowestPriorityPixmap( bool unloadableOnly = false, bool thenRemoveIt = false );
void calculateMaxTextPages();
qulonglong getTotalMemory();
qulonglong getFreeMemory( qulonglong *freeSwap = 0 );

View file

@ -12,18 +12,15 @@
#include <QtCore/qmath.h>
#include <QList>
#define MISSCOUNTER_MAX INT_MAX/2
#define MISSCOUNTER_MIN INT_MIN/2
#define TILES_MAXSIZE 2000000
using namespace Okular;
static bool rankedTilesLessThan( Tile *t1, Tile *t2 )
{
// Order tiles by its dirty state and miss counter. That is: dirty tiles
// will be evicted first then tiles with higher miss values.
// Order tiles by its dirty state and then by distance from the viewport.
if ( t1->dirty == t2->dirty )
return t1->miss < t2->miss;
return t1->distance < t2->distance;
return !t1->dirty;
}
@ -48,7 +45,7 @@ class TilesManager::Private
void deleteTiles( const Tile &tile );
void markParentDirty( const Tile &tile );
void rankTiles( Tile &tile, QList<Tile*> &rankedTiles );
void rankTiles( Tile &tile, QList<Tile*> &rankedTiles, const NormalizedRect &visibleRect, int visiblePageNumber );
/**
* Since the tile can be large enough to occupy a significant amount of
* space, they may be split in more tiles. This operation is performed
@ -69,6 +66,7 @@ class TilesManager::Private
Tile tiles[16];
int width;
int height;
int pageNumber;
long totalPixels;
Rotation rotation;
NormalizedRect visibleRect;
@ -80,6 +78,7 @@ class TilesManager::Private
TilesManager::Private::Private()
: width( 0 )
, height( 0 )
, pageNumber( 0 )
, totalPixels( 0 )
, rotation( Rotation0 )
, requestRect( NormalizedRect() )
@ -88,9 +87,10 @@ TilesManager::Private::Private()
{
}
TilesManager::TilesManager( int width, int height, Rotation rotation )
TilesManager::TilesManager( int pageNumber, int width, int height, Rotation rotation )
: d( new Private )
{
d->pageNumber = pageNumber;
d->width = width;
d->height = height;
d->rotation = rotation;
@ -191,19 +191,6 @@ void TilesManager::Private::markDirty( Tile &tile )
}
}
void TilesManager::setVisibleRect( const NormalizedRect &rect )
{
if ( d->visibleRect == rect )
return;
d->visibleRect = rect;
}
NormalizedRect TilesManager::visibleRect() const
{
return d->visibleRect;
}
void TilesManager::setPixmap( const QPixmap *pixmap, const NormalizedRect &rect )
{
if ( !d->requestRect.isNull() )
@ -368,10 +355,7 @@ QList<Tile> TilesManager::tilesAt( const NormalizedRect &rect, bool allowEmpty )
void TilesManager::Private::tilesAt( const NormalizedRect &rect, Tile &tile, QList<Tile> &result, bool allowEmpty )
{
if ( !tile.rect.intersects( rect ) )
{
tile.miss = qMin( tile.miss+1, MISSCOUNTER_MAX );
return;
}
// split big tiles before the requests are made, otherwise we would end up
// requesting huge areas unnecessarily
@ -379,7 +363,6 @@ void TilesManager::Private::tilesAt( const NormalizedRect &rect, Tile &tile, QLi
if ( ( allowEmpty && tile.nTiles == 0 ) || ( !allowEmpty && tile.pixmap ) )
{
tile.miss = qMax( tile.miss-1, MISSCOUNTER_MIN );
Tile newTile = tile;
if ( rotation != Rotation0 )
newTile.rect = TilesManager::toRotatedRect( tile.rect, rotation );
@ -397,12 +380,12 @@ long TilesManager::totalMemory() const
return 4*d->totalPixels;
}
void TilesManager::cleanupPixmapMemory( qulonglong numberOfBytes )
void TilesManager::cleanupPixmapMemory( qulonglong numberOfBytes, const NormalizedRect &visibleRect, int visiblePageNumber )
{
QList<Tile*> rankedTiles;
for ( int i = 0; i < 16; ++i )
{
d->rankTiles( d->tiles[ i ], rankedTiles );
d->rankTiles( d->tiles[ i ], rankedTiles, visibleRect, visiblePageNumber );
}
qSort( rankedTiles.begin(), rankedTiles.end(), rankedTilesLessThan );
@ -413,7 +396,7 @@ void TilesManager::cleanupPixmapMemory( qulonglong numberOfBytes )
continue;
// do not evict visible pixmaps
if ( tile->rect.intersects( d->visibleRect ) )
if ( tile->rect.intersects( visibleRect ) )
continue;
qulonglong pixels = tile->pixmap->width()*tile->pixmap->height();
@ -423,7 +406,6 @@ void TilesManager::cleanupPixmapMemory( qulonglong numberOfBytes )
else
numberOfBytes -= 4*pixels;
tile->miss = 0;
delete tile->pixmap;
tile->pixmap = 0;
@ -443,24 +425,43 @@ void TilesManager::Private::markParentDirty( const Tile &tile )
}
}
void TilesManager::Private::rankTiles( Tile &tile, QList<Tile*> &rankedTiles )
void TilesManager::Private::rankTiles( Tile &tile, QList<Tile*> &rankedTiles, const NormalizedRect &visibleRect, int visiblePageNumber )
{
if ( tile.parent )
tile.miss = qBound( MISSCOUNTER_MIN, tile.miss + tile.parent->miss, MISSCOUNTER_MAX );
// If the page is visible, visibleRect is not null.
// Otherwise we use the number of one of the visible pages to calculate the
// distance.
// Note that the current page may be visible and yet its pageNumber is
// different from visiblePageNumber. Since we only use this value on hidden
// pages, any visible page number will fit.
if ( visibleRect.isNull() && visiblePageNumber < 0 )
return;
if ( tile.pixmap )
{
// Update distance
if ( !visibleRect.isNull() )
{
NormalizedPoint viewportCenter = visibleRect.center();
NormalizedPoint tileCenter = tile.rect.center();
// Manhattan distance. It's a good and fast approximation.
tile.distance = qAbs(viewportCenter.x - tileCenter.x) + qAbs(viewportCenter.y + tileCenter.y);
}
else
{
// For non visible pages only the vertical distance is used
if ( pageNumber < visiblePageNumber )
tile.distance = 1 - tile.rect.bottom;
else
tile.distance = tile.rect.top;
}
rankedTiles.append( &tile );
}
else
{
for ( int i = 0; i < tile.nTiles; ++i )
{
rankTiles( tile.tiles[ i ], rankedTiles );
rankTiles( tile.tiles[ i ], rankedTiles, visibleRect, visiblePageNumber );
}
if ( tile.nTiles > 0 )
tile.miss = 0;
}
}
@ -564,10 +565,10 @@ NormalizedRect TilesManager::toRotatedRect( const NormalizedRect &rect, Rotation
Tile::Tile()
: pixmap( 0 )
, dirty ( true )
, distance( -1 )
, tiles( 0 )
, nTiles( 0 )
, parent( 0 )
, miss( 0 )
{
}

View file

@ -54,6 +54,11 @@ class OKULAR_EXPORT Tile
* (dirty = false), the parent tile is also considered updated.
*/
bool dirty;
/**
* Distance between the tile and the viewport.
* This is used by the evicting algorithm.
*/
double distance;
/**
* Children tiles
@ -67,14 +72,6 @@ class OKULAR_EXPORT Tile
int nTiles;
Tile *parent;
/**
* Hit/miss counter.
* Increased whenever the tile is not referenced. Decreased whenever a
* reference to the tile is made
* Used by the ranking algorithm.
*/
int miss;
};
/**
@ -93,7 +90,7 @@ class OKULAR_EXPORT Tile
class OKULAR_EXPORT TilesManager
{
public:
TilesManager( int width, int height, Rotation rotation = Rotation0 );
TilesManager( int pageNumber, int width, int height, Rotation rotation = Rotation0 );
virtual ~TilesManager();
/**
@ -119,7 +116,6 @@ class OKULAR_EXPORT TilesManager
*
* As to avoid requests of big areas, each traversed tile is checked
* for its size and split if necessary.
* Also the miss counter is updated for the use in the evicting algorithm.
*
* @param allowEmpty If false only tiles with a non null pixmap are returned
*/
@ -133,9 +129,11 @@ class OKULAR_EXPORT TilesManager
/**
* Removes at least @p numberOfBytes bytes worth of tiles (least ranked
* tiles are removed first).
* Set @p visibleRect to the visible region of the page. Set a
* @p visiblePageNumber if the current page is not visible.
* Visible tiles are not discarded.
*/
void cleanupPixmapMemory( qulonglong numberOfBytes = 1 );
void cleanupPixmapMemory( qulonglong numberOfBytes, const NormalizedRect &visibleRect, int visiblePageNumber );
/**
* Checks whether a given region has already been requested
@ -176,17 +174,6 @@ class OKULAR_EXPORT TilesManager
*/
void markDirty();
/**
* Sets the visible area of the page so tiles in this area will not be
* removed in the evicting process.
*/
void setVisibleRect( const NormalizedRect &rect );
/**
* Returns the visible area of the page
*/
NormalizedRect visibleRect() const;
/**
* Returns a rotated NormalizedRect given a @p rotation
*/

View file

@ -4007,9 +4007,6 @@ void PageView::slotRequestVisiblePixmaps( int newValue )
QRect intersectionRect = viewportRect.intersect( i->croppedGeometry() );
if ( intersectionRect.isEmpty() )
{
Okular::TilesManager *tilesManager = i->page()->tilesManager( PAGEVIEW_ID );
if ( tilesManager )
tilesManager->setVisibleRect( Okular::NormalizedRect() );
continue;
}
@ -4023,9 +4020,6 @@ void PageView::slotRequestVisiblePixmaps( int newValue )
#endif
Okular::TilesManager *tilesManager = i->page()->tilesManager( PAGEVIEW_ID );
if ( tilesManager )
tilesManager->setVisibleRect( vItem->rect );
Okular::NormalizedRect expandedVisibleRect = vItem->rect;
if ( tilesManager && Okular::Settings::memoryLevel() != Okular::Settings::EnumMemoryLevel::Low )
{