wdsp/impulse_cache.c
Uladzimir Karpenka 89c8a0e2b5 first commit
2026-06-01 15:58:45 +03:00

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();
}