Files
test2/intern/itasc/Cache.cpp
2024-09-20 11:33:23 +02:00

653 lines
22 KiB
C++

/* SPDX-FileCopyrightText: 2009 Benoit Bolsee
*
* SPDX-License-Identifier: LGPL-2.1-or-later */
/** \file
* \ingroup intern_itasc
*/
#include "Cache.hpp"
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
namespace iTaSC {
CacheEntry::~CacheEntry()
{
for (unsigned int id = 0; id < m_count; id++)
m_channelArray[id].clear();
if (m_channelArray)
free(m_channelArray);
}
CacheItem *CacheChannel::_findBlock(CacheBuffer *buffer,
unsigned short timeOffset,
unsigned int *retBlock)
{
// the timestamp is necessarily in this buffer
unsigned int lowBlock, highBlock, midBlock;
if (timeOffset <= buffer->lookup[0].m_timeOffset) {
// special case: the item is in the first block, search from start
*retBlock = 0;
return &buffer->m_firstItem;
}
// general case, the item is in the middle of the buffer
// before doing a dycotomic search, we will assume that timestamp
// are regularly spaced so that we can try to locate the block directly
highBlock = buffer->m_lastItemPositionW >> m_positionToBlockShiftW;
lowBlock = midBlock = (timeOffset * highBlock) /
(buffer->m_lastTimestamp - buffer->m_firstTimestamp);
// give some space for security
if (lowBlock > 0)
lowBlock--;
if (timeOffset <= buffer->lookup[lowBlock].m_timeOffset) {
// bad guess, but we know this block is a good high block, just use it
highBlock = lowBlock;
lowBlock = 0;
}
else {
// ok, good guess, now check the high block, give some space
if (midBlock < highBlock)
midBlock++;
if (timeOffset <= buffer->lookup[midBlock].m_timeOffset) {
// good guess, keep that block as the high block
highBlock = midBlock;
}
}
// the item is in a different block, do a dycotomic search
// the timestamp is alway > lowBlock and <= highBlock
while (1) {
midBlock = (lowBlock + highBlock) / 2;
if (midBlock == lowBlock) {
// low block and high block are contigous, we can start search from the low block
break;
}
else if (timeOffset <= buffer->lookup[midBlock].m_timeOffset) {
highBlock = midBlock;
}
else {
lowBlock = midBlock;
}
}
assert(lowBlock != highBlock);
*retBlock = highBlock;
return CACHE_BLOCK_ITEM_ADDR(this, buffer, lowBlock);
}
void CacheChannel::clear()
{
CacheBuffer *buffer, *next;
for (buffer = m_firstBuffer; buffer != 0; buffer = next) {
next = buffer->m_next;
free(buffer);
}
m_firstBuffer = NULL;
m_lastBuffer = NULL;
if (initItem) {
free(initItem);
initItem = NULL;
}
}
CacheBuffer *CacheChannel::allocBuffer()
{
CacheBuffer *buffer;
if (!m_busy)
return NULL;
buffer = (CacheBuffer *)malloc(CACHE_BUFFER_HEADER_SIZE + (m_bufferSizeW << 2));
if (buffer) {
memset(buffer, 0, CACHE_BUFFER_HEADER_SIZE);
}
return buffer;
}
CacheItem *CacheChannel::findItemOrLater(unsigned int timestamp, CacheBuffer **rBuffer)
{
CacheBuffer *buffer;
CacheItem *item, *limit;
if (!m_busy)
return NULL;
if (timestamp == 0 && initItem) {
*rBuffer = NULL;
return initItem;
}
for (buffer = m_firstBuffer; buffer; buffer = buffer->m_next) {
if (buffer->m_firstFreePositionW == 0)
// buffer is empty, this must be the last and we didn't find the timestamp
return NULL;
if (timestamp < buffer->m_firstTimestamp) {
*rBuffer = buffer;
return &buffer->m_firstItem;
}
if (timestamp <= buffer->m_lastTimestamp) {
// the timestamp is necessarily in this buffer
unsigned short timeOffset = (unsigned short)(timestamp - buffer->m_firstTimestamp);
unsigned int highBlock;
item = _findBlock(buffer, timeOffset, &highBlock);
// now we do a linear search until we find a timestamp that is equal or higher
// we should normally always find an item but let's put a limit just in case
limit = CACHE_BLOCK_ITEM_ADDR(this, buffer, highBlock);
while (item <= limit && item->m_timeOffset < timeOffset)
item = CACHE_NEXT_ITEM(item);
assert(item <= limit);
*rBuffer = buffer;
return item;
}
// search in next buffer
}
return NULL;
}
CacheItem *CacheChannel::findItemEarlier(unsigned int timestamp, CacheBuffer **rBuffer)
{
CacheBuffer *buffer, *prevBuffer;
CacheItem *item, *limit, *prevItem;
if (!m_busy)
return NULL;
if (timestamp == 0)
return NULL;
for (prevBuffer = NULL, buffer = m_firstBuffer; buffer;
prevBuffer = buffer, buffer = buffer->m_next)
{
if (buffer->m_firstFreePositionW == 0)
// buffer is empty, this must be the last and we didn't find the timestamp
return NULL;
if (timestamp <= buffer->m_firstTimestamp) {
if (prevBuffer == NULL) {
// no item before, except the initial item
*rBuffer = NULL;
return initItem;
}
// the item is necessarily the last one of previous buffer
*rBuffer = prevBuffer;
return CACHE_ITEM_ADDR(prevBuffer, prevBuffer->m_lastItemPositionW);
}
if (timestamp <= buffer->m_lastTimestamp) {
// the timestamp is necessarily in this buffer
unsigned short timeOffset = (unsigned short)(timestamp - buffer->m_firstTimestamp);
unsigned int highBlock;
item = _findBlock(buffer, timeOffset, &highBlock);
// now we do a linear search until we find a timestamp that is equal or higher
// we should normally always find an item but let's put a limit just in case
limit = CACHE_BLOCK_ITEM_ADDR(this, buffer, highBlock);
prevItem = NULL;
while (item <= limit && item->m_timeOffset < timeOffset) {
prevItem = item;
item = CACHE_NEXT_ITEM(item);
}
assert(item <= limit && prevItem != NULL);
*rBuffer = buffer;
return prevItem;
}
// search in next buffer
}
// pass all buffer, the last item is the last item of the last buffer
if (prevBuffer == NULL) {
// no item before, except the initial item
*rBuffer = NULL;
return initItem;
}
// the item is necessarily the last one of previous buffer
*rBuffer = prevBuffer;
return CACHE_ITEM_ADDR(prevBuffer, prevBuffer->m_lastItemPositionW);
}
Cache::Cache() {}
Cache::~Cache()
{
CacheMap::iterator it;
for (it = m_cache.begin(); it != m_cache.end(); it = m_cache.begin()) {
deleteDevice(it->first);
}
}
int Cache::addChannel(const void *device, const char *name, unsigned int maxItemSize)
{
CacheMap::iterator it = m_cache.find(device);
CacheEntry *entry;
CacheChannel *channel;
unsigned int id;
if (maxItemSize > 0x3FFF0)
return -1;
if (it == m_cache.end()) {
// device does not exist yet, create a new entry
entry = new CacheEntry();
if (entry == NULL)
return -1;
if (!m_cache.insert(CacheMap::value_type(device, entry)).second) {
delete entry;
return -1;
}
}
else {
entry = it->second;
}
// locate a channel with the same name and reuse
for (channel = entry->m_channelArray, id = 0; id < entry->m_count; id++, channel++) {
if (channel->m_busy && !strcmp(name, channel->m_name)) {
// make this channel free again
deleteChannel(device, id);
// there can only be one channel with the same name
break;
}
}
for (channel = entry->m_channelArray, id = 0; id < entry->m_count; id++, channel++) {
// locate a free channel
if (!channel->m_busy)
break;
}
if (id == entry->m_count) {
// no channel free, create new channels
int newcount = entry->m_count + CACHE_CHANNEL_EXTEND_SIZE;
channel = (CacheChannel *)realloc(entry->m_channelArray, newcount * sizeof(CacheChannel));
if (channel == NULL)
return -1;
entry->m_channelArray = channel;
memset(&entry->m_channelArray[entry->m_count],
0,
CACHE_CHANNEL_EXTEND_SIZE * sizeof(CacheChannel));
entry->m_count = newcount;
channel = &entry->m_channelArray[id];
}
// compute the optimal buffer size
// The buffer size must be selected so that
// - it does not contain more than 1630 items (=1s of cache assuming 25 items per second)
// - it contains at least one item
// - it's not bigger than 256kb and preferably around 32kb
// - it a multiple of 4
unsigned int bufSize = 1630 * (maxItemSize + 4);
if (bufSize >= CACHE_DEFAULT_BUFFER_SIZE)
bufSize = CACHE_DEFAULT_BUFFER_SIZE;
if (bufSize < maxItemSize + 16)
bufSize = maxItemSize + 16;
bufSize = (bufSize + 3) & ~0x3;
// compute block size and offset bit mask
// the block size is computed so that
// - it is a power of 2
// - there is at least one item per block
// - there is no more than CACHE_LOOKUP_TABLE_SIZE blocks per buffer
unsigned int blockSize = bufSize / CACHE_LOOKUP_TABLE_SIZE;
if (blockSize < maxItemSize + 12)
blockSize = maxItemSize + 12;
// find the power of 2 that is immediately larger than blockSize
unsigned int m;
unsigned int pwr2Size = blockSize;
while ((m = (pwr2Size & (pwr2Size - 1))) != 0)
pwr2Size = m;
blockSize = (pwr2Size < blockSize) ? pwr2Size << 1 : pwr2Size;
// convert byte size to word size because all positions and size are expressed in 32 bit words
blockSize >>= 2;
channel->m_blockSizeW = blockSize;
channel->m_bufferSizeW = bufSize >> 2;
channel->m_firstBuffer = NULL;
channel->m_lastBuffer = NULL;
channel->m_busy = 1;
channel->initItem = NULL;
channel->m_maxItemSizeB = maxItemSize;
strncpy(channel->m_name, name, sizeof(channel->m_name));
channel->m_name[sizeof(channel->m_name) - 1] = 0;
channel->m_positionToOffsetMaskW = (blockSize - 1);
for (m = 0; blockSize != 1; m++, blockSize >>= 1)
;
channel->m_positionToBlockShiftW = m;
return (int)id;
}
int Cache::deleteChannel(const void *device, int id)
{
CacheMap::iterator it = m_cache.find(device);
CacheEntry *entry;
if (it == m_cache.end()) {
// device does not exist
return -1;
}
entry = it->second;
if (id < 0 || id >= (int)entry->m_count || !entry->m_channelArray[id].m_busy)
return -1;
entry->m_channelArray[id].clear();
entry->m_channelArray[id].m_busy = 0;
return 0;
}
int Cache::deleteDevice(const void *device)
{
CacheMap::iterator it = m_cache.find(device);
CacheEntry *entry;
if (it == m_cache.end()) {
// device does not exist
return -1;
}
entry = it->second;
delete entry;
m_cache.erase(it);
return 0;
}
void Cache::clearCacheFrom(const void *device, CacheTS timestamp)
{
CacheMap::iterator it = (device) ? m_cache.find(device) : m_cache.begin();
CacheEntry *entry;
CacheChannel *channel;
CacheBuffer *buffer, *nextBuffer, *prevBuffer;
CacheItem *item, *prevItem, *nextItem;
unsigned int positionW, block;
while (it != m_cache.end()) {
entry = it->second;
for (unsigned int ch = 0; ch < entry->m_count; ch++) {
channel = &entry->m_channelArray[ch];
if (channel->m_busy) {
item = channel->findItemOrLater(timestamp, &buffer);
if (item) {
if (!buffer) {
// this is possible if we return the special timestamp=0 item, delete all buffers
channel->clear();
}
else {
// this item and all later items will be removed, clear any later buffer
while ((nextBuffer = buffer->m_next) != NULL) {
buffer->m_next = nextBuffer->m_next;
free(nextBuffer);
}
positionW = CACHE_ITEM_POSITIONW(buffer, item);
if (positionW == 0) {
// this item is the first one of the buffer, remove the buffer completely
// first find the buffer just before it
nextBuffer = channel->m_firstBuffer;
prevBuffer = NULL;
while (nextBuffer != buffer) {
prevBuffer = nextBuffer;
nextBuffer = nextBuffer->m_next;
// we must quit this loop before reaching the end of the list
assert(nextBuffer);
}
free(buffer);
buffer = prevBuffer;
if (buffer == NULL)
// this was also the first buffer
channel->m_firstBuffer = NULL;
}
else {
// removing this item means finding the previous item to make it the last one
block = positionW >> channel->m_positionToBlockShiftW;
if (block == 0) {
// start from first item, we know it is not our item because positionW > 0
prevItem = &buffer->m_firstItem;
}
else {
// no need to check the current block, it will point to our item or a later one
// but the previous block will be a good start for sure.
block--;
prevItem = CACHE_BLOCK_ITEM_ADDR(channel, buffer, block);
}
while ((nextItem = CACHE_NEXT_ITEM(prevItem)) < item)
prevItem = nextItem;
// we must have found our item
assert(nextItem == item);
// now set the buffer
buffer->m_lastItemPositionW = CACHE_ITEM_POSITIONW(buffer, prevItem);
buffer->m_firstFreePositionW = positionW;
buffer->m_lastTimestamp = buffer->m_firstTimestamp + prevItem->m_timeOffset;
block = buffer->m_lastItemPositionW >> channel->m_positionToBlockShiftW;
buffer->lookup[block].m_offsetW = buffer->m_lastItemPositionW &
channel->m_positionToOffsetMaskW;
buffer->lookup[block].m_timeOffset = prevItem->m_timeOffset;
}
// set the channel
channel->m_lastBuffer = buffer;
if (buffer) {
channel->m_lastTimestamp = buffer->m_lastTimestamp;
channel->m_lastItemPositionW = buffer->m_lastItemPositionW;
}
}
}
}
}
if (device)
break;
++it;
}
}
void *Cache::addCacheItem(
const void *device, int id, unsigned int timestamp, void *data, unsigned int length)
{
CacheMap::iterator it = m_cache.find(device);
CacheEntry *entry;
CacheChannel *channel;
CacheBuffer *buffer, *next;
CacheItem *item;
unsigned int positionW, sizeW, block;
if (it == m_cache.end()) {
// device does not exist
return NULL;
}
entry = it->second;
if (id < 0 || id >= (int)entry->m_count || !entry->m_channelArray[id].m_busy)
return NULL;
channel = &entry->m_channelArray[id];
if (length > channel->m_maxItemSizeB)
return NULL;
if (timestamp == 0) {
// initial item, delete all buffers
channel->clear();
// and create initial item
item = NULL;
// we will allocate the memory, which is always pointer aligned => compute size
// with NULL will give same result.
sizeW = CACHE_ITEM_SIZEW(item, length);
item = (CacheItem *)calloc(sizeW, 4);
item->m_sizeW = sizeW;
channel->initItem = item;
}
else {
if (!channel->m_lastBuffer) {
// no item in buffer, insert item at first position of first buffer
positionW = 0;
if ((buffer = channel->m_firstBuffer) == NULL) {
buffer = channel->allocBuffer();
channel->m_firstBuffer = buffer;
}
}
else if (timestamp > channel->m_lastTimestamp) {
// this is the normal case: we are writing past lastest timestamp
buffer = channel->m_lastBuffer;
positionW = buffer->m_firstFreePositionW;
}
else if (timestamp == channel->m_lastTimestamp) {
// common case, rewriting the last timestamp, just reuse the last position
buffer = channel->m_lastBuffer;
positionW = channel->m_lastItemPositionW;
}
else {
// general case, write in the middle of the buffer, locate the timestamp
// (or the timestamp just after), clear this item and all future items,
// and write at that position
item = channel->findItemOrLater(timestamp, &buffer);
if (item == NULL) {
// this should not happen
return NULL;
}
// this item will become the last one of this channel, clear any later buffer
while ((next = buffer->m_next) != NULL) {
buffer->m_next = next->m_next;
free(next);
}
// no need to update the buffer, this will be done when the item is written
positionW = CACHE_ITEM_POSITIONW(buffer, item);
}
item = CACHE_ITEM_ADDR(buffer, positionW);
sizeW = CACHE_ITEM_SIZEW(item, length);
// we have positionW pointing where we can put the item
// before we do that we have to check if we can:
// - enough room
// - timestamp not too late
if ((positionW + sizeW > channel->m_bufferSizeW) ||
(positionW > 0 && timestamp >= buffer->m_firstTimestamp + 0x10000))
{
// we must allocate a new buffer to store this item
// but before we must make sure that the current buffer is consistent
if (positionW != buffer->m_firstFreePositionW) {
// This means that we were trying to write in the middle of the buffer.
// We must set the buffer right with positionW being the last position
// and find the item before positionW to make it the last.
block = positionW >> channel->m_positionToBlockShiftW;
CacheItem *previousItem, *nextItem;
if (block == 0) {
// start from first item, we know it is not our item because positionW > 0
previousItem = &buffer->m_firstItem;
}
else {
// no need to check the current block, it will point to our item or a later one
// but the previous block will be a good start for sure.
block--;
previousItem = CACHE_BLOCK_ITEM_ADDR(channel, buffer, block);
}
while ((nextItem = CACHE_NEXT_ITEM(previousItem)) < item)
previousItem = nextItem;
// we must have found our item
assert(nextItem == item);
// now set the buffer
buffer->m_lastItemPositionW = CACHE_ITEM_POSITIONW(buffer, previousItem);
buffer->m_firstFreePositionW = positionW;
buffer->m_lastTimestamp = buffer->m_firstTimestamp + previousItem->m_timeOffset;
block = buffer->m_lastItemPositionW >> channel->m_positionToBlockShiftW;
buffer->lookup[block].m_offsetW = buffer->m_lastItemPositionW &
channel->m_positionToOffsetMaskW;
buffer->lookup[block].m_timeOffset = previousItem->m_timeOffset;
// and also the channel, just in case
channel->m_lastBuffer = buffer;
channel->m_lastTimestamp = buffer->m_lastTimestamp;
channel->m_lastItemPositionW = buffer->m_lastItemPositionW;
}
// now allocate a new buffer
buffer->m_next = channel->allocBuffer();
if (buffer->m_next == NULL)
return NULL;
buffer = buffer->m_next;
positionW = 0;
item = &buffer->m_firstItem;
sizeW = CACHE_ITEM_SIZEW(item, length);
}
// all check passed, ready to write the item
item->m_sizeW = sizeW;
if (positionW == 0) {
item->m_timeOffset = 0;
buffer->m_firstTimestamp = timestamp;
}
else {
item->m_timeOffset = (unsigned short)(timestamp - buffer->m_firstTimestamp);
}
buffer->m_lastItemPositionW = positionW;
buffer->m_firstFreePositionW = positionW + sizeW;
buffer->m_lastTimestamp = timestamp;
block = positionW >> channel->m_positionToBlockShiftW;
buffer->lookup[block].m_offsetW = positionW & channel->m_positionToOffsetMaskW;
buffer->lookup[block].m_timeOffset = item->m_timeOffset;
buffer->m_lastItemPositionW = CACHE_ITEM_POSITIONW(buffer, item);
buffer->m_firstFreePositionW = buffer->m_lastItemPositionW + item->m_sizeW;
channel->m_lastBuffer = buffer;
channel->m_lastItemPositionW = positionW;
channel->m_lastTimestamp = timestamp;
}
// now copy the item
void *itemData = CACHE_ITEM_DATA_POINTER(item);
if (data)
memcpy(itemData, data, length);
return itemData;
}
const void *Cache::getPreviousCacheItem(const void *device, int id, unsigned int *timestamp)
{
CacheMap::iterator it;
CacheEntry *entry;
CacheChannel *channel;
CacheBuffer *buffer;
CacheItem *item;
if (device) {
it = m_cache.find(device);
}
else {
it = m_cache.begin();
}
if (it == m_cache.end()) {
// device does not exist
return NULL;
}
entry = it->second;
if (id < 0 || id >= (int)entry->m_count || !entry->m_channelArray[id].m_busy)
return NULL;
channel = &entry->m_channelArray[id];
if ((item = channel->findItemEarlier(*timestamp, &buffer)) == NULL)
return NULL;
*timestamp = (buffer) ? buffer->m_firstTimestamp + item->m_timeOffset : 0;
return CACHE_ITEM_DATA_POINTER(item);
}
const CacheItem *Cache::getCurrentCacheItemInternal(const void *device, int id, CacheTS timestamp)
{
CacheMap::iterator it = m_cache.find(device);
CacheEntry *entry;
CacheChannel *channel;
CacheBuffer *buffer;
CacheItem *item;
if (it == m_cache.end()) {
// device does not exist
return NULL;
}
entry = it->second;
if (id < 0 || id >= (int)entry->m_count || !entry->m_channelArray[id].m_busy)
return NULL;
channel = &entry->m_channelArray[id];
if ((item = channel->findItemOrLater(timestamp, &buffer)) == NULL)
return NULL;
if (buffer && buffer->m_firstTimestamp + item->m_timeOffset != timestamp)
return NULL;
return item;
}
const void *Cache::getCurrentCacheItem(const void *device, int channel, unsigned int timestamp)
{
const CacheItem *item = getCurrentCacheItemInternal(device, channel, timestamp);
return (item) ? CACHE_ITEM_DATA_POINTER(item) : NULL;
}
double *Cache::addCacheVectorIfDifferent(const void *device,
int channel,
CacheTS timestamp,
double *newdata,
unsigned int length,
double threshold)
{
const CacheItem *item = getCurrentCacheItemInternal(device, channel, timestamp);
unsigned int sizeW = CACHE_ITEM_SIZEW(item, length * sizeof(double));
if (!item || item->m_sizeW != sizeW)
return (double *)addCacheItem(device, channel, timestamp, newdata, length * sizeof(double));
double *olddata = (double *)CACHE_ITEM_DATA_POINTER(item);
if (!length)
return olddata;
double *ref = olddata;
double *v = newdata;
unsigned int i;
for (i = length; i > 0; --i) {
if (fabs(*v - *ref) > threshold)
break;
*ref++ = *v++;
}
if (i)
olddata = (double *)addCacheItem(device, channel, timestamp, newdata, length * sizeof(double));
return olddata;
}
} // namespace iTaSC