Note: while switching to jemalloc might be unoptimal, it could be still useful to gather information about positive and negative implications of jemalloc usage. Let's do that in this issue. I am not advocating to switch to jemalloc (yet), but imo that is worth investigation.
In some situations that I observed, it consumes slightly more memory (~5%), but it is able to significantly reduce the memory usage by orders of magnitude in some cases (basically in a subset of cases where glibc behaves significantly unoptimal).
In some situations, jemalloc consumes significantly more memory though, but appears to be faster.
Testcase 1 (based on #21967):
'use strict';constbs=4*1024*1024;// 4 MiBconstretained=[];leti=0,flag=false;functiontick(){i++;if(i%1000===0){console.log(`RSS [${i}]: ${process.memoryUsage().rss/1024/1024} MiB`);}retained.push(Buffer.allocUnsafe(bs));if(i===5000){console.log('Clearing retained and enabling alloc');retained.length=0;flag=true;}if(flag)Buffer.alloc(bs);// Buffer.alloc(bs - 10) seems to be fine hereif(i<10000)setImmediate(tick);}tick();Atm (with Node.js v10.7.0) it produces the following results:
RSS [1000]: 35.140625 MiBRSS [2000]: 40.24609375 MiBRSS [3000]: 45.25390625 MiBRSS [4000]: 49.27734375 MiBRSS [5000]: 53.29296875 MiBClearing retained and enabling allocRSS [6000]: 993.45703125 MiBRSS [7000]: 2233.32421875 MiBRSS [8000]: 3499.56640625 MiBRSS [9000]: 4792.9140625 MiBRSS [10000]: 5997.30859375 MiB
I traced that down to C++ malloc() behavior (testcase in #21967 (comment)).
This is what happens just with LD_PRELOAD=/usr/lib/libjemalloc.so:
RSS[1000]: 36.640625MiBRSS[2000]: 42.62109375MiBRSS[3000]: 48.21875MiBRSS[4000]: 52.38671875MiBRSS[5000]: 56.7890625MiBClearingretainedandenablingallocRSS[6000]: 58.8828125MiBRSS[7000]: 62.77734375MiBRSS[8000]: 66.9140625MiBRSS[9000]: 71.2421875MiBRSS[10000]: 75.453125MiB
Testcase 2:
constarr=[];for(leti=0;i<1e4;i++)arr.push(Buffer.alloc(1e5));console.log(`RSS: ${process.memoryUsage().rss/1024/1024} MiB`);Normal — 697 MiB, jemalloc — 37 MiB.
Testcase 3 (jemalloc consumes more memory):
functionfoo(){leta;for(leti=0;i<5;i++)a=Buffer.alloc(1e8,1);console.log(`RSS: ${process.memoryUsage().rss/1024/1024} MiB`);}conststart=process.hrtime();foo();foo();gc();gc();console.log(`RSS: ${process.memoryUsage().rss/1024/1024} MiB`);foo();consttime=process.hrtime(start);console.log('Time:',time[0]+time[1]*1e-9);Normal:
RSS: 506.8125 MiB
RSS: 507.98046875 MiB
RSS: 31.2421875 MiB
RSS: 507.8828125 MiB
Time: 0.774285919
jemalloc:
RSS: 507.87890625 MiB
RSS: 604.40625 MiB
RSS: 604.625 MiB
RSS: 604.625 MiB
Time: 0.349531212
Testcase 4:
'use strict';leti=0;functiontick(){consta=Buffer.alloc(1e7);if(i++>=1e4)return;setImmediate(tick);}tick();Measured with /usr/bin/time -f '%M KiB, %e seconds' node testcase-4.js.
Normal (1e4 * 1e7): 129 576 KiB, 11.41 seconds.
jemalloc (1e4 * 1e7): 34 928 KiB, 3.33 seconds.
Normal (1e5 * 1e6): 109 196 KiB, 11.51 seconds.
jemalloc (1e5 * 1e6): 35 636 KiB, 4.13 seconds.
Testcase 5 (like 4, but now we fill the buffer with 1-s):
'use strict';leti=0;functiontick(){consta=Buffer.alloc(1e7,1);if(i++>=1e4)return;setImmediate(tick);}tick();Normal (1e4 * 1e7): 139 060 KiB, 14.38 seconds.
jemalloc (1e4 * 1e7): 170 308 KiB, 10.65 seconds.
Normal (1e5 * 1e6): 105 120 KiB, 12.25 seconds.
jemalloc (1e5 * 1e6): 112 548 KiB, 10.99 seconds.
Testcase 6 (from #8871, where @bnoordhuis mentioned jemalloc):
constzlib=require('zlib');constpayload=Buffer.from(JSON.stringify({some:"data"}));for(leti=0;i<30000;++i)zlib.deflate(payload,()=>{});No improvement in this case and a 5% loss.
Normal: 3 022 496 KiB, 5.88 seconds.
jemalloc: 3 179 716 KiB, 5.91 seconds.
/cc @addaleax@bnoordhuis@mscdex
Note: while switching to
jemallocmight be unoptimal, it could be still useful to gather information about positive and negative implications ofjemallocusage. Let's do that in this issue. I am not advocating to switch tojemalloc(yet), but imo that is worth investigation.In some situations that I observed, it consumes slightly more memory (~5%), but it is able to significantly reduce the memory usage by orders of magnitude in some cases (basically in a subset of cases where
glibcbehaves significantly unoptimal).In some situations, jemalloc consumes significantly more memory though, but appears to be faster.
Testcase 1 (based on #21967):
Atm (with Node.js v10.7.0) it produces the following results:
I traced that down to C++
malloc()behavior (testcase in #21967 (comment)).This is what happens just with
LD_PRELOAD=/usr/lib/libjemalloc.so:Testcase 2:
Normal —
697 MiB, jemalloc —37 MiB.Testcase 3 (jemalloc consumes more memory):
Normal:
jemalloc:
Testcase 4:
Measured with
/usr/bin/time -f '%M KiB, %e seconds' node testcase-4.js.Normal (1e4 * 1e7):
129 576 KiB, 11.41 seconds.jemalloc (1e4 * 1e7):
34 928 KiB, 3.33 seconds.Normal (1e5 * 1e6):
109 196 KiB, 11.51 seconds.jemalloc (1e5 * 1e6):
35 636 KiB, 4.13 seconds.Testcase 5 (like 4, but now we fill the buffer with
1-s):Normal (1e4 * 1e7):
139 060 KiB, 14.38 seconds.jemalloc (1e4 * 1e7):
170 308 KiB, 10.65 seconds.Normal (1e5 * 1e6):
105 120 KiB, 12.25 seconds.jemalloc (1e5 * 1e6):
112 548 KiB, 10.99 seconds.Testcase 6 (from #8871, where @bnoordhuis mentioned
jemalloc):No improvement in this case and a 5% loss.
Normal:
3 022 496 KiB, 5.88 seconds.jemalloc:
3 179 716 KiB, 5.91 seconds./cc @addaleax@bnoordhuis@mscdex