├── .gitignore ├── index.js ├── binding.gyp ├── test ├── stability2.js ├── counter.js ├── test3.js ├── test2.js ├── stability.js ├── test.js └── benchmark.js ├── package.json ├── src ├── bson.h ├── memcache.h ├── lock.h ├── bson.cc ├── binding.cc └── memcache.cc └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.swp 3 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | process.dlopen(module, require.resolve('./build/Release/binding.node')); 2 | 3 | exports.SIZE_DEFAULT = 6; 4 | exports.SIZE_64 = 6; 5 | exports.SIZE_128 = 7; 6 | exports.SIZE_256 = 8; 7 | exports.SIZE_512 = 9; 8 | exports.SIZE_1K = 10; 9 | exports.SIZE_2K = 11; 10 | exports.SIZE_4K = 12; 11 | exports.SIZE_8K = 13; 12 | exports.SIZE_16K = 14; 13 | 14 | if(process.mainModule === module && process.argv[2] === 'release') { 15 | process.argv.slice(3).forEach(exports.release); 16 | } -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "binding", 5 | "sources": [ 6 | "src/binding.cc", 7 | "src/memcache.cc", 8 | "src/bson.cc" 9 | ], 10 | "include_dirs": [ 11 | " 5 | 6 | namespace bson { 7 | typedef enum { 8 | Null, 9 | Undefined, 10 | True, 11 | False, 12 | Int32, 13 | Number, 14 | String, 15 | Array, 16 | Object, 17 | ObjectRef 18 | } TYPES; 19 | 20 | class BSONValue { 21 | private: 22 | size_t length; 23 | uint8_t*pointer; 24 | public: 25 | uint8_t cache[32]; 26 | BSONValue(v8::Handle value); 27 | ~BSONValue(); 28 | inline const uint8_t* Data() { return pointer; } 29 | inline size_t Length () { return length; } 30 | }; 31 | 32 | v8::Local parse(const uint8_t* data); 33 | 34 | 35 | class BSONParser { 36 | private: 37 | uint8_t tmp[1024]; 38 | public: 39 | uint8_t* val; 40 | size_t valLen; 41 | 42 | inline BSONParser() : val(tmp), valLen(sizeof(tmp)) {} 43 | inline ~BSONParser() { 44 | if(valLen > sizeof(tmp)) { 45 | delete[] val; 46 | } 47 | } 48 | 49 | inline v8::Local parse() { 50 | return bson::parse(val); 51 | } 52 | }; 53 | 54 | } 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /test/test3.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | var binding = require('../index.js'); 4 | /* 5 | try { 6 | var obj = new binding.Cache("test", 525312); 7 | } catch (e) { 8 | console.error(e); 9 | } 10 | 11 | try { 12 | var obj = new binding.Cache("test", 4294967295); 13 | } catch (e) { 14 | console.error(e); 15 | } 16 | */ 17 | 18 | var obj = new binding.Cache("test3", 512<<10, binding.SIZE_16K); 19 | obj.foo = "bar"; 20 | 21 | assert.strictEqual(obj.foo, "bar"); 22 | 23 | obj.env = process.env; 24 | 25 | // free block 26 | obj.env = 0; 27 | 28 | // increase block 29 | obj.env = [process.env, process.env]; 30 | 31 | assert.deepEqual(Object.keys(obj).slice(-2), ['foo', 'env']); 32 | 33 | var test = [process.env, process.env]; 34 | 35 | test[2] = {'test':test}; 36 | obj.env = test; 37 | 38 | test = obj.env; 39 | assert.strictEqual(test, test[2].test); 40 | assert.strictEqual(test[0], test[1]); 41 | 42 | delete obj.foo; 43 | assert.ifError('foo' in obj); 44 | assert.strictEqual(obj.foo, undefined); 45 | 46 | 47 | console.time('LRU cache replacement'); 48 | for(var i = 0; i < 17; i++) { 49 | obj['test' + i] = i; 50 | assert.strictEqual(obj['test' + i] , i); 51 | } 52 | console.timeEnd('LRU cache replacement'); 53 | assert.ifError('test0' in obj); 54 | 55 | var longData = Array(8192).join('abcdefgh'); 56 | 57 | obj.test = longData; 58 | assert.strictEqual(obj.test, longData); 59 | -------------------------------------------------------------------------------- /test/test2.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | var binding = require('../index.js'); 4 | /* 5 | try { 6 | var obj = new binding.Cache("test", 525312); 7 | } catch (e) { 8 | console.error(e); 9 | } 10 | 11 | try { 12 | var obj = new binding.Cache("test", 4294967295); 13 | } catch (e) { 14 | console.error(e); 15 | } 16 | */ 17 | 18 | var obj = new binding.Cache("test2", 512<<10, binding.SIZE_64); 19 | 20 | 21 | obj.foo = "bar"; 22 | 23 | assert.strictEqual(obj.foo, "bar"); 24 | 25 | obj.env = process.env; 26 | 27 | // free block 28 | obj.env = 0; 29 | 30 | // increase block 31 | obj.env = [process.env, process.env]; 32 | 33 | assert.deepEqual(Object.keys(obj).slice(-2), ['foo', 'env']); 34 | 35 | var test = [process.env, process.env]; 36 | 37 | test[2] = {'test':test}; 38 | obj.env = test; 39 | 40 | test = obj.env; 41 | assert.strictEqual(test, test[2].test); 42 | assert.strictEqual(test[0], test[1]); 43 | 44 | delete obj.foo; 45 | assert.ifError('foo' in obj); 46 | assert.strictEqual(obj.foo, undefined); 47 | 48 | 49 | console.time('LRU cache replacement'); 50 | for(var i = 0; i < 4097; i++) { 51 | obj['test' + i] = i; 52 | assert.strictEqual(obj['test' + i] , i); 53 | } 54 | console.timeEnd('LRU cache replacement'); 55 | assert.ifError('test0' in obj); 56 | 57 | var longData = Array(8192).join('abcdefgh'); 58 | 59 | obj.test = longData; 60 | assert.strictEqual(obj.test, longData); 61 | -------------------------------------------------------------------------------- /src/memcache.h: -------------------------------------------------------------------------------- 1 | #ifndef MEMCACHE_H_ 2 | #define MEMCACHE_H_ 3 | 4 | #ifndef _WIN32 5 | typedef int HANDLE; 6 | #else 7 | #include 8 | #endif 9 | 10 | #define HEADER_SIZE 262144 11 | 12 | namespace cache { 13 | bool init(void* ptr, uint32_t blocks, uint32_t block_size_shift, bool forced); 14 | 15 | int set(void* ptr, HANDLE fd, const uint16_t* key, size_t keyLen, const uint8_t* val, size_t valLen, uint8_t** oldval = NULL, size_t* oldvalLen = NULL); 16 | 17 | void _enumerate(void* ptr, HANDLE fd, void* enumerator, void(* callback)(void*,uint16_t*,size_t)); 18 | 19 | void _dump(void* ptr, HANDLE fd, void* dumper, void(* callback)(void*,uint16_t*,size_t,uint8_t*)); 20 | 21 | template 22 | inline void enumerate(void* ptr, HANDLE fd, T* enumerator, void(* callback)(T*,uint16_t*,size_t)) { 23 | _enumerate(ptr, fd, enumerator, (void(*)(void*,uint16_t*,size_t)) callback); 24 | } 25 | 26 | template 27 | inline void dump(void* ptr, HANDLE fd, T* dumper, void(* callback)(T*,uint16_t*,size_t,uint8_t*)) { 28 | _dump(ptr, fd, dumper, (void(*)(void*,uint16_t*,size_t,uint8_t*)) callback); 29 | } 30 | 31 | void get(void* ptr, HANDLE fd, const uint16_t* key, size_t keyLen, uint8_t*& val, size_t& valLen); 32 | 33 | void fast_get(void* ptr, HANDLE fd, const uint16_t* key, size_t keyLen, uint8_t*& val, size_t& valLen); 34 | 35 | bool contains(void* ptr, HANDLE fd, const uint16_t* key, size_t keyLen); 36 | 37 | bool unset(void* ptr, HANDLE fd, const uint16_t* key, size_t keyLen); 38 | 39 | void clear(void* ptr, HANDLE fd); 40 | 41 | int32_t increase(void* ptr, HANDLE fd, const uint16_t* key, size_t keyLen, int32_t increase_by); 42 | 43 | } 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /test/stability.js: -------------------------------------------------------------------------------- 1 | var cp = require('child_process'); 2 | 3 | if(process.argv[2] !== 'worker') { 4 | var workers = [], spawned = 0; 5 | for(var i = 0; i < 10; i++) { 6 | spawned++; 7 | var worker = workers[i] = cp.spawn(process.execPath, [__filename, 'worker'], {stdio: 'inherit'}); 8 | } 9 | process.on('SIGINT', function () { 10 | for(var i = 0; i < 10; i++) { 11 | workers[i].kill('SIGINT'); 12 | } 13 | clearInterval(pid); 14 | console.log(spawned + ' children spawned'); 15 | }); 16 | 17 | var pid = setInterval(function() { 18 | var i = Math.random() * 10 | 0; 19 | workers[i].kill('SIGKILL'); 20 | spawned++; 21 | var worker = workers[i] = cp.spawn(process.execPath, [__filename, 'worker'], {stdio: 'inherit'}); 22 | }, 100); 23 | 24 | return; 25 | } 26 | 27 | var n = 0, stopped = false; 28 | var binding = require('../index.js'); 29 | var obj = new binding.Cache("test", 512<<10, binding.SIZE_1K); 30 | 31 | 32 | process.on('SIGINT', function () { 33 | console.log(process.pid, n); 34 | stopped = true; 35 | }); 36 | 37 | function sched() { 38 | n++; 39 | for(var i = 0; i < 1000; i++) { 40 | switch(Math.random() * 10 | 0) { 41 | case 0: obj.foo = !obj.foo; break; 42 | case 1: obj.foo = Date.now() + Math.random(); break; 43 | case 2: obj.foo = process.env; break; 44 | case 3: delete obj.foo; break; 45 | case 4: Object.keys(obj); break; 46 | case 5: obj.foo = null; break; 47 | case 6: 'foo' in obj; break; 48 | default: obj.foo; break; 49 | } 50 | } 51 | stopped || setTimeout(sched, 0); 52 | } 53 | 54 | sched(); 55 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | var binding = require('../index.js'); 4 | /* 5 | try { 6 | var obj = new binding.Cache("test", 525312); 7 | } catch (e) { 8 | console.error(e); 9 | } 10 | 11 | try { 12 | var obj = new binding.Cache("test", 4294967295); 13 | } catch (e) { 14 | console.error(e); 15 | } 16 | */ 17 | 18 | var obj = new binding.Cache("test", 512<<10, binding.SIZE_1K); 19 | binding.clear(obj); 20 | 21 | console.log('set obj.foo'); 22 | obj.foo = "bar"; 23 | 24 | assert.strictEqual(obj.foo, "bar"); 25 | 26 | obj.env = process.env; 27 | 28 | // free block 29 | obj.env = 0; 30 | 31 | var nested = [process.env, process.env, {}]; 32 | obj.nested = nested; 33 | 34 | console.log(binding.dump(obj)); 35 | 36 | assert.deepEqual(binding.dump(obj), {foo: 'bar', env: 0, nested: nested}) 37 | 38 | nested[2].test = nested; 39 | // increase block 40 | obj.nested = nested; 41 | 42 | assert.deepEqual(Object.keys(obj), ['foo', 'env', 'nested']); 43 | 44 | var result = obj.nested; 45 | assert.strictEqual(result, result[2].test); 46 | assert.strictEqual(result[0], result[1]); 47 | 48 | for(var k in obj) { 49 | console.log(k, obj[k]); 50 | } 51 | 52 | 53 | obj.foo2 = 1234; 54 | assert.deepEqual(binding.dump(obj, 'foo'), {foo: 'bar', foo2: 1234}); 55 | assert.deepEqual(binding.dump(obj, 'e'), {env: 0}); 56 | 57 | // test exchange 58 | assert.strictEqual(binding.exchange(obj, 'foo2', 5678), 1234); 59 | assert.strictEqual(obj.foo2, 5678); 60 | assert.strictEqual(binding.exchange(obj, 'blah', 'mew'), undefined); 61 | assert.strictEqual(obj.blah, 'mew'); 62 | assert.strictEqual(binding.fastGet(obj, 'blah'), 'mew'); 63 | assert.strictEqual(binding.fastGet(obj, 'blah~'), undefined); 64 | 65 | delete obj.foo; 66 | assert.ifError('foo' in obj); 67 | assert.strictEqual(obj.foo, undefined); 68 | 69 | console.time('LRU cache replacement'); 70 | for(var i = 0; i < 1000000; i+=3) { 71 | obj['test' + i] = i; 72 | assert.strictEqual(obj['test' + i] , i); 73 | } 74 | console.timeEnd('LRU cache replacement'); 75 | assert.ifError('test0' in obj); 76 | 77 | var longData = Array(15).join(Array(64).join('abcdefgh')); // 17 blocks 78 | 79 | obj.test = longData; 80 | assert.strictEqual(obj.test, longData); 81 | -------------------------------------------------------------------------------- /src/lock.h: -------------------------------------------------------------------------------- 1 | #ifndef _LOCK_H 2 | #define _LOCK_H 3 | 4 | #include 5 | 6 | typedef int32_t mutex_t; 7 | 8 | #ifdef __GNUC__ 9 | 10 | #if GCC_VERSION > 46300 11 | 12 | template 13 | inline t cmpxchg(t& var, t oldval, t newval) { 14 | __atomic_compare_exchange_n(&var, &oldval, newval, 0, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE); 15 | return oldval; 16 | } 17 | 18 | #define xchg(var, newval) __atomic_exchange_n(&var, newval, __ATOMIC_ACQ_REL); 19 | #define atomic_dec(var) __atomic_fetch_sub(&var, 1, __ATOMIC_ACQ_REL) 20 | 21 | #else 22 | 23 | #define cmpxchg(var, oldval, newval) __sync_val_compare_and_swap(&var, oldval, newval) 24 | 25 | #define xchg(var, newval) __sync_lock_test_and_set(&var, newval) 26 | #define atomic_dec(var) __sync_fetch_and_sub (&var, 1) 27 | 28 | #endif 29 | #endif 30 | 31 | 32 | #define TSL(mutex) xchg(mutex, 1) 33 | #define SPIN(mutex) while(TSL(mutex)) 34 | 35 | #ifdef linux 36 | /* Use futex to implement mutex in linux */ 37 | #include 38 | #include 39 | #include 40 | 41 | #define futex_wait(addr, val) syscall(SYS_futex, addr, FUTEX_WAIT, val, 0, 0, 0) 42 | #define futex_wake(addr, count) syscall(SYS_futex, addr, FUTEX_WAKE, count, 0, 0, 0) 43 | 44 | inline void LOCK(mutex_t& mutex) { 45 | mutex_t c; 46 | if((c = cmpxchg(mutex, 0, 1))) { 47 | // locked, c=2 if there is someone waiting 48 | if(c != 2) c = xchg(mutex, 2); 49 | while(c) { 50 | futex_wait(&mutex, 2); 51 | c = xchg(mutex, 2); 52 | } 53 | } 54 | } 55 | 56 | inline void UNLOCK(mutex_t& mutex) { 57 | if(atomic_dec(mutex) != 1) { 58 | mutex = 0; 59 | futex_wake(&mutex, 1); 60 | } 61 | } 62 | 63 | #else 64 | #define LOCK(mutex) SPIN(mutex) while(mutex) 65 | #define UNLOCK(mutex) __sync_lock_release(&mutex) 66 | #endif 67 | 68 | typedef struct { 69 | mutex_t count_lock; 70 | mutex_t mutex; 71 | uint32_t readers; 72 | } rw_lock_t; 73 | 74 | typedef struct read_lock_s { 75 | rw_lock_t& lock; 76 | inline read_lock_s(rw_lock_t& lock) : lock(lock) { 77 | LOCK(lock.count_lock); 78 | if(++lock.readers == 1) { 79 | LOCK(lock.mutex); 80 | } 81 | UNLOCK(lock.count_lock); 82 | } 83 | inline ~read_lock_s() { 84 | LOCK(lock.count_lock); 85 | if(--lock.readers == 0) { 86 | UNLOCK(lock.mutex); 87 | } 88 | UNLOCK(lock.count_lock); 89 | } 90 | } read_lock_t; 91 | 92 | typedef struct write_lock_s { 93 | rw_lock_t& lock; 94 | inline write_lock_s(rw_lock_t& lock) : lock(lock) { 95 | LOCK(lock.mutex); 96 | } 97 | inline ~write_lock_s() { 98 | UNLOCK(lock.mutex); 99 | } 100 | } write_lock_t; 101 | 102 | #endif 103 | -------------------------------------------------------------------------------- /test/benchmark.js: -------------------------------------------------------------------------------- 1 | var binding = require('../index.js'); 2 | 3 | var _t, hrtime = process.hrtime; 4 | function begin() { 5 | _t = hrtime(); 6 | } 7 | 8 | function end() { 9 | _t = hrtime(_t); 10 | return (_t[0] * 1e3 + _t[1] / 1e6).toFixed(2) 11 | } 12 | 13 | // test plain object 14 | var plain = {}; 15 | begin(); 16 | for(var i = 0; i < 1e6; i++) { 17 | plain['test' + (i & 127)] = i; 18 | } 19 | 20 | console.log('write plain obj 100w times: %sms', end()); 21 | 22 | // test shared cache 23 | var obj = new binding.Cache("benchmark", 1048576); 24 | begin(); 25 | for(var i = 0; i < 1e6; i++) { 26 | obj['test' + (i & 127)] = i; 27 | } 28 | console.log('write shared cache 100w times: %sms', end()); 29 | 30 | // test read existing key 31 | begin(); 32 | for(var i = 0; i < 1e6; i++) { 33 | plain['test' + (i & 127)]; 34 | } 35 | console.log('read plain obj 100w times: %sms', end()); 36 | 37 | begin(); 38 | for(var i = 0; i < 1e6; i++) { 39 | obj['test' + (i & 127)]; 40 | } 41 | console.log('read shared cache 100w times: %sms', end()); 42 | 43 | begin(); 44 | for(var i = 0; i < 1e6; i++) { 45 | binding.fastGet(obj, 'test' + (i & 127)); 46 | } 47 | console.log('fastGet 100w times: %sms', end()); 48 | 49 | begin(); 50 | for(var i = 0; i < 1e6; i++) { 51 | plain['oops' + (i & 127)]; 52 | } 53 | console.log('read plain obj with key absent 100w times: %sms', end()); 54 | 55 | begin(); 56 | for(var i = 0; i < 1e6; i++) { 57 | obj['oops' + (i & 127)]; 58 | } 59 | console.log('read shared cache with key absent 100w times: %sms', end()); 60 | 61 | // test enumerating keys 62 | begin(); 63 | for(var i = 0; i < 1e5; i++) { 64 | Object.keys(plain); 65 | } 66 | console.log('enumerate plain obj 10w times: %sms', end()); 67 | 68 | begin(); 69 | for(var i = 0; i < 1e5; i++) { 70 | Object.keys(obj); 71 | } 72 | console.log('enumerate shared cache 10w times: %sms', end()); 73 | 74 | // test object serialization 75 | var input = {env: process.env, arr: [process.env, process.env]}; 76 | begin(); 77 | for(var i = 0; i < 1e5; i++) { 78 | JSON.stringify(input); 79 | } 80 | console.log('JSON.stringify 10w times: %sms', end()); 81 | 82 | begin(); 83 | for(var i = 0; i < 1e5; i++) { 84 | obj.test = input; 85 | } 86 | console.log('binary serialization 10w times: %sms', end()); 87 | 88 | // test object unserialization 89 | input = JSON.stringify(input); 90 | begin(); 91 | for(var i = 0; i < 1e5; i++) { 92 | JSON.parse(input); 93 | } 94 | console.log('JSON.parse 10w times: %sms', end()); 95 | 96 | begin(); 97 | for(var i = 0; i < 1e5; i++) { 98 | obj.test; 99 | } 100 | console.log('binary unserialization 10w times: %sms', end()); -------------------------------------------------------------------------------- /src/bson.cc: -------------------------------------------------------------------------------- 1 | #include "bson.h" 2 | 3 | #include 4 | #include 5 | 6 | typedef struct object_wrapper_s { 7 | v8::Handle object; 8 | uint32_t index; 9 | object_wrapper_s* next; 10 | 11 | object_wrapper_s(v8::Handle obj, object_wrapper_s* curr) : 12 | object(obj), index(curr ? curr->index + 1 : 0), next(curr) {} 13 | } object_wrapper_t; 14 | 15 | typedef struct writer_s { 16 | bool deleteOld; 17 | size_t capacity; 18 | size_t used; 19 | uint8_t* current; 20 | 21 | object_wrapper_t* objects; 22 | 23 | 24 | inline writer_s(bson::BSONValue& value) : 25 | deleteOld(false), capacity(sizeof(value.cache)), used(0), current(value.cache), objects(NULL) {} 26 | 27 | inline void ensureCapacity(size_t required) { 28 | required += used; 29 | if(capacity >= required) { 30 | used = required; 31 | return; 32 | } 33 | 34 | if(!deleteOld) { 35 | capacity = 4096; 36 | } 37 | while(capacity < required) { 38 | capacity <<= 1; 39 | } 40 | 41 | uint8_t* new_pointer = new uint8_t[capacity]; 42 | // fprintf(stderr, "writer::ensureCapacity: new buffer allocated:%x (len:%d, required:%d, used:%d)\n", new_pointer, capacity, required, used); 43 | uint8_t* old_pointer = current - used; 44 | memcpy(new_pointer, old_pointer, used); 45 | 46 | if(deleteOld) delete[] old_pointer; 47 | else deleteOld = true; 48 | 49 | current = new_pointer + used; 50 | 51 | // set used to required + used 52 | used = required; 53 | } 54 | 55 | inline ~writer_s() { 56 | object_wrapper_t* curr = objects; 57 | while(curr) { 58 | object_wrapper_t* next = curr->next; 59 | delete curr; 60 | curr = next; 61 | } 62 | } 63 | 64 | void write(v8::Handle value) { 65 | using namespace v8; 66 | ensureCapacity(1); 67 | if(value->IsString()) { 68 | *(current++) = bson::String; 69 | Local str = value->ToString(); 70 | size_t len = str->Length() << 1; 71 | ensureCapacity(sizeof(uint32_t) + len); 72 | *reinterpret_cast(current) = len; 73 | current += sizeof(uint32_t); 74 | str->Write(reinterpret_cast(current)); 75 | current += len; 76 | } else if(value->IsNull()) { 77 | *(current++) = bson::Null; 78 | } else if(value->IsBoolean()) { 79 | *(current++) = value->IsTrue() ? bson::True : bson::False; 80 | } else if(value->IsInt32()) { 81 | *(current++) = bson::Int32; 82 | ensureCapacity(sizeof(int32_t)); 83 | *reinterpret_cast(current) = value->Int32Value(); 84 | current += sizeof(int32_t); 85 | } else if(value->IsNumber()) { 86 | *(current++) = bson::Number; 87 | ensureCapacity(sizeof(double)); 88 | *reinterpret_cast(current) = value->NumberValue(); 89 | current += sizeof(double); 90 | } else if(value->IsObject()) { 91 | Handle obj = value.As(); 92 | // check if object has already been serialized 93 | object_wrapper_t* curr = objects; 94 | while(curr) { 95 | if(curr->object->StrictEquals(value)) { // found 96 | *(current++) = bson::ObjectRef; 97 | ensureCapacity(sizeof(uint32_t)); 98 | *reinterpret_cast(current) = curr->index; 99 | current += sizeof(uint32_t); 100 | return; 101 | } 102 | curr = curr->next; 103 | } 104 | // curr is null 105 | objects = new object_wrapper_t(obj, objects); 106 | if(value->IsArray()) { 107 | *(current++) = bson::Array; 108 | Handle arr = obj.As(); 109 | uint32_t len = arr->Length(); 110 | ensureCapacity(sizeof(uint32_t)); 111 | *reinterpret_cast(current) = len; 112 | current += sizeof(uint32_t); 113 | for(uint32_t i = 0; i < len; i++) { 114 | // fprintf(stderr, "write array[%d] (len=%d)\n", i, len); 115 | write(arr->Get(i)); 116 | } 117 | } else { // TODO: support for other object types 118 | *(current++) = bson::Object; 119 | Local names = obj->GetOwnPropertyNames(); 120 | uint32_t len = names->Length(); 121 | ensureCapacity(sizeof(uint32_t)); 122 | *reinterpret_cast(current) = len; 123 | current += sizeof(uint32_t); 124 | for(uint32_t i = 0; i < len; i++) { 125 | Local name = names->Get(i); 126 | write(name); 127 | write(obj->Get(name)); 128 | } 129 | } 130 | } else { 131 | *(current++) = bson::Undefined; 132 | } 133 | } 134 | 135 | } writer_t; 136 | 137 | bson::BSONValue::BSONValue(v8::Handle value) { 138 | 139 | writer_t writer(*this); 140 | writer.write(value); 141 | // fprintf(stderr, "%d bytes used writing %s\n", writer.used, *Nan::Utf8String(value)); 142 | 143 | pointer = writer.current - writer.used; 144 | length = writer.used; 145 | } 146 | 147 | bson::BSONValue::~BSONValue() { 148 | if(length > sizeof(cache)) { // new cache is allocated 149 | // fprintf(stderr, "BSONValue: freeing buffer %x (len:%d)\n", pointer, length); 150 | delete[] pointer; 151 | } 152 | } 153 | 154 | static v8::Local parse(const uint8_t*& data, object_wrapper_t*& objects) { 155 | using namespace v8; 156 | uint32_t len; 157 | const uint8_t* tmp; 158 | switch(*(data++)) { 159 | case bson::Null: 160 | return Nan::Null(); 161 | case bson::Undefined: 162 | return Nan::Undefined(); 163 | case bson::True: 164 | return Nan::True(); 165 | case bson::False: 166 | return Nan::False(); 167 | case bson::Int32: 168 | tmp = data; 169 | data += sizeof(int32_t); 170 | return Nan::New(*reinterpret_cast(tmp)); 171 | case bson::Number: 172 | tmp = data; 173 | data += sizeof(double); 174 | return Nan::New(*reinterpret_cast(tmp)); 175 | case bson::String: 176 | len = *reinterpret_cast(data); 177 | tmp = data += sizeof(uint32_t); 178 | data += len; 179 | #if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION) 180 | return v8::String::NewFromTwoByte(Isolate::GetCurrent(), reinterpret_cast(tmp), v8::String::kNormalString, len >> 1); 181 | #else 182 | return v8::String::New(reinterpret_cast(tmp), len >> 1); 183 | #endif 184 | case bson::Array: 185 | len = *reinterpret_cast(data); 186 | data += sizeof(uint32_t); 187 | { 188 | Local arr = Nan::New(len); 189 | objects = new object_wrapper_t(arr, objects); 190 | 191 | for(uint32_t i = 0; i < len; i++) { 192 | arr->Set(i, parse(data, objects)); 193 | } 194 | return arr; 195 | } 196 | case bson::Object: 197 | len = *reinterpret_cast(data); 198 | data += sizeof(uint32_t); 199 | { 200 | Local obj = Nan::New(); 201 | objects = new object_wrapper_t(obj, objects); 202 | 203 | for(uint32_t i = 0; i < len; i++) { 204 | Handle name = parse(data, objects); 205 | obj->Set(name, parse(data, objects)); 206 | } 207 | return obj; 208 | } 209 | case bson::ObjectRef: 210 | len = *reinterpret_cast(data); 211 | data += sizeof(uint32_t); 212 | { 213 | object_wrapper_t* curr = objects; 214 | while(curr->index != len) { 215 | curr = curr->next; 216 | } 217 | return curr->object; 218 | } 219 | } 220 | assert("should not reach here"); 221 | return Local(); 222 | } 223 | 224 | v8::Local bson::parse(const uint8_t* data) { 225 | object_wrapper_t* objects = NULL; 226 | v8::Local ret = ::parse(data, objects); 227 | while(objects) { 228 | object_wrapper_t* next = objects->next; 229 | delete objects; 230 | objects = next; 231 | } 232 | 233 | return ret; 234 | } 235 | -------------------------------------------------------------------------------- /src/binding.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "memcache.h" 8 | #include "bson.h" 9 | 10 | #ifndef _WIN32 11 | #include 12 | #include 13 | #endif 14 | 15 | #ifdef _WIN32 16 | #include 17 | #endif 18 | 19 | 20 | #define CACHE_HEADER_IN_WORDS 131080 21 | 22 | using namespace v8; 23 | 24 | #define FATALIF(expr, n, method) if((expr) == n) {\ 25 | char sbuf[64];\ 26 | sprintf(sbuf, __FILE__ ":%d: `%s' failed with code %d", __LINE__, #method, errno);\ 27 | return Nan::ThrowError(sbuf);\ 28 | } 29 | 30 | #ifndef _WIN32 31 | #define METHOD_SCOPE(holder, ptr, fd) void* ptr = Nan::GetInternalFieldPointer(holder, 0);\ 32 | HANDLE fd = holder->GetInternalField(1)->Int32Value() 33 | #else 34 | #define METHOD_SCOPE(holder, ptr, fd) void* ptr = Nan::GetInternalFieldPointer(holder, 0);\ 35 | HANDLE fd = reinterpret_cast(holder->GetInternalField(1)->IntegerValue()) 36 | #endif 37 | 38 | #define PROPERTY_SCOPE(property, holder, ptr, fd, keyLen, keyBuf) int keyLen = property->Length();\ 39 | if(keyLen > 256) {\ 40 | return Nan::ThrowError("length of property name should not be greater than 256");\ 41 | }\ 42 | METHOD_SCOPE(holder, ptr, fd);\ 43 | if((keyLen << 1) + 32 > 1 << static_cast(ptr)[CACHE_HEADER_IN_WORDS]) {\ 44 | return Nan::ThrowError("length of property name should not be greater than (block size - 32) / 2");\ 45 | }\ 46 | uint16_t keyBuf[256];\ 47 | property->Write(keyBuf) 48 | 49 | 50 | static NAN_METHOD(release) { 51 | #ifndef _WIN32 52 | FATALIF(shm_unlink(*String::Utf8Value(info[0])), -1, shm_unlink); 53 | #endif 54 | } 55 | 56 | static NAN_METHOD(create) { 57 | if(!info.IsConstructCall()) { 58 | return Nan::ThrowError("Illegal constructor"); 59 | } 60 | 61 | uint32_t size = info[1]->Uint32Value(); 62 | uint32_t block_size_shift = info[2]->Uint32Value(); 63 | if(!block_size_shift || block_size_shift > 31) block_size_shift = 6; 64 | 65 | uint32_t blocks = size >> (5 + block_size_shift) << 5; // 32 aligned 66 | size = blocks << block_size_shift; 67 | 68 | if(block_size_shift < 6) { 69 | return Nan::ThrowError("block size should not be smaller than 64 bytes"); 70 | } else if(block_size_shift > 14) { 71 | return Nan::ThrowError("block size should not be larger than 16 KB"); 72 | }else if(size < 524288) { 73 | return Nan::ThrowError("total_size should be larger than 512 KB"); 74 | } 75 | 76 | // fprintf(stderr, "allocating %d bytes memory\n", size); 77 | 78 | HANDLE fd; 79 | void* ptr; 80 | bool forced = false; 81 | Nan::Utf8String name(info[0]); 82 | 83 | #ifndef _WIN32 84 | FATALIF(fd = shm_open(*name, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR), -1, shm_open); 85 | struct stat stat; 86 | FATALIF(fstat(fd, &stat), -1, fstat); 87 | 88 | if(stat.st_size == 0) { 89 | FATALIF(ftruncate(fd, size), -1, ftruncate); 90 | } else if(stat.st_size != size) { 91 | return Nan::ThrowError("cache initialized with different size"); 92 | } 93 | 94 | FATALIF(ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0), MAP_FAILED, mmap); 95 | forced = stat.st_size == 0; 96 | #else 97 | // create/open the file mapping 98 | HANDLE hnd = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, *name); 99 | if (!hnd) { 100 | FATALIF(hnd = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, size, *name), NULL, CreateFileMapping); 101 | forced = true; 102 | } 103 | 104 | // map the memory 105 | FATALIF(ptr = MapViewOfFile(hnd, FILE_MAP_ALL_ACCESS, 0, 0, size), NULL, MapViewOfFile); 106 | 107 | // create a mutex for synchronization 108 | char mutexName[64]; 109 | sprintf(mutexName, "mutex:%s", *name); 110 | fd = CreateMutex(NULL, FALSE, mutexName); 111 | if (!fd) { 112 | if (GetLastError() == ERROR_ALREADY_EXISTS) { 113 | FATALIF(fd = OpenMutex(SYNCHRONIZE, FALSE, mutexName), NULL, OpenMutex); 114 | } else { 115 | Nan::ThrowError("can't create mutex"); 116 | } 117 | } 118 | #endif 119 | 120 | if (cache::init(ptr, blocks, block_size_shift, forced)) { 121 | Nan::SetInternalFieldPointer(info.Holder(), 0, ptr); 122 | #ifdef __MACH__ 123 | char sbuf[64]; 124 | sprintf(sbuf, "/tmp/shared_cache_%s", *name); 125 | FATALIF(fd = open(sbuf, O_CREAT | O_RDONLY, 0400), -1, open); 126 | #endif 127 | 128 | info.Holder()->SetInternalField(1, Nan::New(fd)); 129 | } 130 | else { 131 | Nan::ThrowError("cache initialization failed, maybe it has been initialized with different block size"); 132 | } 133 | } 134 | 135 | static NAN_PROPERTY_GETTER(getter) { 136 | PROPERTY_SCOPE(property, info.Holder(), ptr, fd, keyLen, keyBuf); 137 | 138 | bson::BSONParser parser; 139 | 140 | cache::get(ptr, fd, keyBuf, keyLen, parser.val, parser.valLen); 141 | 142 | if(parser.val) { 143 | info.GetReturnValue().Set(parser.parse()); 144 | } 145 | 146 | } 147 | 148 | static NAN_PROPERTY_SETTER(setter) { 149 | PROPERTY_SCOPE(property, info.Holder(), ptr, fd, keyLen, keyBuf); 150 | 151 | bson::BSONValue bsonValue(value); 152 | 153 | FATALIF(cache::set(ptr, fd, keyBuf, keyLen, bsonValue.Data(), bsonValue.Length()), -1, cache::set); 154 | info.GetReturnValue().Set(value); 155 | } 156 | 157 | class KeysEnumerator { 158 | public: 159 | uint32_t length; 160 | Local keys; 161 | 162 | static void next(KeysEnumerator* self, uint16_t* key, size_t keyLen) { 163 | self->keys->Set(self->length++, Nan::New(key, keyLen).ToLocalChecked()); 164 | } 165 | 166 | inline KeysEnumerator() : length(0), keys(Nan::New()) {} 167 | }; 168 | 169 | static NAN_PROPERTY_ENUMERATOR(enumerator) { 170 | METHOD_SCOPE(info.Holder(), ptr, fd); 171 | // fprintf(stderr, "enumerating properties %x\n", ptr); 172 | 173 | KeysEnumerator enumerator; 174 | cache::enumerate(ptr, fd, &enumerator, KeysEnumerator::next); 175 | 176 | info.GetReturnValue().Set(enumerator.keys); 177 | } 178 | 179 | static NAN_PROPERTY_DELETER(deleter) { 180 | PROPERTY_SCOPE(property, info.Holder(), ptr, fd, keyLen, keyBuf); 181 | 182 | info.GetReturnValue().Set(cache::unset(ptr, fd, keyBuf, keyLen)); 183 | } 184 | 185 | static NAN_PROPERTY_QUERY(querier) { 186 | PROPERTY_SCOPE(property, info.Holder(), ptr, fd, keyLen, keyBuf); 187 | if(cache::contains(ptr, fd, keyBuf, keyLen)) { 188 | info.GetReturnValue().Set(0); 189 | } 190 | } 191 | 192 | // increase(holder, key, [by]) 193 | static NAN_METHOD(increase) { 194 | Local holder = Local::Cast(info[0]); 195 | PROPERTY_SCOPE(info[1]->ToString(), holder, ptr, fd, keyLen, keyBuf); 196 | uint32_t increase_by = info.Length() > 2 ? info[2]->Uint32Value() : 1; 197 | info.GetReturnValue().Set(cache::increase(ptr, fd, keyBuf, keyLen, increase_by)); 198 | } 199 | 200 | // exchange(holder, key, val) 201 | // exchanges current key with new value, the old value is returned 202 | static NAN_METHOD(exchange) { 203 | Local holder = Local::Cast(info[0]); 204 | PROPERTY_SCOPE(info[1]->ToString(), holder, ptr, fd, keyLen, keyBuf); 205 | 206 | bson::BSONValue bsonValue(info[2]); 207 | 208 | bson::BSONParser parser; 209 | FATALIF(cache::set(ptr, fd, keyBuf, keyLen, bsonValue.Data(), bsonValue.Length(), &parser.val, &parser.valLen), -1, cache::exchange); 210 | 211 | if(parser.val) { 212 | info.GetReturnValue().Set(parser.parse()); 213 | } 214 | } 215 | 216 | // fastGet(instance, key) 217 | static NAN_METHOD(fastGet) { 218 | Local holder = Local::Cast(info[0]); 219 | PROPERTY_SCOPE(info[1]->ToString(), holder, ptr, fd, keyLen, keyBuf); 220 | 221 | bson::BSONParser parser; 222 | 223 | cache::fast_get(ptr, fd, keyBuf, keyLen, parser.val, parser.valLen); 224 | 225 | if(parser.val) { 226 | info.GetReturnValue().Set(parser.parse()); 227 | } 228 | } 229 | 230 | class EntriesDumper { 231 | public: 232 | Local entries; 233 | uint16_t key[256]; 234 | size_t keyLen; 235 | 236 | static void next(EntriesDumper* self, uint16_t* key, size_t keyLen, uint8_t* val) { 237 | if(self->keyLen) { 238 | if(self->keyLen > keyLen || memcmp(self->key, key, self->keyLen << 1)) return; 239 | } 240 | self->entries->Set(Nan::New(key, keyLen).ToLocalChecked(), bson::parse(val)); 241 | } 242 | 243 | inline EntriesDumper() : entries(Nan::New()), keyLen(0) {} 244 | }; 245 | 246 | static NAN_METHOD(dump) { 247 | Local holder = Local::Cast(info[0]); 248 | METHOD_SCOPE(holder, ptr, fd); 249 | EntriesDumper dumper; 250 | 251 | if(info.Length() > 1 && info[1]->BooleanValue()) { 252 | Local prefix = info[1]->ToString(); 253 | int keyLen = prefix->Length(); 254 | if(keyLen > 256) { 255 | info.GetReturnValue().Set(dumper.entries); 256 | return; 257 | } 258 | 259 | prefix->Write(dumper.key); 260 | dumper.keyLen = keyLen; 261 | } 262 | 263 | 264 | cache::dump(ptr, fd, &dumper, EntriesDumper::next); 265 | info.GetReturnValue().Set(dumper.entries); 266 | } 267 | 268 | static NAN_METHOD(clear) { 269 | Local holder = Local::Cast(info[0]); 270 | METHOD_SCOPE(holder, ptr, fd); 271 | cache::clear(ptr, fd); 272 | } 273 | 274 | void init(Handle exports) { 275 | 276 | Local constructor = Nan::New(create); 277 | Local inst = constructor->InstanceTemplate(); 278 | inst->SetInternalFieldCount(2); // ptr, fd (synchronization object) 279 | Nan::SetNamedPropertyHandler(inst, getter, setter, querier, deleter, enumerator); 280 | 281 | Nan::Set(exports, Nan::New("Cache").ToLocalChecked(), constructor->GetFunction()); 282 | Nan::SetMethod(exports, "release", release); 283 | Nan::SetMethod(exports, "increase", increase); 284 | Nan::SetMethod(exports, "exchange", exchange); 285 | Nan::SetMethod(exports, "fastGet", fastGet); 286 | Nan::SetMethod(exports, "clear", clear); 287 | Nan::SetMethod(exports, "dump", dump); 288 | } 289 | 290 | 291 | NODE_MODULE(binding, init) 292 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## node-shared-cache 2 | 3 | Interprocess shared memory cache for Node.JS 4 | 5 | It supports auto memory-management and fast object serialization. It uses a hashmap and LRU cache internally to maintain its contents. 6 | 7 | ## Updates 8 | 9 | - 1.6.2 10 | - Add `exchange` method which can be used as atomic lock as well as `increase` 11 | - Add `fastGet` method which does not touch the LRU sequence 12 | - 1.6.1 Update `nan` requirement to 2.4.0 13 | - 1.6.0 Add support for Win32 ([#7](https://github.com/kyriosli/node-shared-cache/issues/7)). Thanks to [@matthias-christen](https://github.com/matthias-christen) [@dancrumb](https://github.com/dancrumb) 14 | 15 | ## Install 16 | 17 | You can install it with npm. Just type `npm i node-shared-cache` will do it. 18 | 19 | You can also download and install it manually, but you need to install Node.JS and `node-gyp` first. 20 | 21 | git clone https://github.com/kyriosli/node-shared-cache.git 22 | cd node-shared-cache 23 | node-gyp rebuild 24 | 25 | 26 | ## Terms of Use 27 | 28 | This software (source code and its binary builds) is absolutely copy free and any download or modification is permitted except for unprohibited 29 | commercial use. 30 | 31 | But due to the complexity of this software, any bugs or runtime exceptions could happen when programs which includeed it run into an unexpected 32 | situation, which in most cases should be harmless but also have the chance to cause: 33 | 34 | - program crash 35 | - system down 36 | - software damage 37 | - hardware damage 38 | 39 | which would lead to data corruption or even economic losses. 40 | 41 | So when you are using this software, DO 42 | 43 | - check the data 44 | - double check the data 45 | - avoid undefined behavior to happen 46 | 47 | To avoid data crupption, we use a read-write lock to ensure that data modification is exclusive. But when a program is writting data when something 48 | bad, for example, a SIGKILL, happens that crashes the program before the write operation is complete and lock is released, other processes may not be 49 | able to enter the exclusive region again. I do not use an auto recovery lock such as `flock`, which will automatically release when process exits, just 50 | in case that wrong data is returned when performing a reading operation, or even, causing a segment fault. 51 | 52 | ## usage 53 | 54 | ```js 55 | // create cache instance 56 | var cache = require('node-shared-cache'); 57 | var obj = new cache.Cache("test", 557056); 58 | 59 | // setting property 60 | obj.foo = "bar"; 61 | 62 | // getting property 63 | console.log(obj.foo); 64 | 65 | // enumerating properties 66 | for(var k in obj); 67 | Object.keys(obj); 68 | 69 | // deleting property 70 | delete obj.foo; 71 | 72 | // writing objects is also supported 73 | obj.foo = {'foo': 'bar'}; 74 | // but original object reference is not saved 75 | var test = obj.foo = {'foo': 'bar'}; 76 | test === obj.foo; // false 77 | 78 | // circular reference is supported. 79 | test.self = test; 80 | obj.foo = test; 81 | // and saved result is also circular 82 | test = obj.foo; 83 | test.self === test; // true 84 | 85 | // increase a key 86 | cache.increase(obj, "foo"); 87 | cache.increase(obj, "foo", 3); 88 | 89 | // exchange current key with new value, the old value is returned 90 | cache.set(obj, "foo", 123); 91 | cache.exchange(obj, "foo", 456); // 123 92 | obj.foo; // 456 93 | 94 | // release memory region 95 | cache.release("test"); 96 | 97 | // dump current cache 98 | var values = cache.dump(obj); 99 | // dump current cache by key prefix 100 | values = cache.dump(obj, "foo_"); 101 | ``` 102 | 103 | ### class Cache 104 | 105 | #### constructor 106 | 107 | ```js 108 | function Cache(name, size, optional block_size) 109 | ``` 110 | 111 | `name` represents a file name in shared memory, `size` represents memory size in bytes to be used. `block_size` denotes the size of the unit of the memory block. 112 | 113 | `block_size` can be any of: 114 | 115 | - cache.SIZE_64 (6): 64 bytes (default) 116 | - cache.SIZE_128 (7): 128 bytes 117 | - cache.SIZE_256 (8): 256 bytes 118 | - cache.SIZE_512 (9): 512 bytes 119 | - cache.SIZE_1K (10): 1KB 120 | - cache.SIZE_2K (11): 2KB 121 | - ... 122 | - cache.SIZE_16K (14): 16KB 123 | 124 | Note that: 125 | 126 | - `size` should not be smaller than 524288 (512KB) 127 | - block count is 32-aligned 128 | - key length should not be greater than `(block_size - 32) / 2`, for example, when block size is 64 bytes, maximum key length is 16 chars. 129 | - key length should also not be greater than 256 130 | 131 | So when block_size is set to default, the maximum memory size that can be used is 128M, and the maximum keys that can be stored is 2088960 (8192 blocks is used for data structure) 132 | 133 | #### property setter 134 | 135 | ```js 136 | set(name, value) 137 | ``` 138 | 139 | ### exported methods 140 | 141 | #### release 142 | 143 | ```js 144 | function release(name) 145 | ``` 146 | 147 | The shared memory named `name` will be released. Throws error if shared memory is not found. Note that this method simply calls `shm_unlink` and does not check whether the memory region is really initiated by this module. 148 | 149 | Don't call this method when the cache is still used by some process, may cause memory leak 150 | 151 | #### clear 152 | 153 | function clear(instance) 154 | 155 | Clears a cache 156 | 157 | #### increase 158 | 159 | ```js 160 | function increase(instance, name, optional increase_by) 161 | ``` 162 | 163 | Increase a key in the cache by an integer (default to 1). If the key is absent, or not an integer, the key will be set to `increase_by`. 164 | 165 | #### exchange 166 | 167 | ```js 168 | function exchange(instance, name, new_value) 169 | ``` 170 | 171 | Update a key in the cache with a new value, the old value is returned. 172 | 173 | #### fastGet 174 | 175 | ```js 176 | function fastGet(instance, name) 177 | ``` 178 | 179 | Get the value of a key without touching the LRU sequence. This method is usually faster than `instance[name]` because it uses 180 | different lock mechanism to ensure shared reading across processes. 181 | 182 | #### dump 183 | 184 | ```js 185 | function dump(instance, optional prefix) 186 | ``` 187 | 188 | Dump keys and values 189 | 190 | ## Performance 191 | 192 | Tests are run under a virtual machine with one processor: 193 | ```sh 194 | $ node -v 195 | v0.12.4 196 | $ cat /proc/cpuinfo 197 | processor : 0 198 | vendor_id : GenuineIntel 199 | cpu family : 6 200 | model : 45 201 | model name : Intel(R) Xeon(R) CPU E5-2630 0 @ 2.30GHz 202 | stepping : 7 203 | microcode : 0x70d 204 | cpu MHz : 2300.090 205 | cache size : 15360 KB 206 | ... 207 | ``` 208 | 209 | Block size is set to 64 and 1MB of memory is used. 210 | 211 | ### Setting property 212 | 213 | When setting property 100w times: 214 | 215 | ```js 216 | // test plain object 217 | var plain = {}; 218 | console.time('plain obj'); 219 | for(var i = 0; i < 1000000; i++) { 220 | plain['test' + (i & 127)] = i; 221 | } 222 | console.timeEnd('plain obj'); 223 | 224 | // test shared cache 225 | var obj = new binding.Cache("test", 1048576); 226 | console.time('shared cache'); 227 | for(var i = 0; i < 1000000; i++) { 228 | obj['test' + (i & 127)] = i; 229 | } 230 | console.timeEnd('shared cache'); 231 | ``` 232 | 233 | The result is: 234 | 235 | plain obj: 227ms 236 | shared cache: 492ms (1:2.17) 237 | 238 | ### Getting property 239 | 240 | When trying to read existing key: 241 | 242 | ```js 243 | console.time('read plain obj'); 244 | for(var i = 0; i < 1000000; i++) { 245 | plain['test' + (i & 127)]; 246 | } 247 | console.timeEnd('read plain obj'); 248 | 249 | console.time('read shared cache'); 250 | for(var i = 0; i < 1000000; i++) { 251 | obj['test' + (i & 127)]; 252 | } 253 | console.timeEnd('read shared cache'); 254 | ``` 255 | 256 | The result is: 257 | 258 | read plain obj: 138ms 259 | read shared cache: 524ms (1:3.80) 260 | 261 | When trying to read keys that are not existed: 262 | 263 | ```js 264 | console.time('read plain obj with key absent'); 265 | for(var i = 0; i < 1000000; i++) { 266 | plain['oops' + (i & 127)]; 267 | } 268 | console.timeEnd('read plain obj with key absent'); 269 | 270 | console.time('read shared cache with key absent'); 271 | for(var i = 0; i < 1000000; i++) { 272 | obj['oops' + (i & 127)]; 273 | } 274 | console.timeEnd('read shared cache with key absent'); 275 | ``` 276 | 277 | The result is: 278 | 279 | read plain obj with key absent: 265ms 280 | read shared cache with key absent: 595ms (1:2.24) 281 | 282 | ### Enumerating properties 283 | 284 | When enumerating all the keys: 285 | 286 | ```js 287 | console.time('enumerate plain obj'); 288 | for(var i = 0; i < 100000; i++) { 289 | Object.keys(plain); 290 | } 291 | console.timeEnd('enumerate plain obj'); 292 | 293 | console.time('enumerate shared cache'); 294 | for(var i = 0; i < 100000; i++) { 295 | Object.keys(obj); 296 | } 297 | console.timeEnd('enumerate shared cache'); 298 | ``` 299 | 300 | The result is: 301 | 302 | enumerate plain obj: 1201ms 303 | enumerate shared cache: 4262ms (1:3.55) 304 | 305 | Warn: Because the shared memory can be modified at any time even the current Node.js 306 | process is running, depending on keys enumeration result to determine whether a key 307 | is cached is unwise. On the other hand, it takes so long a time to build strings from 308 | memory slice, as well as putting them into an array, so DO NOT USE IT unless you know 309 | that what you are doing. 310 | 311 | ### Object serialization 312 | 313 | We choose a c-style binary serialization method rather than `JSON.stringify`, in two 314 | concepts: 315 | 316 | - Performance serializing and unserializing 317 | - Support for circular reference 318 | 319 | Tests code list: 320 | 321 | ```js 322 | var input = {env: process.env, arr: [process.env, process.env]}; 323 | console.time('JSON.stringify'); 324 | for(var i = 0; i < 100000; i++) { 325 | JSON.stringify(input); 326 | } 327 | console.timeEnd('JSON.stringify'); 328 | 329 | console.time('binary serialization'); 330 | for(var i = 0; i < 100000; i++) { 331 | obj.test = input; 332 | } 333 | console.timeEnd('binary serialization'); 334 | 335 | // test object unserialization 336 | input = JSON.stringify(input); 337 | console.time('JSON.parse'); 338 | for(var i = 0; i < 100000; i++) { 339 | JSON.parse(input); 340 | } 341 | console.timeEnd('JSON.parse'); 342 | 343 | console.time('binary unserialization'); 344 | for(var i = 0; i < 100000; i++) { 345 | obj.test; 346 | } 347 | console.timeEnd('binary unserialization'); 348 | ``` 349 | 350 | The result is: 351 | 352 | JSON.stringify: 5876ms 353 | binary serialization: 2523ms (2.33:1) 354 | JSON.parse: 2042ms 355 | binary unserialization: 2098ms (1:1.03) 356 | 357 | 358 | ## TODO 359 | -------------------------------------------------------------------------------- /src/memcache.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include // memset 3 | #include // errno 4 | #include // uint32_t 5 | #include "memcache.h" 6 | #include "bson.h" 7 | 8 | #ifndef _WIN32 9 | #include // flock 10 | #else 11 | #define LOCK_SH 1 12 | #define LOCK_EX 2 13 | #define LOCK_UN 8 14 | #endif 15 | 16 | #ifdef _WIN32 17 | #include 18 | #endif 19 | 20 | #ifdef _WIN32 21 | static uint32_t __inline __builtin_clz(uint32_t x) 22 | { 23 | unsigned long idx = 0; 24 | _BitScanReverse(&idx, x); 25 | return 31 - idx; 26 | } 27 | #endif 28 | 29 | #define MAGIC 0xdeadbeef 30 | 31 | namespace cache { 32 | 33 | #ifndef _WIN32 34 | #define LOCK(fd, ACT) while(flock(fd, ACT)) 35 | #else 36 | #define LOCK(fd, ACT) if (ACT == LOCK_UN) ReleaseMutex(fd); else WaitForSingleObject(fd, INFINITE) 37 | #endif 38 | 39 | typedef struct read_lock_s { 40 | HANDLE fd; 41 | inline read_lock_s(HANDLE fd) : fd(fd) { 42 | LOCK(fd, LOCK_SH); 43 | } 44 | inline ~read_lock_s() { 45 | LOCK(fd, LOCK_UN); 46 | } 47 | } read_lock_t; 48 | 49 | typedef struct write_lock_s { 50 | HANDLE fd; 51 | inline write_lock_s(HANDLE fd) : fd(fd) { 52 | LOCK(fd, LOCK_EX); 53 | } 54 | inline ~write_lock_s() { 55 | LOCK(fd, LOCK_UN); 56 | } 57 | } write_lock_t; 58 | #undef LOCK 59 | 60 | typedef struct node_s { 61 | uint32_t prev; 62 | uint32_t next; 63 | uint32_t hash_next; 64 | uint32_t blocks; 65 | uint32_t valLen; 66 | uint32_t hash; 67 | uint16_t keyLen; 68 | uint16_t key[0]; 69 | } node_t; 70 | 71 | typedef struct cache_s { 72 | uint32_t hashmap[65536]; 73 | 74 | union { 75 | uint32_t nexts[0]; 76 | 77 | struct { // can be at most 32 dwords 78 | uint32_t magic; // 1 79 | uint32_t blocks_total; // 2 80 | uint32_t blocks_available; // 3 81 | uint32_t dirty; // 4 82 | 83 | uint16_t block_size_shift; 84 | uint16_t first_block; // 5 85 | 86 | uint32_t next_bitmap_index; // 6 next bitmap position to look for when allocating block 87 | uint32_t blocks_used; // 7 88 | uint32_t head; // 8 89 | uint32_t tail; // 9 90 | } info; 91 | 92 | }; 93 | 94 | template 95 | inline T* address(uint32_t block) const { 96 | return reinterpret_cast(((uint8_t*) this) + (block << info.block_size_shift)); 97 | } 98 | 99 | inline uint32_t find(const uint16_t* key, size_t keyLen, uint32_t hash) const { 100 | 101 | uint32_t curr = hashmap[hash & 0xffff]; 102 | while(curr) { 103 | node_t& node = *address(curr); 104 | // fprintf(stderr, "cache::find: tests match block %d (keyLen=%d hash=%d)\n", curr, node.keyLen, node.hash); 105 | if(node.keyLen == keyLen && node.hash == hash && !memcmp(node.key, key, keyLen << 1)) { 106 | return curr; 107 | } 108 | curr = nexts[curr]; 109 | } 110 | 111 | return 0; 112 | } 113 | 114 | inline void format() { 115 | // fprintf(stderr, "format %x\n", this); 116 | // clear bitmap and hashmap 117 | memset(hashmap, 0, sizeof(hashmap)); 118 | 119 | info.blocks_used = 0; 120 | info.next_bitmap_index = info.first_block >> 5; 121 | // mark bits as used 122 | 123 | uint32_t* bitmap = nexts + info.blocks_total; 124 | memset(bitmap, 0, info.blocks_total >> 3); // 3 means 8 blocks per byte 125 | 126 | if(info.first_block & 31) { 127 | uint32_t mask = 0xffffffff << (info.first_block & 31); 128 | nexts[info.blocks_total + info.next_bitmap_index] = ~mask; 129 | } 130 | 131 | info.head = 0; 132 | info.tail = 0; 133 | info.dirty = 0; // at last, set dirty to 0 134 | } 135 | 136 | inline uint32_t selectOne() { 137 | uint32_t* bitmap = nexts + info.blocks_total; 138 | 139 | uint32_t& curr = info.next_bitmap_index; 140 | uint32_t bits = bitmap[curr]; 141 | while(bits == 0xffffffff) { 142 | curr++; 143 | if(curr == info.blocks_total >> 5) { 144 | curr = info.first_block >> 5; 145 | } 146 | // fprintf(stderr, "will try slot %d\n", curr); 147 | bits = bitmap[curr]; 148 | } 149 | // assert(bits != 0xffffffff) 150 | uint32_t bitSelected = 31 - __builtin_clz(~bits); 151 | bitmap[curr] = bits | 1 << bitSelected; 152 | return curr << 5 | bitSelected; 153 | } 154 | 155 | inline void release(uint32_t block) { 156 | uint32_t* bitmap = nexts + info.blocks_total; 157 | uint32_t count = 0; 158 | for(uint32_t next = block; next; next = nexts[next]) { 159 | // fprintf(stderr, "free block %d assert(bit:%d) %x ^ %x = %x\n", next, (bitmap[next >> 5] >> (next & 31)) & 1, bitmap[next >> 5], 1 << (next & 31), bitmap[next >> 5] ^ 1 << (next & 31)); 160 | bitmap[next >> 5] ^= 1 << (next & 31); 161 | // fprintf(stderr, "freed 1 bit, bitmap[%d] = %x\n", next>>5, bitmap[next>>5]); 162 | count++; 163 | } 164 | info.blocks_used -= count; 165 | } 166 | 167 | inline void dropNode(uint32_t first_block) { 168 | node_t& node = *address(first_block); 169 | // fprintf(stderr, "dropping node %d (prev:%d next:%d)\n", first_block, node.prev, node.next); 170 | // remove from lru list 171 | uint32_t& $prev = node.prev ? address(node.prev)->next : info.head; 172 | uint32_t& $next = node.next ? address(node.next)->prev : info.tail; 173 | $prev = node.next; 174 | $next = node.prev; 175 | 176 | // remove from hash list 177 | uint32_t* toModify = &hashmap[node.hash & 0xffff]; 178 | while(*toModify != first_block) { 179 | node_t* pcurr = address(*toModify); 180 | toModify = &pcurr->hash_next; 181 | } 182 | *toModify = node.hash_next; 183 | // release blocks 184 | release(first_block); 185 | } 186 | 187 | inline uint32_t allocate(uint32_t count) { 188 | uint32_t target = info.blocks_available - count; 189 | // fprintf(stderr, "allocate: total=%d used=%d, count=%d, target=%d\n", info.blocks_total, info.blocks_used, count, target); 190 | if(info.blocks_used > target) { // not enough 191 | do { 192 | dropNode(info.head); 193 | } while (info.blocks_used > target); 194 | } 195 | // TODO 196 | uint32_t first_block = selectOne(); 197 | // fprintf(stderr, "select %d blocks (first: %d)\n", count, first_block); 198 | info.blocks_used += count; 199 | 200 | uint32_t curr = first_block; 201 | while(--count) { 202 | uint32_t nextBlock = selectOne(); 203 | // fprintf(stderr, "selected block %d\n", nextBlock); 204 | nexts[curr] = nextBlock; 205 | curr = nextBlock; 206 | } 207 | // close last slave 208 | nexts[curr] = 0; 209 | return first_block; 210 | } 211 | 212 | inline void touch(uint32_t curr) { 213 | // fprintf(stderr, "cache::touch %d (current: %d)\n", curr, info.tail); 214 | if(info.tail == curr) { 215 | return; 216 | } 217 | // bring to tail 218 | node_t& node = *address(curr); 219 | // assert: node.next != 0 220 | address(node.next)->prev = node.prev; 221 | 222 | if(node.prev) { 223 | address(node.prev)->next = node.next; 224 | } else { // node is head 225 | info.head = node.next; 226 | } 227 | node.next = 0; 228 | node.prev = info.tail; 229 | address(info.tail)->next = curr; 230 | info.tail = curr; 231 | // fprintf(stderr, "touch: head=%d tail=%d curr=%d prev=%d next=%d\n", info.head, info.tail, curr, node.prev, node.next); 232 | } 233 | 234 | inline uint32_t setup(uint32_t blocks, uint32_t hash, size_t keyLen, const uint16_t* key) { 235 | uint32_t found = allocate(blocks); 236 | node_t& node = *address(found); 237 | node.blocks = blocks; 238 | node.hash = hash; 239 | 240 | uint32_t& hash_head = hashmap[hash & 0xffff]; 241 | node.hash_next = hash_head; // insert into linked list 242 | hash_head = found; 243 | 244 | if(!info.tail) { 245 | info.head = info.tail = found; 246 | node.prev = node.next = 0; 247 | } else { 248 | address(info.tail)->next = found; 249 | node.prev = info.tail; 250 | node.next = 0; 251 | info.tail = found; 252 | } 253 | node.keyLen = keyLen; 254 | memcpy(node.key, key, keyLen << 1); 255 | return found; 256 | } 257 | 258 | inline uint32_t& next(uint32_t node, uint32_t count) { 259 | for(uint32_t i = 1; i < count; i++) { 260 | node = nexts[node]; 261 | } 262 | return nexts[node]; 263 | } 264 | 265 | 266 | void read(uint32_t found, uint8_t*& retval, size_t& retvalLen) const { 267 | node_t* pnode = address(found); 268 | size_t valLen = pnode->valLen; 269 | uint8_t* val; 270 | 271 | if(valLen > retvalLen) { 272 | val = retval = new uint8_t[valLen]; 273 | } else { 274 | val = retval; 275 | } 276 | retvalLen = valLen; 277 | 278 | uint8_t* currentBlock = reinterpret_cast(pnode); 279 | uint32_t offset = sizeof(node_t) + (pnode->keyLen << 1); 280 | const uint32_t BLK_SIZE = 1 << info.block_size_shift; 281 | uint32_t capacity = BLK_SIZE - offset; 282 | 283 | while(capacity < valLen) { 284 | // fprintf(stderr, "copying val (%x+%d) %d bytes\n", currentBlock, offset, capacity); 285 | memcpy(val, currentBlock + offset, BLK_SIZE - offset); 286 | val += capacity; 287 | valLen -= capacity; 288 | found = nexts[found]; 289 | currentBlock = address(found); 290 | offset = 0; 291 | capacity = BLK_SIZE; 292 | } 293 | if(valLen) { // capacity >= valLen 294 | // fprintf(stderr, "copying remaining val (%x+%d) %d bytes\n", currentBlock, offset, valLen); 295 | memcpy(val, reinterpret_cast(currentBlock) + offset, valLen); 296 | } 297 | } 298 | } cache_t; 299 | 300 | bool init(void* ptr, uint32_t blocks, uint32_t block_size_shift, bool forced) { 301 | uint32_t bitmap_size = blocks >> 3; 302 | uint32_t nexts_size = blocks << 2; 303 | uint32_t blocks_available = ((blocks << block_size_shift) - (HEADER_SIZE + bitmap_size + nexts_size)) >> block_size_shift; 304 | uint16_t first_block = blocks - blocks_available; 305 | 306 | cache_t& cache = *static_cast(ptr); 307 | 308 | if(!forced && cache.info.magic == MAGIC) { 309 | return cache.info.blocks_total == blocks && 310 | cache.info.blocks_available == blocks_available && 311 | cache.info.block_size_shift == block_size_shift && 312 | cache.info.first_block == first_block; 313 | } 314 | 315 | // initialize key words 316 | cache.info.magic = MAGIC; 317 | cache.info.blocks_total = blocks; 318 | cache.info.blocks_available = blocks_available; 319 | cache.info.block_size_shift = block_size_shift; 320 | cache.info.first_block = first_block; 321 | cache.format(); 322 | // fprintf(stderr, "init cache: size %d, blocks %d, usage %d/%d\n", blocks << block_size_shift, blocks, cache.info.blocks_used, cache.info.blocks_available); 323 | return true; 324 | } 325 | 326 | #if(0) 327 | static void dump(cache_t& cache) { 328 | fprintf(stderr, "== DUMP START: head: %d tail:%d", cache.info.head, cache.info.tail); 329 | uint32_t prev = 0; 330 | 331 | for(uint32_t curr = cache.info.head; curr;) { 332 | node_t& node = *cache.address(curr); 333 | if(node.prev != prev) { 334 | fprintf(stderr, "ERROR: %d->prev=%d != %d\n", curr, node.prev, prev); 335 | } 336 | char buf[128]; 337 | int i = 0; 338 | while(i < 127 && i < node.keyLen) { 339 | buf[i] = node.key[i]; 340 | i++; 341 | } 342 | buf[i] = 0; 343 | fprintf(stderr, "\n%d(hash:%d prev:%d next:%d): %s", curr, node.hash, node.prev, node.next, buf); 344 | 345 | 346 | for(uint32_t next = cache.nexts[curr]; next; ) { 347 | fprintf(stderr, "-->%d", next); 348 | next = cache.nexts[next]; 349 | } 350 | prev = curr; 351 | curr = node.next; 352 | } 353 | if(prev != cache.info.tail) { 354 | fprintf(stderr, "\nERROR: ended at %d != tail(%d)\n", prev, cache.info.tail); 355 | } else { 356 | fprintf(stderr, "\nDUMP END ==\n"); 357 | } 358 | 359 | } 360 | #endif 361 | 362 | 363 | inline uint32_t hashsum(const uint16_t* key, size_t keyLen) { 364 | uint32_t hash = 0xffffffff; 365 | for(size_t i = 0; i < keyLen; i++) { 366 | hash = hash * 31 + key[i]; 367 | // fprintf(stderr, "hash %d %c %x\n", i, key[i], hash); 368 | } 369 | return hash; 370 | } 371 | 372 | void get(void* ptr, HANDLE fd, const uint16_t* key, size_t keyLen, uint8_t*& retval, size_t& retvalLen) { 373 | // fprintf(stderr, "cache::get: key len %d\n", keyLen); 374 | cache_t& cache = *static_cast(ptr); 375 | 376 | uint32_t hash = hashsum(key, keyLen); 377 | write_lock_t lock(fd); 378 | if(cache.info.dirty) { 379 | retval = NULL; 380 | return; 381 | } 382 | 383 | uint32_t found = cache.find(key, keyLen, hash); 384 | // fprintf(stderr, "cache::get hash=%d found=%d\n", hash, found); 385 | if(!found) { 386 | retval = NULL; 387 | return; 388 | } 389 | 390 | cache.info.dirty = 1; 391 | cache.touch(found); 392 | cache.info.dirty = 0; 393 | 394 | // found, read it out 395 | cache.read(found, retval, retvalLen); 396 | // dump(cache); 397 | } 398 | 399 | void fast_get(void* ptr, HANDLE fd, const uint16_t* key, size_t keyLen, uint8_t*& retval, size_t& retvalLen) { 400 | // fprintf(stderr, "cache::fast_get: key len %d\n", keyLen); 401 | cache_t& cache = *static_cast(ptr); 402 | 403 | uint32_t hash = hashsum(key, keyLen); 404 | read_lock_t lock(fd); 405 | if(cache.info.dirty) { 406 | retval = NULL; 407 | return; 408 | } 409 | 410 | uint32_t found = cache.find(key, keyLen, hash); 411 | // fprintf(stderr, "cache::fast_get hash=%d found=%d\n", hash, found); 412 | if(!found) { 413 | retval = NULL; 414 | return; 415 | } 416 | 417 | cache.read(found, retval, retvalLen); 418 | } 419 | 420 | int set(void* ptr, HANDLE fd, const uint16_t* key, size_t keyLen, const uint8_t* val, size_t valLen, uint8_t** oldval, size_t* oldvalLen) { 421 | cache_t& cache = *static_cast(ptr); 422 | const size_t totalLen = (keyLen << 1) + valLen + sizeof(node_s); 423 | const uint32_t BLK_SIZE = 1 << cache.info.block_size_shift; 424 | const uint32_t blocksRequired = totalLen / BLK_SIZE + (totalLen % BLK_SIZE ? 1 : 0); 425 | // fprintf(stderr, "cache::set: total len %d (%d blocks required)\n", totalLen, blocksRequired); 426 | 427 | 428 | if(blocksRequired > cache.info.blocks_total) { 429 | errno = E2BIG; 430 | return -1; 431 | } 432 | 433 | uint32_t hash = hashsum(key, keyLen); 434 | 435 | write_lock_t lock(fd); 436 | if(cache.info.dirty) { 437 | cache.format(); 438 | } 439 | // find if key is already exists 440 | uint32_t found = cache.find(key, keyLen, hash); 441 | node_t* selectedBlock; 442 | cache.info.dirty = 1; 443 | // fprintf(stderr, "cache::set hash=%d found=%d hash_head=%d\n", hash, found, cache.hashmap[hash & 0xffff]); 444 | if(found) { // update 445 | if(oldval) { // preserve old value 446 | cache.read(found, *oldval, *oldvalLen); 447 | } 448 | cache.touch(found); 449 | selectedBlock = cache.address(found); 450 | node_t& node = *selectedBlock; 451 | if(node.blocks > blocksRequired) { // free extra blocks 452 | uint32_t& lastBlk = cache.next(found, blocksRequired); 453 | // drop remaining blocks 454 | cache.release(lastBlk); 455 | // fprintf(stderr, "freeing %d blocks (%d used)\n", node.blocks - blocksRequired, cache.info.blocks_used); 456 | lastBlk = 0; 457 | } else if(node.blocks < blocksRequired) { 458 | cache.next(found, node.blocks) = cache.allocate(blocksRequired - node.blocks); 459 | } 460 | node.blocks = blocksRequired; 461 | } else { // insert 462 | if(oldval) { 463 | *oldval = NULL; 464 | } 465 | // insert into hash table 466 | found = cache.setup(blocksRequired, hash, keyLen, key); 467 | // fprintf(stderr, "cache::set allocated new block %d\n", found); 468 | selectedBlock = cache.address(found); 469 | } 470 | selectedBlock->valLen = valLen; 471 | 472 | // copy values 473 | 474 | uint8_t* currentBlock = reinterpret_cast(selectedBlock); 475 | uint32_t offset = sizeof(node_t) + (keyLen << 1); 476 | uint32_t capacity = BLK_SIZE - offset; 477 | 478 | while(capacity < valLen) { 479 | // fprintf(stderr, "copying val (%x+%d) %d bytes. next=%d\n", currentBlock, offset, capacity, cache.nexts[found]); 480 | memcpy(currentBlock + offset, val, capacity); 481 | val += capacity; 482 | valLen -= capacity; 483 | found = cache.nexts[found]; 484 | currentBlock = cache.address(found); 485 | offset = 0; 486 | capacity = BLK_SIZE; 487 | } 488 | 489 | if(valLen) { // capacity >= valLen 490 | // fprintf(stderr, "copying remaining val (%x+%d) %d bytes\n", currentBlock, offset, valLen); 491 | memcpy(reinterpret_cast(currentBlock) + offset, val, valLen); 492 | } 493 | cache.info.dirty = 0; 494 | // dump(cache); 495 | return 0; 496 | } 497 | 498 | void _enumerate(void* ptr, HANDLE fd, void* enumerator, void(* callback)(void*,uint16_t*,size_t)) { 499 | const cache_t& cache = *static_cast(ptr); 500 | 501 | read_lock_t lock(fd); 502 | if(cache.info.dirty) { 503 | return; 504 | } 505 | uint32_t curr = cache.info.head; 506 | 507 | while(curr) { 508 | node_t& node = *cache.address(curr); 509 | callback(enumerator, node.key, node.keyLen); 510 | curr = node.next; 511 | } 512 | } 513 | 514 | void _dump(void* ptr, HANDLE fd, void* dumper, void(* callback)(void*,uint16_t*,size_t,uint8_t*)) { 515 | const cache_t& cache = *static_cast(ptr); 516 | 517 | read_lock_t lock(fd); 518 | if(cache.info.dirty) { 519 | return; 520 | } 521 | uint32_t curr = cache.info.head; 522 | 523 | uint8_t tmp[1024]; 524 | uint8_t* val = tmp; 525 | size_t valLen = sizeof(tmp); 526 | 527 | while(curr) { 528 | node_t& node = *cache.address(curr); 529 | uint8_t* newVal = val; 530 | size_t newValLen = valLen; 531 | cache.read(curr, newVal, newValLen); 532 | if(newValLen > valLen) { 533 | if(valLen > sizeof(tmp)) delete[] val; 534 | valLen = newValLen; 535 | val = newVal; 536 | } 537 | callback(dumper, node.key, node.keyLen, newVal); 538 | curr = node.next; 539 | } 540 | if(valLen > sizeof(tmp)) delete[] val; 541 | } 542 | 543 | bool contains(void* ptr, HANDLE fd, const uint16_t* key, size_t keyLen) { 544 | const cache_t& cache = *static_cast(ptr); 545 | 546 | uint32_t hash = hashsum(key, keyLen); 547 | 548 | read_lock_t lock(fd); 549 | if(cache.info.dirty) { 550 | return false; 551 | } 552 | return cache.find(key, keyLen, hash); 553 | } 554 | 555 | bool unset(void* ptr, HANDLE fd, const uint16_t* key, size_t keyLen) { 556 | cache_t& cache = *static_cast(ptr); 557 | 558 | uint32_t hash = hashsum(key, keyLen); 559 | 560 | write_lock_t lock(fd); 561 | if(cache.info.dirty) { 562 | return false; 563 | } 564 | 565 | uint32_t found = cache.find(key, keyLen, hash); 566 | if(found) { 567 | cache.info.dirty = 1; 568 | cache.dropNode(found); 569 | cache.info.dirty = 0; 570 | } 571 | return found; 572 | } 573 | 574 | void clear(void* ptr, HANDLE fd) { 575 | cache_t& cache = *static_cast(ptr); 576 | write_lock_t lock(fd); 577 | cache.format(); 578 | } 579 | 580 | int32_t increase(void* ptr, HANDLE fd, const uint16_t* key, size_t keyLen, int32_t increase_by) { 581 | cache_t& cache = *static_cast(ptr); 582 | uint32_t hash = hashsum(key, keyLen); 583 | const uint32_t blocksRequired = 1; 584 | 585 | write_lock_t lock(fd); 586 | if(cache.info.dirty) { 587 | cache.format(); 588 | } 589 | 590 | // find if key is already exists 591 | uint32_t found = cache.find(key, keyLen, hash); 592 | node_t* selectedBlock; 593 | cache.info.dirty = 1; 594 | // fprintf(stderr, "cache::set hash=%d found=%d\n", hash, found); 595 | if(found) { // update 596 | cache.touch(found); 597 | selectedBlock = cache.address(found); 598 | node_t& node = *selectedBlock; 599 | if(node.blocks > blocksRequired) { // free extra blocks 600 | uint32_t& lastBlk = cache.next(found, blocksRequired); 601 | // drop remaining blocks 602 | cache.release(lastBlk); 603 | // fprintf(stderr, "freeing %d blocks (%d used)\n", node.blocks - blocksRequired, cache.info.blocks_used); 604 | lastBlk = 0; 605 | node.blocks = blocksRequired; 606 | selectedBlock->valLen = 0; 607 | } 608 | } else { // insert 609 | // insert into hash table 610 | found = cache.setup(blocksRequired, hash, keyLen, key); 611 | // fprintf(stderr, "cache::set allocated new block %d\n", found); 612 | selectedBlock = cache.address(found); 613 | selectedBlock->valLen = 0; 614 | } 615 | uint8_t* data = reinterpret_cast(selectedBlock) + sizeof(node_t) + (keyLen << 1); 616 | int32_t& val = *reinterpret_cast(data + 1); 617 | if(selectedBlock->valLen != 5 || data[0] != bson::Int32) { 618 | selectedBlock->valLen = 5; 619 | data[0] = bson::Int32; 620 | val = 0; 621 | } 622 | 623 | val += increase_by; 624 | cache.info.dirty = 0; 625 | return val; 626 | } 627 | 628 | } 629 | --------------------------------------------------------------------------------