//| Copyright: (C) 2020-2024 Kevin Larke //| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file. #include "cwCommon.h" #include "cwLog.h" #include "cwCommonImpl.h" #include "cwMem.h" #include "cwNbMem.h" #include "cwThread.h" /* ======================= 0000 void* mem - pointer to base of memory (also pointer to this block_t record) 0008 var_t* var - pointer to first var 0016 block_t* link - link to next block 0024 unsigned byteN - count of bytes following first 0028 unsigned - (reserved) 0032 unsigned byteN - data byte count (16) 0036 unsigned refN - reference count 0040 <----- first data word 0044 0048 0052 0056 unsigned byteN - data byte count (16) 0060 unsigned refN - reference count 0064 <----- first data word 0068 0072 0076 */ namespace cw { namespace nbmem { typedef struct var_str { std::atomic refN; // unsigned byteN; // byteN is the size of the data area of this variable (The total size is (sizeof(var_t) + byteN) unsigned reserved; // } var_t; typedef struct block_str { char* mem; // base of data memory for this block | block |var0|data0|var1|data1| |varx|datax| var_t* var; // struct block_str* link; // unsigned byteN; // mem bytes = sizeof(block_t) + byteN unsigned res0; // } block_t; typedef struct nbmem_str { unsigned blockByteN; // block_t* base; // } nbmem_t; nbmem_t* _handleToPtr( handle_t h ) { return handleToPtr(h); } rc_t _destroy( nbmem_t* p ) { rc_t rc = kOkRC; block_t* b = p->base; while( b!=nullptr ) { block_t* b0 = b->link; mem::release(b); b = b0; } mem::release(p); return rc; } block_t* _alloc_block( unsigned byteN ) { unsigned blockByteN = byteN + sizeof(block_t); char* mem = mem::allocZ(blockByteN); block_t* b = (block_t*)mem; b->mem = mem; b->byteN = byteN; b->link = nullptr; b->var = (var_t*)( mem + sizeof(block_t) ); b->var->byteN = byteN - sizeof(var_t); b->var->refN = 0; return b; } // void* _alloc_var( var_t* v, unsigned byteN, char* dataAreaEnd ) { // while( (char*)v < dataAreaEnd ) { char* b = (char*)v; // base of this variable memory char* d = b + sizeof(var_t); // ptr to variable data area // is this variable available? if( v->refN == 0 && v->byteN > byteN ) { v->refN = 1; // ATOMIC mark variable as unavailable - should set thread id NOT 1 // if there is more than sizeof(var_t) extra data space ... if( v->byteN > byteN + sizeof(var_t) ) { // ... then split the space with another variable unsigned byte0N = v->byteN; // setup the split var_t var_t* v0 = (var_t*)(d + byteN); v0->byteN = byte0N - (byteN + sizeof(var_t)); v0->refN = 0; // update the leading var size - last v->byteN = byteN; // ATOMIC release } return d; } // v is not available advance to the next var v = (var_t*)(d + v->byteN); } return nullptr; } } } cw::rc_t cw::nbmem::create( handle_t& h, unsigned blockByteN ) { rc_t rc = kOkRC; if((rc = destroy(h)) != kOkRC ) return rc; nbmem_t* p = mem::allocZ< nbmem_t >(1); p->base = _alloc_block( blockByteN ); p->blockByteN = blockByteN; h.set(p); return rc; } cw::rc_t cw::nbmem::destroy( handle_t& h ) { rc_t rc = kOkRC; if( !h.isValid() ) return rc; nbmem_t* p = _handleToPtr(h); if((rc = _destroy(p)) != kOkRC ) return rc; h.clear(); return rc; } void* cw::nbmem::alloc( handle_t h, unsigned byteN ) { nbmem_t* p = _handleToPtr(h); block_t* b = p->base; void* result = nullptr; // try to locate an available variable in the existing blocks for(; b!=nullptr; b=b->link) if((result = _alloc_var( b->var, byteN, b->mem + b->byteN )) != nullptr ) break; // if no available var space was found if( b == nullptr ) { // the new block size must be at least as large as the requested variable size unsigned blockByteN = std::max( p->blockByteN, byteN ); // allocate a new block block_t* b = _alloc_block( blockByteN ); // try the var allocation again result = _alloc_var( b->var, byteN, b->mem + b->byteN ); // link in the new block b->link = p->base; p->base = b; // ATOMIC } return result; } void cw::nbmem::release( handle_t h, void* v ) { nbmem_t* p = _handleToPtr(h); block_t* b = p->base; for(; b!=nullptr; b=b->link) { // if the memory to free is in this block if( (b->mem <= (char*)v) && ((char*)v < b->mem + (sizeof(block_t) + b->byteN)) ) { // v points to data space - back up by the size of var_t to point to the block header var_t* var = static_cast(v) - 1; var->refN -= 1; // ATOMIC // check for too many frees if( var->refN < 0 ) { var->refN = 0; // ATOMIC cwLogError(kInvalidOpRC,"An nbmem memory block was doube freed."); } return; } } cwLogError(kInvalidArgRC,"Invalid memory pointer passed to nbmem::release()."); } void cw::nbmem::report( handle_t h ) { nbmem_t* p = _handleToPtr(h); unsigned i = 0; block_t* b = p->base; for(; b!=nullptr; b=b->link) { printf("mem:%p byteN:%i var:%p link:%p\n",b->mem,b->byteN,b->var,b->link); var_t* v = b->var; char* blockEnd = b->mem + sizeof(block_t) + b->byteN; while( (char*)v < blockEnd ) { void* data = (v+1); if( v->refN > 0 ) { unsigned* val = static_cast(data); var_t* var = static_cast(data) - 1; printf("%i %i bN:%i ref:%li 0x%p\n",i,*val, var->byteN, var->refN, val); i += 1; } v = (var_t*)( static_cast(data) + v->byteN); } } } cw::rc_t cw::nbmem::test() { unsigned preallocMemN = 64; unsigned N = 64; void* varA[ N ]; handle_t h; rc_t rc; // create a nbmem mgr object if((rc = create( h, preallocMemN )) == kOkRC ) { // allocate N blocks of memory of length sizeof(unsigned) for(unsigned i=0; i(arg); // select a random variable index unsigned idx = lround((double)rand() * (r->varN-1) / RAND_MAX); cwAssert( 0 <= idx && idx < r->varN ); // if the variable has not been allocated then allocate it ... if( r->varA[idx] == nullptr ) { r->varA[idx] = alloc( r->ctx->nbmH, 10 ); } else // ... otherwise free it { release( r->ctx->nbmH, r->varA[idx] ); r->varA[idx] = nullptr; } return true; } } } cw::rc_t cw::nbmem::test_multi_threaded() { unsigned preallocMemN = 1024; unsigned threadN = 10; unsigned threadVarN = 20; rc_t rc = kOkRC; test_ctx_t ctx; ctx.threadN = threadN; ctx.threadA = mem::allocZ(ctx.threadN); if((rc = create( ctx.nbmH, preallocMemN )) != kOkRC ) goto errLabel; // create ctx.threadN threads for(unsigned i=0; i(threadVarN); ctx.threadA[i].varN = threadVarN; if((rc = thread::create(ctx.threadA[i].threadH, _test_thread_func, ctx.threadA + i, "nb_mem" )) != kOkRC ) break; } if( rc == kOkRC ) { // start each thread for(unsigned i=0; i