问题
系统原先使用OTP R15B03版本,在升级到OTP 18.0后,CPU下降20%左右(我会告诉你升级就是为了这个么~),但同时发现erlang内存使用比原先高出4倍多。代码、测试环境和用户量均没有变化。通过系统命令发现升级后binary消耗内存显著增加。
升级前,
> erlang:memory(binary).
132252416
升级后,
> erlang:memory(binary).
487293840
排查
参考Erlang-In-Anger使用recon API recon:bin_leak/1和recon:proc_count/2,并没有发现特定进程占用大量binary的现象。系统启动后不接入用户,并没有观察到内存有显著增长。在接入用户并且用户数量稳定一段时间后,binary内存开销逐渐稳定.
当清空用户后,binary内存回落。故推测内存开销和用户行为相关。
随机抓取用户pid,通过erlang:process_info(Pid, binary)查看用户binary内存开销。发现OTP升级后,用户进程会多出4000 bytes的ref binary开销。
> process_info(pid(0,4398,0),binary).
{binary,[{
2017145916624,128,3},
{
2017145923600,4000,3}, %% <-升级后出现
{
2017144817240,176,2}
...]}
系统中每个用户只会处理基本的消息收发,并且消息数据量远远小于4000 bytes,故推测4000 bytes应该是由erlang库函数导致的。搜索erlang源代码,在zlib中有如下
#define DEFAULT_BUFSZ 4000
...
static ErlDrvData zlib_start(ErlDrvPort port, char* buf)
{
ZLibData* d;
if ((d = (ZLibData*) driver_alloc(sizeof(ZLibData))) == NULL)
return ERL_DRV_ERROR_GENERAL;
memset(&d->s, 0, sizeof(z_stream));
d->s.zalloc = zlib_alloc;
d->s.zfree = zlib_free;
d->s.opaque = d;
d->s.data_type = Z_BINARY;
d->port = port;
d->state = ST_NONE;
d->bin = NULL;
d->binsz = 0;
d->binsz_need = DEFAULT_BUFSZ; /* initial buf size for zlib driver*/
d->crc = crc32(0L, Z_NULL, 0);
d->inflate_eos_seen = 0;
d->want_crc = 0;
return (ErlDrvData)d;
}
在用户执行的代码里,我们使用zlib对用户数据进行压缩并存储
compress(UState) ->
...
zlib:zip(term_to_binary(UData)).