mem: 优化 C 内存统计, 缓解多线程竞争导致的 skynet_malloc 性能下降 (#2083)

* mem: 优化 C 内存统计, 缓解多线程竞争导致的 skynet_malloc 性能下降

* rm unused SPMC_STEP_LG macro

---------

Co-authored-by: efve.zff <efve.zff@alibaba-inc.com>
This commit is contained in:
EfveZombie
2025-09-11 21:23:07 +08:00
committed by GitHub
parent 754932cef4
commit 528b6bd32f
5 changed files with 182 additions and 69 deletions

View File

@@ -29,13 +29,13 @@ JEMALLOC_STATICLIB := 3rd/jemalloc/lib/libjemalloc_pic.a
JEMALLOC_INC := 3rd/jemalloc/include/jemalloc JEMALLOC_INC := 3rd/jemalloc/include/jemalloc
all : jemalloc all : jemalloc
.PHONY : jemalloc update3rd .PHONY : jemalloc update3rd
MALLOC_STATICLIB := $(JEMALLOC_STATICLIB) MALLOC_STATICLIB := $(JEMALLOC_STATICLIB)
$(JEMALLOC_STATICLIB) : 3rd/jemalloc/Makefile $(JEMALLOC_STATICLIB) : 3rd/jemalloc/Makefile
cd 3rd/jemalloc && $(MAKE) CC=$(CC) cd 3rd/jemalloc && $(MAKE) CC=$(CC)
3rd/jemalloc/autogen.sh : 3rd/jemalloc/autogen.sh :
git submodule update --init git submodule update --init
@@ -74,12 +74,12 @@ LUA_CLIB_SKYNET = \
SKYNET_SRC = skynet_main.c skynet_handle.c skynet_module.c skynet_mq.c \ SKYNET_SRC = skynet_main.c skynet_handle.c skynet_module.c skynet_mq.c \
skynet_server.c skynet_start.c skynet_timer.c skynet_error.c \ skynet_server.c skynet_start.c skynet_timer.c skynet_error.c \
skynet_harbor.c skynet_env.c skynet_monitor.c skynet_socket.c socket_server.c \ skynet_harbor.c skynet_env.c skynet_monitor.c skynet_socket.c socket_server.c \
malloc_hook.c skynet_daemon.c skynet_log.c mem_info.c malloc_hook.c skynet_daemon.c skynet_log.c
all : \ all : \
$(SKYNET_BUILD_PATH)/skynet \ $(SKYNET_BUILD_PATH)/skynet \
$(foreach v, $(CSERVICE), $(CSERVICE_PATH)/$(v).so) \ $(foreach v, $(CSERVICE), $(CSERVICE_PATH)/$(v).so) \
$(foreach v, $(LUA_CLIB), $(LUA_CLIB_PATH)/$(v).so) $(foreach v, $(LUA_CLIB), $(LUA_CLIB_PATH)/$(v).so)
$(SKYNET_BUILD_PATH)/skynet : $(foreach v, $(SKYNET_SRC), skynet-src/$(v)) $(LUA_LIB) $(MALLOC_STATICLIB) $(SKYNET_BUILD_PATH)/skynet : $(foreach v, $(SKYNET_SRC), skynet-src/$(v)) $(LUA_LIB) $(MALLOC_STATICLIB)
$(CC) $(CFLAGS) -o $@ $^ -Iskynet-src -I$(JEMALLOC_INC) $(LDFLAGS) $(EXPORT) $(SKYNET_LIBS) $(SKYNET_DEFINES) $(CC) $(CFLAGS) -o $@ $^ -Iskynet-src -I$(JEMALLOC_INC) $(LDFLAGS) $(EXPORT) $(SKYNET_LIBS) $(SKYNET_DEFINES)
@@ -104,19 +104,19 @@ $(LUA_CLIB_PATH)/bson.so : lualib-src/lua-bson.c | $(LUA_CLIB_PATH)
$(CC) $(CFLAGS) $(SHARED) -Iskynet-src $^ -o $@ $(CC) $(CFLAGS) $(SHARED) -Iskynet-src $^ -o $@
$(LUA_CLIB_PATH)/md5.so : 3rd/lua-md5/md5.c 3rd/lua-md5/md5lib.c 3rd/lua-md5/compat-5.2.c | $(LUA_CLIB_PATH) $(LUA_CLIB_PATH)/md5.so : 3rd/lua-md5/md5.c 3rd/lua-md5/md5lib.c 3rd/lua-md5/compat-5.2.c | $(LUA_CLIB_PATH)
$(CC) $(CFLAGS) $(SHARED) -I3rd/lua-md5 $^ -o $@ $(CC) $(CFLAGS) $(SHARED) -I3rd/lua-md5 $^ -o $@
$(LUA_CLIB_PATH)/client.so : lualib-src/lua-clientsocket.c lualib-src/lua-crypt.c lualib-src/lsha1.c | $(LUA_CLIB_PATH) $(LUA_CLIB_PATH)/client.so : lualib-src/lua-clientsocket.c lualib-src/lua-crypt.c lualib-src/lsha1.c | $(LUA_CLIB_PATH)
$(CC) $(CFLAGS) $(SHARED) $^ -o $@ -lpthread $(CC) $(CFLAGS) $(SHARED) $^ -o $@ -lpthread
$(LUA_CLIB_PATH)/sproto.so : lualib-src/sproto/sproto.c lualib-src/sproto/lsproto.c | $(LUA_CLIB_PATH) $(LUA_CLIB_PATH)/sproto.so : lualib-src/sproto/sproto.c lualib-src/sproto/lsproto.c | $(LUA_CLIB_PATH)
$(CC) $(CFLAGS) $(SHARED) -Ilualib-src/sproto $^ -o $@ $(CC) $(CFLAGS) $(SHARED) -Ilualib-src/sproto $^ -o $@
$(LUA_CLIB_PATH)/ltls.so : lualib-src/ltls.c | $(LUA_CLIB_PATH) $(LUA_CLIB_PATH)/ltls.so : lualib-src/ltls.c | $(LUA_CLIB_PATH)
$(CC) $(CFLAGS) $(SHARED) -Iskynet-src -L$(TLS_LIB) -I$(TLS_INC) $^ -o $@ -lssl $(CC) $(CFLAGS) $(SHARED) -Iskynet-src -L$(TLS_LIB) -I$(TLS_INC) $^ -o $@ -lssl
$(LUA_CLIB_PATH)/lpeg.so : 3rd/lpeg/lpcap.c 3rd/lpeg/lpcode.c 3rd/lpeg/lpprint.c 3rd/lpeg/lptree.c 3rd/lpeg/lpvm.c 3rd/lpeg/lpcset.c | $(LUA_CLIB_PATH) $(LUA_CLIB_PATH)/lpeg.so : 3rd/lpeg/lpcap.c 3rd/lpeg/lpcode.c 3rd/lpeg/lpprint.c 3rd/lpeg/lptree.c 3rd/lpeg/lpvm.c 3rd/lpeg/lpcset.c | $(LUA_CLIB_PATH)
$(CC) $(CFLAGS) $(SHARED) -I3rd/lpeg $^ -o $@ $(CC) $(CFLAGS) $(SHARED) -I3rd/lpeg $^ -o $@
clean : clean :
rm -f $(SKYNET_BUILD_PATH)/skynet $(CSERVICE_PATH)/*.so $(LUA_CLIB_PATH)/*.so && \ rm -f $(SKYNET_BUILD_PATH)/skynet $(CSERVICE_PATH)/*.so $(LUA_CLIB_PATH)/*.so && \
@@ -128,4 +128,3 @@ ifneq (,$(wildcard 3rd/jemalloc/Makefile))
endif endif
cd 3rd/lua && $(MAKE) clean cd 3rd/lua && $(MAKE) clean
rm -f $(LUA_STATICLIB) rm -f $(LUA_STATICLIB)

View File

@@ -1,27 +1,27 @@
#include <stdio.h>
#include <string.h> #include <string.h>
#include <assert.h> #include <assert.h>
#include <stdlib.h> #include <stdlib.h>
#include <lua.h>
#include <stdio.h> #include <stdio.h>
#include "malloc_hook.h" #include <lauxlib.h>
#include "skynet.h" #include "skynet.h"
#include "atomic.h" #include "atomic.h"
#include "malloc_hook.h"
// turn on MEMORY_CHECK can do more memory check, such as double free // turn on MEMORY_CHECK can do more memory check, such as double free
// #define MEMORY_CHECK // #define MEMORY_CHECK
#define MEMORY_ALLOCTAG 0x20140605 #define MEMORY_ALLOCTAG 0x20140605
#define MEMORY_FREETAG 0x0badf00d #define MEMORY_FREETAG 0x0badf00d
static ATOM_SIZET _used_memory = 0;
static ATOM_SIZET _memory_block = 0;
struct mem_data { struct mem_data {
ATOM_ULONG handle; alignas(CACHE_LINE_SIZE)
ATOM_SIZET allocated; ATOM_ULONG handle;
AtomicMemInfo info;
}; };
_Static_assert(sizeof(struct mem_data) % CACHE_LINE_SIZE == 0, "mem_data must be cache-line aligned");
struct mem_cookie { struct mem_cookie {
size_t size; size_t size;
@@ -36,7 +36,14 @@ struct mem_cookie {
#define PREFIX_SIZE sizeof(struct mem_cookie) #define PREFIX_SIZE sizeof(struct mem_cookie)
static struct mem_data mem_stats[SLOT_SIZE]; static struct mem_data mem_stats[SLOT_SIZE];
_Static_assert(alignof(mem_stats) % CACHE_LINE_SIZE == 0, "mem_stats must be cache-line aligned");
static struct mem_data *
get_mem_stat(uint32_t handle) {
int h = (int)(handle & (SLOT_SIZE - 1));
struct mem_data *data = &mem_stats[h];
return data;
}
#ifndef NOUSE_JEMALLOC #ifndef NOUSE_JEMALLOC
@@ -46,45 +53,19 @@ static struct mem_data mem_stats[SLOT_SIZE];
#define raw_realloc je_realloc #define raw_realloc je_realloc
#define raw_free je_free #define raw_free je_free
static ATOM_SIZET *
get_allocated_field(uint32_t handle) {
int h = (int)(handle & (SLOT_SIZE - 1));
struct mem_data *data = &mem_stats[h];
uint32_t old_handle = data->handle;
ssize_t old_alloc = (ssize_t)data->allocated;
if(old_handle == 0 || old_alloc <= 0) {
// data->allocated may less than zero, because it may not count at start.
if(!ATOM_CAS_ULONG(&data->handle, old_handle, handle)) {
return 0;
}
if (old_alloc < 0) {
ATOM_CAS_SIZET(&data->allocated, (size_t)old_alloc, 0);
}
}
if(data->handle != handle) {
return 0;
}
return &data->allocated;
}
inline static void inline static void
update_xmalloc_stat_alloc(uint32_t handle, size_t __n) { update_xmalloc_stat_alloc(uint32_t handle, size_t __n) {
ATOM_FADD(&_used_memory, __n); struct mem_data *data = get_mem_stat(handle);
ATOM_FINC(&_memory_block); // 当两个不同的 handle 被哈希到同一个槽位时, 新的服务会覆盖旧服务的数据
ATOM_SIZET * allocated = get_allocated_field(handle); // 这种情况在实际运行中非常罕见, 因为同时存在的服务数量很难超过 65536
if(allocated) { ATOM_STORE(&data->handle, handle);
ATOM_FADD(allocated, __n); atomic_meminfo_alloc(&data->info, __n);
}
} }
inline static void inline static void
update_xmalloc_stat_free(uint32_t handle, size_t __n) { update_xmalloc_stat_free(uint32_t handle, size_t __n) {
ATOM_FSUB(&_used_memory, __n); struct mem_data *data = get_mem_stat(handle);
ATOM_FDEC(&_memory_block); atomic_meminfo_free(&data->info, __n);
ATOM_SIZET * allocated = get_allocated_field(handle);
if(allocated) {
ATOM_FSUB(allocated, __n);
}
} }
inline static void* inline static void*
@@ -92,6 +73,7 @@ fill_prefix(char* ptr, size_t sz, uint32_t cookie_size) {
uint32_t handle = skynet_current_handle(); uint32_t handle = skynet_current_handle();
struct mem_cookie *p = (struct mem_cookie *)ptr; struct mem_cookie *p = (struct mem_cookie *)ptr;
char * ret = ptr + cookie_size; char * ret = ptr + cookie_size;
sz += cookie_size;
p->size = sz; p->size = sz;
p->handle = handle; p->handle = handle;
#ifdef MEMORY_CHECK #ifdef MEMORY_CHECK
@@ -300,27 +282,55 @@ mallctl_cmd(const char* name) {
size_t size_t
malloc_used_memory(void) { malloc_used_memory(void) {
return ATOM_LOAD(&_used_memory); MemInfo total = {};
for(int i = 0; i < SLOT_SIZE; i++) {
struct mem_data* data = &mem_stats[i];
const uint32_t handle = ATOM_LOAD(&data->handle);
if (handle != 0) {
atomic_meminfo_merge(&total, &data->info);
}
}
return total.alloc - total.free;
} }
size_t size_t
malloc_memory_block(void) { malloc_memory_block(void) {
return ATOM_LOAD(&_memory_block); MemInfo total = {};
for(int i = 0; i < SLOT_SIZE; i++) {
struct mem_data* data = &mem_stats[i];
const uint32_t handle = ATOM_LOAD(&data->handle);
if (handle != 0) {
atomic_meminfo_merge(&total, &data->info);
}
}
return total.alloc_count - total.free_count;
} }
void void
dump_c_mem() { dump_c_mem() {
int i;
size_t total = 0;
skynet_error(NULL, "dump all service mem:"); skynet_error(NULL, "dump all service mem:");
for(i=0; i<SLOT_SIZE; i++) { MemInfo total = {};
for(int i = 0; i < SLOT_SIZE; i++) {
struct mem_data* data = &mem_stats[i]; struct mem_data* data = &mem_stats[i];
if(data->handle != 0 && data->allocated != 0) { const uint32_t handle = ATOM_LOAD(&data->handle);
total += data->allocated; if (handle != 0) {
skynet_error(NULL, ":%08x -> %zdkb %db", data->handle, data->allocated >> 10, (int)(data->allocated % 1024)); MemInfo info = {};
atomic_meminfo_merge(&info, &data->info);
meminfo_merge(&total, &info);
const size_t using = info.alloc - info.free;
skynet_error(NULL, ":%08x -> %zukb %zub", handle, using >> 10, using);
} }
} }
skynet_error(NULL, "+total: %zdkb",total >> 10); const size_t using = total.alloc - total.free;
skynet_error(NULL, "+total: %zukb", using >> 10);
}
char *
skynet_strdup(const char *str) {
size_t sz = strlen(str);
char * ret = skynet_malloc(sz+1);
memcpy(ret, str, sz+1);
return ret;
} }
void * void *
@@ -339,9 +349,12 @@ dump_mem_lua(lua_State *L) {
lua_newtable(L); lua_newtable(L);
for(i=0; i<SLOT_SIZE; i++) { for(i=0; i<SLOT_SIZE; i++) {
struct mem_data* data = &mem_stats[i]; struct mem_data* data = &mem_stats[i];
if(data->handle != 0 && data->allocated != 0) { const uint32_t handle = ATOM_LOAD(&data->handle);
lua_pushinteger(L, data->allocated); if (handle != 0) {
lua_rawseti(L, -2, (lua_Integer)data->handle); MemInfo info = {};
atomic_meminfo_merge(&info, &data->info);
lua_pushinteger(L, info.alloc - info.free);
lua_rawseti(L, -2, handle);
} }
} }
return 1; return 1;
@@ -350,14 +363,13 @@ dump_mem_lua(lua_State *L) {
size_t size_t
malloc_current_memory(void) { malloc_current_memory(void) {
uint32_t handle = skynet_current_handle(); uint32_t handle = skynet_current_handle();
int i; struct mem_data *data = get_mem_stat(handle);
for(i=0; i<SLOT_SIZE; i++) { if (ATOM_LOAD(&data->handle) != handle) {
struct mem_data* data = &mem_stats[i]; return 0;
if(data->handle == (uint32_t)handle && data->allocated != 0) {
return (size_t) data->allocated;
}
} }
return 0; MemInfo info = {};
atomic_meminfo_merge(&info, &data->info);
return info.alloc - info.free;
} }
void void

View File

@@ -5,6 +5,8 @@
#include <stdbool.h> #include <stdbool.h>
#include <lua.h> #include <lua.h>
#include "mem_info.h"
extern size_t malloc_used_memory(void); extern size_t malloc_used_memory(void);
extern size_t malloc_memory_block(void); extern size_t malloc_memory_block(void);
extern void memory_info_dump(const char *opts); extern void memory_info_dump(const char *opts);
@@ -17,4 +19,3 @@ extern int dump_mem_lua(lua_State *L);
extern size_t malloc_current_memory(void); extern size_t malloc_current_memory(void);
#endif /* SKYNET_MALLOC_HOOK_H */ #endif /* SKYNET_MALLOC_HOOK_H */

59
skynet-src/mem_info.c Normal file
View File

@@ -0,0 +1,59 @@
#include <string.h>
#include "mem_info.h"
void
meminfo_init(MemInfo *info) {
memset(info, 0, sizeof(*info));
}
void
atomic_meminfo_init(AtomicMemInfo *info) {
ATOM_INIT(&info->alloc, 0);
ATOM_INIT(&info->alloc_count, 0);
ATOM_INIT(&info->free, 0);
ATOM_INIT(&info->free_count, 0);
}
void
meminfo_alloc(MemInfo *info, size_t size) {
info->alloc += size;
++info->alloc_count;
}
void
atomic_meminfo_alloc(AtomicMemInfo *info, size_t size) {
ATOM_FADD(&info->alloc, size);
ATOM_FADD(&info->alloc_count, 1);
}
void
meminfo_free(MemInfo *info, size_t size) {
info->free += size;
++info->free_count;
}
void
atomic_meminfo_free(AtomicMemInfo *info, size_t size) {
ATOM_FADD(&info->free, size);
ATOM_FADD(&info->free_count, 1);
}
void
meminfo_merge(MemInfo *dest, const MemInfo *src) {
dest->alloc += src->alloc;
dest->alloc_count += src->alloc_count;
dest->free += src->free;
dest->free_count += src->free_count;
}
void
atomic_meminfo_merge(MemInfo *dest, const AtomicMemInfo *src) {
MemInfo info;
// 先加载 free 后加载 alloc 避免大小错乱
info.free_count = ATOM_LOAD(&src->free_count);
info.free = ATOM_LOAD(&src->free);
info.alloc_count = ATOM_LOAD(&src->alloc_count);
info.alloc = ATOM_LOAD(&src->alloc);
meminfo_merge(dest, &info);
}

42
skynet-src/mem_info.h Normal file
View File

@@ -0,0 +1,42 @@
#ifndef _MEM_INFO_H_
#define _MEM_INFO_H_
#include <stdalign.h>
#include <stddef.h>
#include "atomic.h"
#define CACHE_LINE_SIZE 64
typedef struct {
size_t alloc;
size_t alloc_count;
size_t free;
size_t free_count;
} MemInfo;
typedef struct {
// alloc 与 free 可能不在一个线程
// 为了避免竞争,这里也拆成两个 CacheLine 大小
alignas(CACHE_LINE_SIZE)
ATOM_SIZET alloc;
ATOM_SIZET alloc_count;
alignas(CACHE_LINE_SIZE)
ATOM_SIZET free;
ATOM_SIZET free_count;
} AtomicMemInfo;
void meminfo_init(MemInfo *info);
void atomic_meminfo_init(AtomicMemInfo *info);
void meminfo_alloc(MemInfo *info, size_t size);
void atomic_meminfo_alloc(AtomicMemInfo *info, size_t size);
void meminfo_free(MemInfo *info, size_t size);
void atomic_meminfo_free(AtomicMemInfo *info, size_t size);
void meminfo_merge(MemInfo *dest, const MemInfo *src);
void atomic_meminfo_merge(MemInfo *dest, const AtomicMemInfo *src);
#endif // _MEM_INFO_H_