C++Talk.NET Forum Index C++Talk.NET
C++ language newsgroups
 
Archives   FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

cache - fixed size map

 
Post new topic   Reply to topic    C++Talk.NET Forum Index -> C++ Language (Moderated)
View previous topic :: View next topic  
Author Message
Radu Grigore
Guest





PostPosted: Mon Aug 16, 2004 7:43 pm    Post subject: cache - fixed size map Reply with quote



A cache can be implemented as a (hash_)map. In my case the growing
size might become a problem. A possible solution is to remove elements
(LFU, LRU) either periodically or, probably easier, in such a way as
to preserve a maximum size. It seems like a common problem and I'm
wondering if anyone has already done it: preferably as an open source
library, but an open source project (so I can read the code) is OK
too.

thanks,
radu

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
Back to top
Kai-Uwe Bux
Guest





PostPosted: Tue Aug 17, 2004 9:45 am    Post subject: Re: cache - fixed size map Reply with quote



Radu Grigore wrote:

Quote:
A cache can be implemented as a (hash_)map. In my case the growing
size might become a problem. A possible solution is to remove elements
(LFU, LRU) either periodically or, probably easier, in such a way as
to preserve a maximum size. It seems like a common problem and I'm
wondering if anyone has already done it: preferably as an open source
library, but an open source project (so I can read the code) is OK
too.

Hi,



this is some simple code that might get you started. I have to give a
little warning notice:

(a) The code is *not* well tested.
(b) It could need better documentation.


// cache.cc (C) Kai-Uwe Bux [2004]
// ===============================
//
// Defines the cache container template.
/*
Quote:
The cache template provides an object that wraps around a
computationally expensive function to avoid recomputation
of already known values.

The following example code snipplet should illustrate the
intended use:

class SomeIncrediblyHardFunction {
public:

data_type operator() ( const key_type & key ) {
// your code goes here
}

} the_function;

cache< key_type, data_type, SomeIncrediblyHardFunction
wrapper ( the_function, initial_size );

// later:

data = wrapper( key );

The wrapper will keep an internal table of up to of given argument, the warpper will return this value. Otherwise,
and only otherwise, the value will be computed from scratch
using <the_function>. Then the new pair will be stored in the
table. If need be, entries will be freed on a "least recently used"
basis.
*/


// ================================================================
// || INTERFACE ||
// ================================================================
#if 0

namespace kubux {

template< typename KeyType,
typename DataType,
typename Function,
std::size_t Max_Size = 4096,
typename Compare = std::less< KeyType >,
typename Allocator = std::allocator< std::pair< const KeyType,
DataType > >
Quote:

class cache {

public:

typedef KeyType key_type;
typedef DataType mapped_type;
typedef std::pair< const KeyType, DataType > value_type;
typedef Compare key_compare;
typedef Function transform;
typedef Allocator alloc_type;

cache ( const cache & other );
// copy constructor.

cache ( Function & function, std::size_t capacity = Max_Size );
// constructor.

~cache ( void );
// destructor.

const cache & operator= ( const cache & other );
// assignment operator.

DataType operator() ( const KeyType & key ) const;
// evaluate the stored function at <key>

void clear ( void ) const;
// clear the table.

std::size_t size ( void ) const;
// return the number of currently stored entries.

void resize ( std::size_t new_capacity ) const;
// set the capacity.
// if size() > new_capacity, remove entries based on
// a "least recently used" policy.

bool empty ( void ) const;
// return( size() == 0 )

std::size_t capacity ( void ) const;
// return the current capacity.

void copy ( const cache & other, std::size_t length );
// clear() and copy <length> entries from <other>
// choose those entries that are most recently used.

void write( std::ostream & ostr ) const;
// write a list entries to <ostr> in order of use:
// start from the most recently used and work your way
// to the least recently used.

}; // cache<>

} // namespace kubux

// in global namespace:
std::ostream & operator<< ( std::ostream & ostr,
const kubux::cache & the_cache );

#endif // end of interface description

// ====================================================================
// || IMPLEMENTATION ||
// ====================================================================

#ifndef __KUBUX_cache__
#define __KUBUX_cache__

#include #include <utility>
#include <functional>
#include <memory>
#include <iostream>
#include "sequenceio.cc"

