291 lines
8.2 KiB
C
291 lines
8.2 KiB
C
/* impulse_cache.c
|
|
|
|
This file is part of a program that implements a Software-Defined Radio.
|
|
|
|
Copyright (C) 2013, 2019, 2024 Warren Pratt, NR0V
|
|
Copyright (C) 2025 Richard Samphire, MW0LGE
|
|
|
|
This program 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.
|
|
|
|
This program 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 this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
The author can be reached by email at
|
|
|
|
warren@wpratt.com
|
|
mw0lge@grange-lane.co.uk
|
|
|
|
*/
|
|
//
|
|
//============================================================================================//
|
|
// Dual-Licensing Statement (Applies Only to Author's Contributions, Richard Samphire MW0LGE) //
|
|
// ------------------------------------------------------------------------------------------ //
|
|
// For any code originally written by Richard Samphire MW0LGE, or for any modifications //
|
|
// made by him, the copyright holder for those portions (Richard Samphire) reserves the //
|
|
// right to use, license, and distribute such code under different terms, including //
|
|
// closed-source and proprietary licences, in addition to the GNU General Public License //
|
|
// granted above. Nothing in this statement restricts any rights granted to recipients under //
|
|
// the GNU GPL. Code contributed by others (not Richard Samphire) remains licensed under //
|
|
// its original terms and is not affected by this dual-licensing statement in any way. //
|
|
// Richard Samphire can be reached by email at : mw0lge@grange-lane.co.uk //
|
|
//============================================================================================//
|
|
|
|
#define _CRT_SECURE_NO_WARNINGS
|
|
#include "comm.h"
|
|
|
|
/********************************************************************************************************
|
|
* *
|
|
* Impulse Cache implementation *
|
|
* *
|
|
********************************************************************************************************/
|
|
|
|
#if defined(_WIN64)
|
|
static const uint64_t FNV_OFFSET_BASIS_64 = 14695981039346656037ULL; // 0xcbf29ce484222325
|
|
static const uint64_t FNV_PRIME_64 = 1099511628211ULL; // 0x100000001b3
|
|
|
|
uint64_t fnv1a_hash64(const void* data, size_t len) {
|
|
const uint8_t* bytes = (const uint8_t*)data;
|
|
uint64_t hash = FNV_OFFSET_BASIS_64;
|
|
for (size_t i = 0; i < len; ++i) {
|
|
hash ^= bytes[i];
|
|
hash *= FNV_PRIME_64;
|
|
}
|
|
return hash;
|
|
}
|
|
#else
|
|
static const uint32_t FNV_OFFSET_BASIS_32 = 2166136261U; // 0x811C9DC5
|
|
static const uint32_t FNV_PRIME_32 = 16777619U; // 0x01000193
|
|
|
|
uint32_t fnv1a_hash32(const void* data, size_t len) {
|
|
const uint8_t* bytes = (const uint8_t*)data;
|
|
uint32_t hash = FNV_OFFSET_BASIS_32;
|
|
for (size_t i = 0; i < len; i++) {
|
|
hash ^= bytes[i];
|
|
hash *= FNV_PRIME_32;
|
|
}
|
|
return hash;
|
|
}
|
|
#endif
|
|
|
|
typedef struct _cache_entry {
|
|
HASH_T hash;
|
|
int N; // N complex entries in impulse. Leave as signed int as that is used everywhere
|
|
double* impulse;
|
|
struct _cache_entry* next;
|
|
} cache_entry;
|
|
|
|
static size_t _cache_counts[CACHE_BUCKETS] = { 0 };
|
|
static cache_entry* _cache_heads[CACHE_BUCKETS] = { NULL };
|
|
static CRITICAL_SECTION _cs_use_cache;
|
|
static int _run = 0;
|
|
static int _use_cache = 1;
|
|
|
|
void remove_impulse_cache_tail(size_t bucket)
|
|
{
|
|
if (bucket >= CACHE_BUCKETS) return;
|
|
|
|
cache_entry** pp = &_cache_heads[bucket];
|
|
|
|
while (*pp && (*pp)->next)
|
|
{
|
|
pp = &(*pp)->next;
|
|
}
|
|
|
|
if (*pp)
|
|
{
|
|
_aligned_free((*pp)->impulse);
|
|
_aligned_free(*pp);
|
|
*pp = NULL;
|
|
_cache_counts[bucket]--;
|
|
}
|
|
}
|
|
|
|
void free_impulse_cache(void)
|
|
{
|
|
for (size_t b = 0; b < CACHE_BUCKETS; ++b) {
|
|
cache_entry* e = _cache_heads[b];
|
|
while (e) {
|
|
cache_entry* next = e->next;
|
|
_aligned_free(e->impulse);
|
|
_aligned_free(e);
|
|
e = next;
|
|
}
|
|
_cache_heads[b] = NULL;
|
|
_cache_counts[b] = 0;
|
|
}
|
|
}
|
|
|
|
double* get_impulse_cache_entry(size_t bucket, HASH_T hash, int N)
|
|
{
|
|
if (!_run) return NULL;
|
|
|
|
int use;
|
|
EnterCriticalSection(&_cs_use_cache);
|
|
use = _use_cache;
|
|
LeaveCriticalSection(&_cs_use_cache);
|
|
|
|
if (!use || bucket >= CACHE_BUCKETS) return NULL;
|
|
|
|
// lru, least recently used, moves cache hit to head
|
|
// old cache entries will move towards the tail and eventually be dumped
|
|
cache_entry* prev = NULL;
|
|
cache_entry* e = _cache_heads[bucket];
|
|
|
|
while (e) {
|
|
if (e->hash == hash && e->N == N)
|
|
{
|
|
if (prev)
|
|
{
|
|
prev->next = e->next;
|
|
e->next = _cache_heads[bucket];
|
|
_cache_heads[bucket] = e;
|
|
}
|
|
double* imp = (double*) malloc0(e->N * sizeof(complex));
|
|
memcpy(imp, e->impulse, e->N * sizeof(complex));
|
|
return imp;
|
|
}
|
|
prev = e;
|
|
e = e->next;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void add_impulse_to_cache(size_t bucket, HASH_T hash, int N, double* impulse)
|
|
{
|
|
if (!_run) return;
|
|
|
|
int use;
|
|
EnterCriticalSection(&_cs_use_cache);
|
|
use = _use_cache;
|
|
LeaveCriticalSection(&_cs_use_cache);
|
|
|
|
if (!use || bucket >= CACHE_BUCKETS) return;
|
|
|
|
if (_cache_counts[bucket] >= MAX_CACHE_ENTRIES) remove_impulse_cache_tail(bucket);
|
|
|
|
cache_entry* e = malloc0(sizeof(cache_entry));
|
|
e->hash = hash;
|
|
e->N = N;
|
|
e->impulse = (double *) malloc0(N * sizeof(complex));
|
|
memcpy(e->impulse, impulse, N * sizeof(complex));
|
|
e->next = _cache_heads[bucket];
|
|
_cache_heads[bucket] = e;
|
|
_cache_counts[bucket]++;
|
|
}
|
|
|
|
PORT
|
|
int save_impulse_cache(const char* path)
|
|
{
|
|
if (!_run) return 0;
|
|
|
|
int use;
|
|
EnterCriticalSection(&_cs_use_cache);
|
|
use = _use_cache;
|
|
LeaveCriticalSection(&_cs_use_cache);
|
|
if (!use) return 0;
|
|
|
|
FILE* fp = fopen(path, "wb");
|
|
if (!fp) return -1;
|
|
uint32_t buckets = CACHE_BUCKETS;
|
|
if (fwrite(&buckets, sizeof(buckets), 1, fp) != 1) { fclose(fp); return -1; }
|
|
for (size_t b = 0; b < CACHE_BUCKETS; b++) {
|
|
uint32_t count = 0;
|
|
for (cache_entry* e = _cache_heads[b]; e; e = e->next) count++;
|
|
if (fwrite(&count, sizeof(count), 1, fp) != 1) { fclose(fp); return -1; }
|
|
for (cache_entry* e = _cache_heads[b]; e; e = e->next) {
|
|
if (fwrite(&e->hash, sizeof(HASH_T), 1, fp) != 1) { fclose(fp); return -1; }
|
|
if (fwrite(&e->N, sizeof(e->N), 1, fp) != 1) { fclose(fp); return -1; }
|
|
if (fwrite(e->impulse, sizeof(complex), e->N, fp) != (size_t)e->N) { fclose(fp); return -1; }
|
|
}
|
|
}
|
|
fclose(fp);
|
|
return 0;
|
|
}
|
|
|
|
PORT
|
|
int read_impulse_cache(const char* path)
|
|
{
|
|
if (!_run) return 0;
|
|
|
|
free_impulse_cache();
|
|
|
|
int use;
|
|
EnterCriticalSection(&_cs_use_cache);
|
|
use = _use_cache;
|
|
LeaveCriticalSection(&_cs_use_cache);
|
|
if (!use) return 0;
|
|
|
|
FILE* fp = fopen(path, "rb");
|
|
if (!fp) return -1;
|
|
uint32_t buckets;
|
|
if (fread(&buckets, sizeof(buckets), 1, fp) != 1) { fclose(fp); return -1; }
|
|
if (buckets != CACHE_BUCKETS) { fclose(fp); return -1; }
|
|
for (size_t b = 0; b < buckets; b++) {
|
|
uint32_t count;
|
|
if (fread(&count, sizeof(count), 1, fp) != 1) { fclose(fp); return -1; }
|
|
cache_entry* tail = NULL;
|
|
for (uint32_t i = 0; i < count; i++) {
|
|
HASH_T hash;
|
|
int N;
|
|
if (fread(&hash, sizeof(HASH_T), 1, fp) != 1) { fclose(fp); return -1; }
|
|
if (fread(&N, sizeof(N), 1, fp) != 1) { fclose(fp); return -1; }
|
|
double* data = (double*)malloc0(N * sizeof(complex));
|
|
if (fread(data, sizeof(complex), N, fp) != (size_t)N) { _aligned_free(data); fclose(fp); return -1; }
|
|
cache_entry* e = (cache_entry*)malloc0(sizeof(cache_entry));
|
|
e->hash = hash;
|
|
e->N = N;
|
|
e->impulse = data;
|
|
e->next = NULL;
|
|
if (tail)
|
|
tail->next = e;
|
|
else
|
|
_cache_heads[b] = e;
|
|
tail = e;
|
|
_cache_counts[b]++;
|
|
}
|
|
}
|
|
fclose(fp);
|
|
return 0;
|
|
}
|
|
|
|
PORT
|
|
void use_impulse_cache(int use)
|
|
{
|
|
EnterCriticalSection(&_cs_use_cache);
|
|
_use_cache = use;
|
|
LeaveCriticalSection(&_cs_use_cache);
|
|
}
|
|
|
|
PORT
|
|
void init_impulse_cache(int use)
|
|
{
|
|
//InitializeCriticalSection(&_cs_use_cache);
|
|
InitializeCriticalSectionAndSpinCount(&_cs_use_cache, 2500);
|
|
|
|
EnterCriticalSection(&_cs_use_cache);
|
|
_use_cache = use;
|
|
LeaveCriticalSection(&_cs_use_cache);
|
|
|
|
_run = 1;
|
|
}
|
|
|
|
PORT
|
|
void destroy_impulse_cache(void)
|
|
{
|
|
_run = 0;
|
|
|
|
DeleteCriticalSection(&_cs_use_cache);
|
|
|
|
free_impulse_cache();
|
|
}
|