Files
secondo/Tools/Flob/NativeFlobCache.h
2026-01-23 17:03:45 +08:00

1544 lines
37 KiB
C++

/*
----
This file is part of SECONDO.
Copyright (C) 2009, University in Hagen, Department of Computer Science,
Database Systems for New Applications.
SECONDO is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
SECONDO is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with SECONDO; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
----
//paragraph [10] Title: [{\Large \bf \begin {center}] [\end {center}}]
//[ae] [\"a]
//[ue] [\"u]
//[oe] [\"o]
[10] Header File of Module NativeFlobCache
June 2010, C. D[ue]ntgen: Added comments
1 Overview
The ~NativeFlobCache~ is responsible for accelerating the access to data
referenced by native Flobs. Native Flobs are temporary Flobs, i.e. Flobs that
are created e.g. within local variables of a Flob-containing data type. The
data referenced by these native Flobs is maintained within a dedicated file, the
native Flob file, which is managed by the FlobManager.
Since temporary Flobs are often accessed repeatedly by many algorithms, e.g. to
append data, not every single access should rely on disk access. Thus, a main
memory buffer is used for cached read/ write access to the according data.
If a native Flob is destroyed or deleted by the FlobManager, its content is
never modified on disk, regardless whether is is marked modified or not.
The according
cache entries are just removed from the cache. This is due to the temporary
nature of native Flobs.
1.1 Slot Based Approach
The FlobCache's view of a Flob is a sequence of fixed size memory slices.
Each slice can be kept separately within the cache, which consists of a limited
number of fixed size memory buffers (slices), that are organized in an open
hashtable containing instances of class ~NativeCacheEntry~.
As a secondary organisation, a LRU-priority list linking the cache entries
is used, since LRU-policy is used to replace data if necessary.
If cached Flob data is modified, it is marked as ~changed~. Whenever changed
Flob data is removed from the Cache, it is written to disk. Unchanged Flob data
does not need to be written back to disk.
1.2 Whole-Flob-Based Approach
In this variant, the Flob data is organized using an AVLTree. A Flob is
copied to the cache either whole or not at all (e.g. if it does not fit into the
cache).
*/
#include "Flob.h"
#include "FlobManager.h"
#include <assert.h>
#include <iostream>
#include <cstdlib>
#undef __TRACE_ENTER__
#undef __TRACE_LEAVE__
#define __TRACE_ENTER__
#define __TRACE_LEAVE__
/*#define __TRACE_ENTER__ std::cerr << "Enter : " << \
__PRETTY_FUNCTION__ << std::endl;
#define __TRACE_LEAVE__ std::cerr << "Leave : " << \
__PRETTY_FUNCTION__ << "@" << __LINE__ << std::endl;
*/
//#undef SLOT_BASED_APPROACH
#define SLOT_BASED_APPROACH
#ifdef SLOT_BASED_APPROACH
/*
1 Class NativeCacheEntry
*/
class NativeCacheEntry{
public:
/*
1.1 Constructor
Creates a new entry for Flob __flob__ and slot number __slotNo__
with given slot size.
*/
NativeCacheEntry(const Flob& _flob, const size_t _slotNo, size_t _slotSize):
flobId(_flob.id), slotNo(_slotNo), tableNext(0), tablePrev(0),
lruPrev(0), lruNext(0), changed(false) {
assert(_flob.size>0);
size_t offset = _slotNo * _slotSize;
size = std::min(_slotSize, (_flob.getSize() - offset));
assert(offset <= _flob.getSize());
mem = (char*) malloc(size);
}
void resize(const size_t _newFlobSize, const size_t _slotSize){
size_t offset = slotNo * _slotSize;
assert(offset <= _newFlobSize);
size_t mySize = std::min(_slotSize, (_newFlobSize - offset));
if(mySize==size){
return;
}
if(mySize>0){
mem = (char*) realloc(mem, mySize);
} else {
free(mem);
mem = 0;
}
size = mySize;
}
/*
1.2 Destructor
Detroys an entry of a Flob.
*/
virtual ~NativeCacheEntry(){
if(mem){
free(mem);
}
mem = 0;
}
/*
1.3 hashValue
Returns a hash value for this entry. The hash value is used to quickly test
whether a slice of Flob data is within the cache.
*/
size_t hashValue(size_t tableSize){
return (flobId.hashValue() + slotNo) % tableSize;
}
/*
1.4 check for equality/ inequality
A pair (Flob-id, slot number) uniquely identifies a cache entry.
*/
bool operator==(const NativeCacheEntry& e){
return flobId == e.flobId &&
slotNo == e.slotNo;
}
bool operator!=(const NativeCacheEntry& e){
return !(*this == e);
}
bool matches(const Flob& f, const size_t slotNo) const{
return (f.id == flobId) && (this->slotNo == slotNo);
}
bool matches(const FlobId& fid, const size_t slotNo) const{
return (fid == flobId) && (this->slotNo == slotNo);
}
std::ostream& print(std::ostream& o) const{
o << "FlobId = " << flobId << ", slot = " << slotNo << ", size = " << size;
return o;
}
/*
1.5 Members
All members are private because only the ~FlobCache~ class knows about and uses
the ~NativeCacheEntry~ class.
*/
FlobId flobId;
size_t slotNo;
NativeCacheEntry* tableNext;
NativeCacheEntry* tablePrev;
NativeCacheEntry* lruPrev;
NativeCacheEntry* lruNext;
bool changed;
char* mem;
size_t size;
};
/*
1.6 Output operator
For simple output of an entry to an ostream.
*/
std::ostream& operator<<(std::ostream& o, const NativeCacheEntry& e){
o << "[" << e.flobId << ", " << e.slotNo << "]" ;
return o;
}
class NativeFlobCache{
public:
/*
2.1 Constructor
Creates a new cache with a given maximum size and a given slotsize.
*/
NativeFlobCache(size_t _maxSize, size_t _slotSize, size_t _avgSize):
maxSize(_maxSize), slotSize(_slotSize), usedSize(0), first(0), last(0) {
assert(maxSize > slotSize);
assert(_avgSize <= slotSize);
assert(slotSize);
tableSize = ((maxSize / _avgSize) * 2);
if(tableSize < 1u){
tableSize = 1u;
}
// initialize hashTable
hashtable = new NativeCacheEntry*[tableSize];
for(unsigned int i=0;i<tableSize; i++){
hashtable[i] = 0;
}
//assert(check());
}
/*
2.2 Destructor
destroys the cache.
*/
~NativeFlobCache(){
//assert(check());
clear();
delete[] hashtable;
hashtable = 0;
}
/*
2.3 clear
removes all entries from the cache.
*/
void clear(){
// first, kill entries from hashtable without deleting them from lru
//assert(check());
for(unsigned int i=0;i<tableSize;i++){
hashtable[i] =0;
}
NativeCacheEntry* entry = first;
size_t killed = 0;
while(entry){
NativeCacheEntry* victim = entry;
entry = entry->lruNext;
killed += victim->size;
delete victim;
}
first = 0;
last = 0;
usedSize = 0;
//assert(check());
}
/*
2.4 getData
Retrieves Flob data. First, it tries to get it from the cache.
If the data are not cached, the FlobManager is used to access the data and bring
it into the cache. Empty Flobs are not cached.
*/
bool getData(
const Flob& flob,
char* buffer,
const SmiSize offset,
const SmiSize size ){
if(size==0){ // nothing to do
return true;
}
assert(offset+size <= flob.size);
//assert(check());
size_t slotNo = offset / slotSize;
size_t slotOffset = offset % slotSize;
size_t bufferOffset(0);
while(bufferOffset < size){
if(!getDataFromSlot(flob, slotNo, slotOffset,
bufferOffset, size, buffer)){
std::cerr << "Warning getData failed" << std::endl;
//assert(check());
return false;
}
slotNo++;
slotOffset = 0; // slotoffset only neeeded for the first slot
//if(bufferOffset < size){
// std::cout << "Required second slot for Flob" << std::endl;
// std::cout << "bufferoffset: " << bufferOffset << std::endl;
// std::cout << "required size: " << size << std::endl;
// std::cout << "flob. size " << flob.size << std::endl;
// std::cout << "initial offset : " << offset << std::endl;
// }
}
//assert(check());
return true;
}
/*
2.5 putData
Updates the data of a Flob. If the data is not resident within the FlobCache,
it is brought to memory. All touched slices are marked ~modified~.
*/
bool putData(
const Flob& flob,
const char* buffer,
const SmiSize offset,
const SmiSize size) {
//assert(check());
size_t slotNo = offset / slotSize;
size_t slotOffset = offset % slotSize;
size_t bufferOffset(0);
while(bufferOffset < size){
putDataToFlobSlot(flob, slotNo, slotOffset, bufferOffset, size, buffer);
slotNo++;
slotOffset = 0;
}
//assert(check());
return true;
}
bool saveToDisk(Flob& flob, NativeCacheEntry* e){
if(e==0 || e->size==0){ // nothing to do
return true;
}
return FlobManager::getInstance().putData(flob, e->mem,
e->slotNo * slotSize,
e->size, true);
}
bool saveToDisk(const FlobId& flobId, NativeCacheEntry* e){
if(e==0 || e->size==0){ // nothing to do
return true;
}
return FlobManager::getInstance().putData(flobId, e->mem,
e->slotNo * slotSize, e->size);
}
/*
2.7 eraseSlot
Removes a slot from the cache. If saveChanges is set to true,
the slot is stored to disk if the ~modified~ flag is set.
*/
bool eraseSlot(Flob& flob, const size_t slotNo, const bool saveChanges){
//assert(check());
size_t index = (flob.id.hashValue() + slotNo) % tableSize;
NativeCacheEntry* e = hashtable[index];
while(e && !e->matches(flob, slotNo)){
e = e->tableNext;
}
if(e==0){ // slot not chached
return true;
}
if(saveChanges && e->changed){
if(!this->saveToDisk(flob,e)){
return false;
}
}
// remove slot from hashtable
if(hashtable[index] == e){ // e is the first entry in the table
hashtable[index] = e->tableNext;
if(hashtable[index]){
hashtable[index]->tablePrev = 0;
}
} else { // not the top entry
e->tablePrev->tableNext = e->tableNext;
if(e->tableNext){
e->tableNext->tablePrev = e->tablePrev;
}
}
e->tablePrev = 0;
e->tableNext = 0;
// remove from lru
if(e==first){
if(e==last){
first = 0;
last = 0;
} else {
first = e->lruNext;
first->lruPrev = 0;
e->lruNext = 0;
}
} else { // e is not the top lru element
if(e==last){
last = e->lruPrev;
last->lruNext = 0;
e->lruPrev = 0;
} else {
e->lruPrev->lruNext = e->lruNext;
e->lruNext->lruPrev = e->lruPrev;
e->lruPrev = 0;
e->lruNext = 0;
}
}
assert(usedSize >= e->size);
usedSize -= e->size;
delete e;
//assert(check());
return true;
}
/*
removes a slot from cache
*/
bool erase(Flob& flob, const bool saveChanges=false){
if(flob.size > 536870912){ // 512 MB
std::cerr << "Warning try to erase very big flob , size = "
<< flob.size <<std::endl;
}
//assert(check());
if(flob.size/slotSize < tableSize*4 ){
size_t numFlobs = flob.getSize() / slotSize;
for(unsigned int i = 0 ; i<= numFlobs; i++){
if(!eraseSlot(flob, i, saveChanges)){
//assert(check());
return false;
}
}
} else {
// scan the whole cache, because its faster in most cases
for(unsigned int i=0;i<tableSize;i++){
NativeCacheEntry* e = hashtable[i];
while(e){
if(e->flobId==flob.id){
if(!eraseSlot(flob,e->slotNo, saveChanges)){
return false;
} else {
e = hashtable[i];
}
} else {
e = e->tableNext;
}
}
}
}
return true;
}
/*
*/
bool create(Flob& flob){
//assert(check());
if(flob.size >0){
size_t num = flob.size/slotSize;
for(size_t i =0; i<num; i++){
NativeCacheEntry* e = new NativeCacheEntry(flob,i, slotSize );
if(e->size>0){
usedSize += e->size;
putAtFront(e);
size_t index = e->hashValue(tableSize);
if(hashtable[index]){
e->tableNext = hashtable[index];
hashtable[index]->tablePrev = e;
}
hashtable[index] = e;
reduce();
} else {
delete e;
}
}
}
//assert(check());
return true;
}
/*
2.7 getDataFromSlot
retrieves flob data for a specified flob from the cache and copies it to the
indicated position of a provided buffer.
*/
bool getDataFromSlot(const Flob& flob,
const size_t slotNo,
const size_t slotOffset,
size_t& bufferOffset,
const size_t size,
char* buffer) {
unsigned int index = (flob.hashValue() + slotNo) % tableSize;
if(hashtable[index]==0){
NativeCacheEntry* newEntry = createEntry(flob, slotNo,true);
if(newEntry){
hashtable[index] = newEntry;
usedSize += newEntry->size;
putAtFront(newEntry);
reduce(); // remove entries if too much memory is used
getData(newEntry, slotOffset, bufferOffset, size, buffer);
}
return true;
}
// hashtable[index] is already used
NativeCacheEntry* entry = hashtable[index];
while(entry->tableNext && !(entry->matches(flob.id, slotNo))){
entry = entry->tableNext;
}
if(!entry->matches(flob.id, slotNo)){ // no hit
NativeCacheEntry* newEntry = createEntry(flob, slotNo, true);
if(newEntry){
newEntry->tablePrev = entry;
entry->tableNext = newEntry;
assert(first);
putAtFront(newEntry);
usedSize += newEntry->size;
reduce();
getData(newEntry, slotOffset, bufferOffset, size, buffer);
}
return true;
} else { // hit, does not access disk data
getData(entry, slotOffset, bufferOffset, size, buffer);
bringToFront(entry);
return true;
}
}
/*
2.8 getData
Copies data from a given NativeCacheEntry to a buffer.
*/
void getData(NativeCacheEntry* entry,
size_t slotOffset,
size_t& bufferOffset,
const size_t size,
char* buffer){
size_t mb = size-bufferOffset; // missing bytes
size_t ab = entry->size - slotOffset; // bytes available in slot
size_t pb = std::min(mb,ab); // provided bytes
memcpy(buffer+bufferOffset, entry->mem + slotOffset, pb);
bufferOffset += pb;
}
/*
2.9 putData
puts data from a buffer to a given NativeCacheEntry and marks it modified.
*/
void putData(NativeCacheEntry* entry,
const size_t slotOffset,
size_t& bufferOffset,
const size_t size,
const char* buffer){
size_t mb = size-bufferOffset; // missing bytes
size_t ab = entry->size - slotOffset; // bytes available in slot
size_t pb = std::min(mb,ab); // provided bytes
if(pb>0){
memcpy(entry->mem + slotOffset, buffer+bufferOffset, pb);
bufferOffset += pb;
entry->changed=true;
}
}
/*
2.10 createEntry
produces a new NativeCacheEntry. If readData are set to be __true__ (default),
the slot data are loaded from disk using the ~FlobManager~.
*/
NativeCacheEntry* createEntry(const Flob& flob,
const size_t slotNo,
const bool readData=true) const{
NativeCacheEntry* res = new NativeCacheEntry(flob, slotNo, slotSize);
if(res->size==0){
delete res;
return 0;
}
if(readData){
FlobManager::getInstance().getData(flob,res->mem,
slotNo*slotSize, res->size, true);
res->changed = false;
}
return res;
}
/*
2.11 reduce
removes slots from the cache until the size is smaller than the cache's maximum
size.
*/
void reduce(){
while(last && usedSize > maxSize){
NativeCacheEntry* victim = last;
// remove from lru
last = victim->lruPrev;
last->lruNext = 0;
victim->lruPrev = 0;
assert(victim->lruNext == 0);
// remove from hashtable
if(victim->tablePrev){ // not the first entry in table
if(victim->tableNext){
victim->tablePrev->tableNext = victim->tableNext;
victim->tableNext->tablePrev = victim->tablePrev;
victim->tablePrev = 0;
victim->tableNext = 0;
} else { // last entry in table
victim->tablePrev->tableNext = 0;
victim->tablePrev = 0;
}
} else { // first entry in table
size_t index = victim->hashValue(tableSize);
assert(hashtable[index] == victim);
if(victim->tableNext==0){ // the only entry in hashtable
hashtable[index] = 0;
} else {
hashtable[index] = victim->tableNext;
victim->tableNext->tablePrev = 0;
victim->tableNext = 0;
}
}
assert(usedSize >= victim->size);
usedSize -= victim->size;
if(victim->changed){
saveToDisk(victim->flobId, victim);
}
delete victim;
}
if(!last){
first = 0;
}
}
/*
2.12 putDataToFlobSlot
puts data to a given slot. If the slot is not present in the
cache, it is created.
*/
bool putDataToFlobSlot(
const Flob& flob,
const size_t slotNo,
const size_t slotOffset,
size_t& bufferOffset,
const size_t size,
const char* buffer){
//assert(check());
size_t index = (flob.hashValue() + slotNo) % tableSize;
NativeCacheEntry* entry = hashtable[index];
while(entry && !entry->matches(flob.id, slotNo)){
entry = entry->tableNext;
}
if(!entry){ // slot not cached
entry = createEntry(flob, slotNo);
if(entry){
putAtFront(entry);
entry->tableNext = hashtable[index];
if(hashtable[index]){
hashtable[index]->tablePrev = entry;
}
hashtable[index] = entry;
usedSize += entry->size;
reduce();
} else {
return true;
}
}
putData(entry, slotOffset, bufferOffset, size,buffer);
return true;
}
bool resize(Flob& flob, const size_t newSize){
if(flob.size==0){
flob.size= newSize;
create(flob);
return true;
}
if(newSize==0){
erase(flob,false);
flob.size = 0;
return true;
}
if(newSize < flob.size){
size_t start = newSize/slotSize + 1;
size_t end = flob.size/slotSize + 1;
for(size_t i = start; i< end; i++){
eraseSlot(flob,i,false);
}
resizeSlot(flob,start-1,newSize);
flob.size=newSize;
return true;
}
// flob is enlarged
size_t start = flob.size/slotSize + 1;
resizeSlot(flob, start-1, newSize);
size_t end = newSize/slotSize +1;
flob.size = newSize;
for(size_t i=start;i<end;i++){
NativeCacheEntry* e = new NativeCacheEntry(flob,i,slotSize);
if(e->size>0){
usedSize += e->size;
putAtFront(e);
size_t index = e->hashValue(tableSize);
if(hashtable[index]){
e->tableNext = hashtable[index];
hashtable[index]->tablePrev = e;
}
hashtable[index] = e;
} else {
delete e;
}
}
reduce();
return true;
}
bool resizeSlot(Flob& flob, size_t slot, size_t newFlobSize){
size_t index = (flob.id.hashValue()+slot)%tableSize;
NativeCacheEntry* e = hashtable[index];
while( e && !e->matches(flob.id, slot)){
e = e->tableNext;
}
if(e){
size_t oldSize = e->size;
e->resize(newFlobSize, slotSize);
if(e->size==0){ // delete entry because size is 0
usedSize -= oldSize;
// delete from lru
if(first==e){
first = first->lruNext;
if(first){
first->lruPrev = 0;
} else {
last = 0;
}
} else if(e==last){
last = last->lruPrev;
last->lruNext = 0;
} else {
e->lruPrev->lruNext = e->lruNext;
e->lruNext->lruPrev = e->lruPrev;
}
e->lruNext=0;
e->lruPrev=0;
// delete from hashtbale
if(e==hashtable[index]){
hashtable[index] = e->tableNext;
if(hashtable[index]){
hashtable[index]->tablePrev = 0;
}
} else {
e->tablePrev->tableNext = e->tableNext;
if(e->tableNext){
e->tableNext->tablePrev = e->tablePrev;
}
}
e->tableNext = 0;
e->tablePrev = 0;
delete e;
} else {
usedSize = (usedSize + e->size) - oldSize;
}
}
return true;
}
/*
2.13 putAtFront
puts an unused NativeCacheEntry to the front of the LRU list.
*/
void putAtFront(NativeCacheEntry* newEntry){
assert(newEntry->lruPrev==0);
assert(newEntry->lruNext==0);
if(!first){
assert(!last);
first = newEntry;
last = newEntry;
} else {
newEntry->lruNext = first;
first->lruPrev = newEntry;
first = newEntry;
}
}
/*
2.14 bringToFront
moves an entry already present in the lru-list to the top of that list.
*/
void bringToFront(NativeCacheEntry* entry){
if(first==entry){ // already on front
return;
}
if(last==entry){ // the last element
assert(last->lruNext==0);
last= last->lruPrev;
last->lruNext=0;
entry->lruPrev=0;
entry->lruNext = first;
first->lruPrev = entry;
first = entry;
return;
}
// entry in in the middle of the list
assert(entry->lruPrev);
assert(entry->lruNext);
entry->lruPrev->lruNext = entry->lruNext;
entry->lruNext->lruPrev = entry->lruPrev;
entry->lruPrev = 0;
entry->lruNext = first;
first->lruPrev = entry;
first = entry;
}
/*
2.14 check
Debugging function.
*/
bool check(){
if(first==0 || last==0){
if(first!=last){
std::cerr << "inconsistence in lru list, first = " << (void*) first
<< " , last = " << (void*) last << std::endl;
return false;
}
for(unsigned int i=0;i< tableSize;i++){
if(hashtable[i]){
std::cerr << "lru is empty, but hashtable[" << i
<< "] contains an element" << std::endl;
return false;
}
}
// empty cache
return true;
}
// lru is not empty, check first and last element
if(first->lruPrev){
std::cerr << "lru: first has a predecessor" << std::endl;
return false;
}
if(last->lruNext){
std::cerr << "lru: last has a successor" << std::endl;
return false;
}
// check whether ech element in lru is also an element in hashtable
NativeCacheEntry* e = first;
int lrucount = 0;
size_t compSize(0);
while(e){
if(e->size > slotSize){
std::cerr << "entry found having a size > slotSize" << std::endl;
std::cerr << " slotSize = " << slotSize << " entry = " << (*e)
<< std::endl;
return false;
}
compSize += e->size ;
lrucount++;
size_t index = e->hashValue(tableSize);
if(!hashtable[index]){
std::cerr << "element " << (*e) << " stored in lru but hashtable["
<< index << " is null" << std::endl;
return false;
}
NativeCacheEntry* e2 = hashtable[index];
while(e2 && (*e)!=(*e2)){
e2 = e2->tableNext;
}
if(!e2){
std::cerr << "element " << (*e) << " stored in lru but not in hashtable["
<< index << "]" << std::endl;
return false;
}
e = e->lruNext;
}
if(compSize!=usedSize){
std::cerr << "difference in usedSize and stored size " << std::endl;
std::cerr << "usedSiez = " << usedSize << ", stored Size = "
<< compSize << std::endl;
return false;
}
// check hashtable
int tablecount = 0;
for(unsigned int i=0; i<tableSize;i++){
if(hashtable[i]){
if(hashtable[i]->tablePrev){
std::cerr << " hashtable[" << i << " has a predecessor" << std::endl;
}
e = hashtable[i];
while(e){
tablecount++;
if(e->hashValue(tableSize)!=i){
std::cerr << "element << " << (*e) << " has hashvalue "
<< e->hashValue(tableSize) << " but is stored at position "
<< i << std::endl;
return false;
}
if(e->tableNext){
if(e->tableNext->tablePrev!=e){
std::cerr << "error in tablelist found" << std::endl;
return false;
}
}
e = e->tableNext;
}
}
}
if(lrucount!=tablecount){
std::cerr << "lrucount = " << lrucount << " # tablecount = "
<< tablecount << std::endl;
return false;
}
return true;
}
private:
size_t maxSize; // maximum allocated memory
size_t slotSize; // size of a single slot
size_t usedSize; // currently used memory
NativeCacheEntry** hashtable; // open hash table
size_t tableSize; // size of the hashtable
NativeCacheEntry* first; // pointer to the first entry for lru
NativeCacheEntry* last; // pointer to the last entry for lru
};
#else // whole flob based approach
#include "AvlTree.h"
#include <set>
class NativeCacheEntry{
public:
FlobId flobId;
SmiSize size;
char* mem;
NativeCacheEntry* prev;
NativeCacheEntry* next;
bool changed;
NativeCacheEntry(): flobId(0,0,0,true), size(0),mem(0),prev(0),
next(0),changed(true){}
static void putAtFront(NativeCacheEntry* e,
NativeCacheEntry*& first,
NativeCacheEntry*& last){
if(first==0){ // first element in the list
assert(last==0);
first = e;
last = e;
e->next = 0;
e->prev = 0;
return;
}
assert(last!=0);
e->prev = 0;
e->next = first;
first->prev = e;
first = e;
}
static void connect(NativeCacheEntry* e1, NativeCacheEntry* e2,
NativeCacheEntry*& first, NativeCacheEntry*& last){
__TRACE_ENTER__;
if(e1==0 && e2==0){ // connect nothing
first = 0;
last = 0;
return;
}
if(e1==0){
e2->prev = 0;
first = e2;
return;
}
if(e2==0){
e1->next=0;
last = e1;
return;
}
e1->next = e2;
e2->prev = e1;
}
NativeCacheEntry(const Flob& flob, const bool alloc = false):
flobId(flob.id), size(flob.size),
mem(0), prev(0), next(0), changed(false) {
if(alloc){
mem = (char*) malloc(size);
}
}
bool operator==(const NativeCacheEntry& e) const{
__TRACE_ENTER__;
return flobId == e.flobId;
}
bool operator<(const NativeCacheEntry& e) const {
__TRACE_ENTER__;
return flobId < e.flobId;
}
bool operator>(const NativeCacheEntry& e) const{
__TRACE_ENTER__;
return flobId > e.flobId;
}
std::ostream& print(std::ostream& o) const{
o << " [ FlobId = " << flobId << ", "
<< " size = " << size << ", " ;
if(mem){
o << " mem : allocated ,";
} else {
o << " mem : NULL ,";
}
if(prev){
o << "prev = " << prev->flobId <<", ";
} else {
o << "prev = NULL, ";
}
if(next){
o << "next = " << next->flobId <<", ";
} else {
o << "next = NULL, ";
}
o << "changed = " << changed << "]";
return o;
}
};
std::ostream& operator<<(std::ostream& o, const NativeCacheEntry entry);
class NativeFlobCache{
public:
/*
~Constructor~
Creates an empy cache with a given capacity.
*/
NativeFlobCache(const size_t _maxSize): cache(),
maxSize(_maxSize),
size(0),
first(0),
last(0){
__TRACE_ENTER__;
// assert(check());
}
/*
~getData~
Returns data stored in the cahce. If the flob in not in cache and
even the flob cannot be put into the cache, the result is zero.
*/
bool getData(const Flob& flob,
char* dest,
const SmiSize offset,
const SmiSize size) {
__TRACE_ENTER__;
NativeCacheEntry* entry = useFlob(flob);
if(entry==0){ // entry not in cache and not cachable
return FlobManager::getInstance().getData(flob, dest,
offset, size, true);
}
assert(offset + size <= entry->size);
memcpy(dest, entry->mem + offset, size);
return true;
}
~NativeFlobCache(){
__TRACE_ENTER__;
// assert(check());
clear();
// assert(check());
}
/*
~clear~
Removes all entries from the cache.
*/
void clear(const bool saveChanges = false ){
__TRACE_ENTER__;
// assert(check());
if(size > maxSize){
std::cerr << "NativeFlobCache::clear(), size > maxSize detected"
<< std::endl;
std::cerr << "size = " << size << "maxSize = " << maxSize << std::endl;
}
NativeCacheEntry* e = first;
while(e){
if(saveChanges){
this->saveChanges(e);
}
free(e->mem);
size -= e->size;
e = e->next;
}
cache.Empty();
first = 0;
last = 0;
if(size!=0){
std::cerr << "FlobCache::clear() : size computation failed,"
<< " remaining size:"
<< size << std::endl;
size = 0;
}
}
/*
~erase~
Removes a given flob from the cache without
flushing changes to disk. If the flob was not cached,
the result will be false.
*/
bool erase(const Flob& victim){
__TRACE_ENTER__;
NativeCacheEntry finder(victim);
NativeCacheEntry* entry = (NativeCacheEntry*)cache.getMember(finder);
if(entry==0){
return false;
}
// delete from double linked list
NativeCacheEntry::connect(entry->prev, entry->next, first, last);
// give up memory
free(entry->mem);
entry->mem = 0;
size -= entry->size;
// delete from tree
cache.remove(finder);
return true;
}
/*
~create~
Allocates memory for the (uncached) flob.
*/
bool create(const Flob& flob){
__TRACE_ENTER__;
if((flob.size==0 ) || (flob.size>maxSize)){ // non-cachable sizes
return false;
}
NativeCacheEntry* entry = (NativeCacheEntry*)
cache.insert2(NativeCacheEntry(flob,true));
NativeCacheEntry::putAtFront(entry,first,last);
size += entry->size;
if(size>maxSize){
reduce();
}
return true;
}
/*
~eraseFromCache~
Stores chached made only in cache to disk and erases the flob from cache.
*/
bool eraseFromCache(const Flob& victim){
__TRACE_ENTER__;
return saveChanges(victim) && erase(victim);
}
/*
~putData~
Stores new data of a flob.
*/
bool putData(const Flob& dest,
const char* buffer,
const SmiSize& targetoffset,
const SmiSize& length){
__TRACE_ENTER__;
assert(targetoffset + length <= dest.size);
NativeCacheEntry* entry = useFlob(dest);
if(entry==0){
return FlobManager::getInstance().putData(dest,buffer,
targetoffset, length, true);
}
// put data into memory representation
memcpy(entry->mem+targetoffset, buffer, length);
entry->changed = true;
return true;
}
/*
~resize~
resizes the given flob using the size stored in the argument.
*/
bool resize(Flob& flob, SmiSize newSize){
__TRACE_ENTER__;
if(newSize > maxSize ){
eraseFromCache(flob); // saveChanges and kill cache
return FlobManager::getInstance().resize(flob, newSize,true);
}
if(newSize==0){
erase(flob); // kill cache without saving changes
flob.size = 0;
return true;
}
NativeCacheEntry finder(flob);
NativeCacheEntry* entry = (NativeCacheEntry*) cache.getMember(finder);
if(entry==0){
if(flob.size==0){
flob.size = newSize;
return create(flob);
}
flob.size = newSize;
bool ok = useFlob(flob)!=0; // chache flob
assert(ok);
return true;
}
NativeCacheEntry::connect(entry->prev, entry->next, first, last);
NativeCacheEntry::putAtFront(entry, first, last);
if(entry->size == newSize){ // no change
return true;
}
entry->mem = (char*)realloc(entry->mem, newSize);
size = size + - entry->size + newSize;
entry->size = newSize; // store size
flob.size = newSize;
return true;
}
private:
avltree::AVLTree<NativeCacheEntry> cache;
size_t maxSize;
size_t size;
NativeCacheEntry* first;
NativeCacheEntry* last;
/*
~check~
This is debugging function checking the cache for invalid state.
In the double linked list, each flobid must be unique.
The content of the avl tree and the list muts be the same.
If the chache is not empty, last and first cannot be null.
The size is the sum of all stored sizes.
*/
bool check(){
if(cache.IsEmpty()){
if(first!=0){
cout << "cache is empty, but first is not null !" << std::endl;
return false;
}
if(last!=0){
cout << "cache is empty, but last is not null !" << std::endl;
return false;
}
return true;
}
if(first==0){
cout << "cache is not empty, but first is null" << std::endl;
return false;
}
if(last==0){
cout << "cache is not empty, but last is null" << std::endl;
return false;
}
NativeCacheEntry* e = first;
int length = 0;
int csize = 0;
set<NativeCacheEntry> testset;
while(e){
length++;
csize += e->size;
if(testset.find(*e) != testset.end()){
cout << "Element found twice in the list " << (*e) << std::endl;
return false;
}
testset.insert(*e);
if(e->next==0){
if(last!=e){
cout << "last does not point to the last list element"
<< std::endl;
return false;
}
}
e = e->next;
}
// list structure ok, check content with the avltree
if(length!=cache.Size()){
cout << "different number of elements in cache and list " << std::endl;
cout << "listLength = " << length << std::endl;
cout << "treeSize = " << cache.Size() << std::endl;
return false;
}
if(csize!=(int)size){
cout << "different sizes), computed : " << csize
<< " , stored : " << size << std::endl;
return false;
}
set<NativeCacheEntry>::iterator it = testset.begin();
while(it!=testset.end()){
if(cache.getMember(*it) == 0){
cout << "Element " << (*it)
<< "stored in list but not in tree " << std::endl;
return false;
}
it++;
}
return true;
}
NativeCacheEntry* useFlob(const Flob& flob){
__TRACE_ENTER__;
NativeCacheEntry finder(flob);
NativeCacheEntry* entry1 = (NativeCacheEntry*) cache.getMember(finder);
if(entry1!=0){
// entry cached
NativeCacheEntry::connect(entry1->prev, entry1->next, first, last);
NativeCacheEntry::putAtFront(entry1, first, last);
return entry1;
}
// flob not already stored
if(flob.size>maxSize || flob.size==0){ // non-cachable sizes
return 0;
}
// bring entry to chache
NativeCacheEntry entry(flob);
entry.changed = false;
entry.mem = (char*) malloc(flob.size);
FlobManager::getInstance().getData(flob, entry.mem,0, flob.size, true);
NativeCacheEntry* stored = (NativeCacheEntry*) cache.insert2(entry);
NativeCacheEntry::putAtFront(stored, first, last);
size += flob.size;
if(size > maxSize){
reduce();
}
return stored;
}
bool saveChanges(const Flob& flob){
__TRACE_ENTER__;
NativeCacheEntry finder(flob);
NativeCacheEntry* entry = (NativeCacheEntry*) cache.getMember(finder);
if(entry==0){
return true;
}
return saveChanges(entry);
}
bool saveChanges(NativeCacheEntry* entry){
if(!entry->changed){
return true;
}
Flob flob(entry->flobId, entry->size);
bool res1 = FlobManager::getInstance().resize(flob, entry->size, true);
bool res = FlobManager::getInstance().putData(flob,
entry->mem,
0,
entry->size,
true);
if(res){
entry->changed=false;
}
return res&&res1;
}
bool reduce(){
__TRACE_ENTER__;
while(!cache.IsEmpty() && size > maxSize){
NativeCacheEntry* e = last;
saveChanges(last);
assert(e!=0);
// remove from list
last = e->prev;
if(last==0){
first = 0;
}else {
last->next = 0;
}
e->prev = 0;
e->next = 0;
free(e->mem);
e->mem=0;
size -= e->size;
bool rm = cache.remove(*e);
assert(rm);
}
return true;
}
};
#endif