QuickJS 是大神 (Fabrice Bellard) 写的一个 JavaScript 引擎
优势:
1. 纯 C,没外部库依赖
2. 代码体积小,Hello world 在 x86 上只占 200 多 K
3. 通过 ECMAScript 标准测试,支持 ES2019 绝大部份
4. Benchmark 性能也非常不错,具体看官网报告
其它详见: https://bellard.org/quickjs
今天给大家分享一个他的内存分配接口设计
struct JSRuntime {
JSMallocFunctions mf;
JSMallocState malloc_state;
const char *rt_info;
...
struct list_head gc_obj_list;
...
};
这是虚拟机运行结构体
创建函数看一下
JSRuntime *JS_NewRuntime(void)
{
return JS_NewRuntime2(&def_malloc_funcs, NULL);
}
JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque)
{
...
}
我们关注 JSMallocFunctions 及 JSMallocState 二个结构体, def_malloc_funcs 的参数在下面实现。
typedef struct JSMallocState {
size_t malloc_count;
size_t malloc_size;
size_t malloc_limit;
void *opaque; /* user opaque */
} JSMallocState;
typedef struct JSMallocFunctions {
void *(*js_malloc)(JSMallocState *s, size_t size);
void (*js_free)(JSMallocState *s, void *ptr);
void *(*js_realloc)(JSMallocState *s, void *ptr, size_t size);
size_t (*js_malloc_usable_size)(const void *ptr);
} JSMallocFunctions;
这里的一个小技巧是,用函数指针组成的结构体,来实现 C++ 的类接口,来用实现内存分配及销毁的钩子,实现内存分配器可外接
static const JSMallocFunctions def_malloc_funcs = {
js_def_malloc,
js_def_free,
js_def_realloc,
#if defined(__APPLE__)
malloc_size,
#elif defined(_WIN32)
(size_t (*)(const void *))_msize,
#elif defined(EMSCRIPTEN)
NULL,
#elif defined(__linux__)
(size_t (*)(const void *))malloc_usable_size,
#else
/* change this to `NULL,` if compilation fails */
malloc_usable_size,
#endif
};
这里用一个静态结构给来定义默认实现,我们可以关注一下 js_def_malloc 的玩法,看看是如何默认实现的,看代码
static void *js_def_malloc(JSMallocState *s, size_t size)
{
void *ptr;
/* Do not allocate zero bytes: behavior is platform dependent */
assert(size != 0);
if (unlikely(s->malloc_size + size > s->malloc_limit))
return NULL;
ptr = malloc(size);
if (!ptr)
return NULL;
s->malloc_count++;
s->malloc_size += js_def_malloc_usable_size(ptr) + MALLOC_OVERHEAD;
return ptr;
}
可以看出,在内存分配的时候,用到了 JSMallocState 进行记录,这样可以很容易的实现最初级的内存泄露检查,那么问题来了,可是如果一但 malloc_count 真不归 0,如何排查是哪块有泄露呢?
这块作者没有在 JSMallocState 做更详细的记录,因为引擎除了工作需要申请的对像,大量都是 Script 中引来的对像,这个对像有一个 GC 管理器,是在 Runtime 里有一个 gc_obj_list,链表进行管理的,在宏 DUMP_LEAKS 生效后,能够检测出来。
虽然没有太多的技术含量,也要总结一下:
1. 内存管理设计的时候一定可让外界接管,包括 SGI 里也是一样的,方便不同环境用不同的内存分配器优化。
2. 通过函数结构体,可以很容易的实现面向接口变程,方便对接各种系统
3. 对外扩展是灵活的,内部是相对封闭的,符合开闭原则。
功能越是简单,越是基础,越不能偷懒