namespace kubux {

template< typename KeyType,
typename DataType,
typename Function,
std::size_t Max_Size = 4096,
typename Compare = std::less< KeyType >,
typename Allocator =
std::allocator< std::pair< const KeyType, DataType > >
Quote:

class cache {

private:
/*
Quote:
We implement the cache on top of a map: the pairs <key,data> are
stored in a map for efficient retrieval. The "least recent use"
policy for removing entries is implemented by a self organizing
list: we keep a doubly linked list of entries and whenever an
element
is accessed it moves to the head. The tail is always the least
recently used entry.

The problem is to merge the two data structures. We want the list
to consist of the entries in the map. Thus, we would like to have a
next and prev field within each entry pointing to the next and
previous entry according to the list. The diffculty lies is that
next and prev should be of type map::iterator; and the map type
is yet to be formed. We overcome this, by a forward declaration.
*/


class EntryType;

typedef typename Allocator::template
rebind< std::pair< const KeyType, EntryType > >::other
MapEntryAllocator;

typedef typename
std::map< KeyType, EntryType, Compare, MapEntryAllocator > MapType;

typedef typename MapType::iterator IterType;

typedef typename MapType::value_type PairType;

class EntryType {
public:

DataType data;
IterType next;
IterType prev;

EntryType ( const DataType & _data,
const IterType & _next,
const IterType & _prev ) :
data( _data ),
next( _next ),
prev( _prev )
{}

}; // EntryType;

/*
Quote:
The following are clearly needed:
*/


Function & the_function;
mutable MapType the_map;
std::size_t max_capacity;

/*
Quote:
Now, we implement the doubly linked list.
*/


mutable IterType head;
mutable IterType tail;

/*
Quote:
Since there is no nil value for iterators (the_map.end() may
change any the time!), we have no good values for

head->second.prev> and <tail->second.next

Thus, we will treat these as *undefined*.
*/


void head_insert ( const KeyType & key, const DataType & data ) const {
EntryType entry ( data, head, the_map.end() );
IterType new_head = the_map.insert( PairType( key, entry ) ).first;
if ( the_map.size() > 1 ) {
// head is still the old head !
head->second.prev = new_head;
} else {
tail = new_head;
}
head = new_head;
}

void tail_insert ( const KeyType & key, const DataType & data ) const {
EntryType entry ( data, head, the_map.end() );
IterType new_tail = the_map.insert( PairType( key, entry ) ).first;
if ( the_map.size() > 1 ) {
// tail is still the old tail !
tail->second.next = new_tail;
} else {
head = new_tail;
}
tail = new_tail;
}

void move_to_front ( const IterType & pos ) const {
if ( pos != head ) {
if ( pos != tail ) {
pos->second.next->second.prev = pos->second.prev;
pos->second.prev->second.next = pos->second.next;
} else {
tail = pos->second.prev;
}
pos->second.next = head;
head->second.prev= pos;
head = pos;
}
}

/*
Quote:
Given the list operation, we can now implement some
basic routines.
*/

void remove_last ( void ) const {
assert( the_map.size() > 0 );
IterType old_tail ( tail );
if ( the_map.size() > 1 ) {
tail = old_tail->second.prev;
the_map.erase( old_tail );
} else {
the_map.erase( old_tail );
head = tail = the_map.end();
}
}

void shrink_to_fit ( void ) const {
while ( the_map.size() > max_capacity ) {
remove_last();
}
}

public:

typedef KeyType key_type;
typedef DataType mapped_type;
typedef std::pair< const KeyType, DataType > value_type;
typedef Compare key_compare;
typedef Function transform;
typedef Allocator alloc_type;

cache ( Function & _function, std::size_t new_capacity = Max_Size ) :
the_function( _function ),
the_map(),
max_capacity( new_capacity ),
head( the_map.end() ),
tail( the_map.end() )
{}

cache ( const cache & other ) {
copy( other, other.capacity() );
}

~cache ( void ) {
this->clear();
}

const cache & operator= ( const cache & other ) {
if ( this != &other ) {
copy( other, other.capacity() );
}
return( *this );
}

DataType operator() ( const KeyType & key ) const {
IterType entry_pos = the_map.find( key );
if ( entry_pos == the_map.end() ) {
// not cached
DataType result ( the_function( key ) );
head_insert( key, result );
this->shrink_to_fit();
return( result );
} else {
// cached
move_to_front( entry_pos );
return( head->second.data );
}
}

void clear ( void ) const {
the_map.clear();
head = the_map.end();
tail = the_map.end();
}

bool empty ( void ) const {
return( the_map.empty() );
}

std::size_t size ( void ) const {
return( the_map.size() );
}

void resize ( std::size_t new_capacity ) const {
max_capacity = new_capacity;
this->shrink_to_fit();
}

std::size_t capacity ( void ) const {
return( max_capacity );
}

void copy ( const cache & other, std::size_t length ) {
the_function = other.the_function;
the_map.clear();
max_capacity = length;
IterType iter = other.head;
for ( std::size_t i = std::min( length, other.size() );
i > 0;
--i ) {
tail_insert( iter->first, iter->second.data );
iter = iter->second.next;
}
}

void write ( std::ostream & ostr ) const {
ostr << '[';
if ( the_map.size() > 0 ) {
IterType iter = head;
while( true ) {
ostr << ' '
<< std::pair ( iter->first, iter->second.data );
if ( iter == tail ) {
break;
}
ostr << ',';
iter = iter->second.next;
}
}
ostr << " ]";
}

}; // cache<>

} // namespace kubux


template< typename KeyType,
typename DataType,
typename Function,
std::size_t Max_Size,
typename Compare,
typename Allocator
Quote:

std::ostream & operator<< ( std::ostream & ostr,

const kubux::cache<
KeyType,
DataType,
Function,
Max_Size,
Compare,
Allocator > & the_cache ) {
the_cache.write( ostr );
return( ostr );
}

#endif // __KUBUX_cache__

// end of file


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Back to top
Maciej Sobczak
Guest





PostPosted: Tue Aug 17, 2004 7:59 pm    Post subject: Re: cache - fixed size map Reply with quote



Hi,

Radu Grigore wrote:
Quote:
A cache can be implemented as a (hash_)map. In my case the growing
size might become a problem. A possible solution is to remove elements
(LFU, LRU) either periodically or, probably easier, in such a way as
to preserve a maximum size. It seems like a common problem and I'm
wondering if anyone has already done it: preferably as an open source
library, but an open source project (so I can read the code) is OK
too.

Take a look at: <http://www.msobczak.com/prog/downloads.html>
and get the agingcache.zip file.

Internally, it is a list that is kept within a given maximum size limit.
It is quite old code in the sense that today I would write it much
differently, but it may anyway give you some ideas for your own development.


--
Maciej Sobczak : http://www.msobczak.com/
Programming : http://www.msobczak.com/prog/


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Back to top
Display posts from previous:   
Post new topic   Reply to topic    C++Talk.NET Forum Index -> C++ Language (Moderated) All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2006 phpBB Group
SEO toolkit © 2004-2006 webmedic.