Skip to content

Instantly share code, notes, and snippets.

@colinbendell
Last active December 16, 2021 20:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save colinbendell/42eaacf3b0c76beb0326727da2061d12 to your computer and use it in GitHub Desktop.
Save colinbendell/42eaacf3b0c76beb0326727da2061d12 to your computer and use it in GitHub Desktop.
Compare Javascript v8 .slice.pop() vs. .slice(-1) memory and timing performance
const { PerformanceObserver, performance, constants } = require('perf_hooks');
const MAX_TEST_SIZE = Math.pow(2,16); //65536
// pre-allocate an array filled with objects (primatives like Number and Boolean have internal cpp optimizations)
const a = new Array(MAX_TEST_SIZE).map(v => ({}));
// pre-allocate an array for the results. this way we don't count the heap overhead from assigning the target variables
const b = new Array(MAX_TEST_SIZE);
let totalHeapUsed = 0;
let heapUsed = 0;
let lastHeapUsed = 0;
let duration = 0;
let gcCount = 0;
let gcTime = 0;
// Create a performance observer
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'measure' && entry.name === 'evaltime') {
duration += entry.duration;
}
else if (entry.entryType === 'measure' && entry.name === 'internal-time') {
duration -= entry.duration;
// console.log(entry.duration);
}
else if (entry.entryType === 'gc') {
// node 16 adopted UserTiming Level3. Accessing other property methods (like entry.kind for nod 15) is considered deprecated
if (entry.detail?.kind !== constants.NODE_PERFORMANCE_GC_MINOR) {
// for debugging, trying to minimize GC_MAJOR and TIME based GC events
console.log(entry.detail);
}
gcCount++;
gcTime += entry.duration;
}
}
let message = `duration: ${Math.round(duration)/1000}s used-heap: ${Math.round((totalHeapUsed)/1024/1024*10)/10}MB`;
if (process.argv.includes('--gc')) message += ` (gc-count: ${gcCount} gc-time: ${Math.round(gcTime)/1000}s)`;
console.log(message);
performance.clearMarks();
});
observer.observe({ entryTypes: ['gc', 'measure'], buffered: true });
// calling process.memoryUsage() isn't free. we do an internal timing to net out at the end
performance.mark('internal-start')
for (const i in [...Array(MAX_TEST_SIZE)]) {
process.memoryUsage();
}
performance.mark('internal-end')
performance.measure('internal-time', 'internal-start', 'internal-end');
performance.mark('start');
// zero out our total heap used so far
totalHeapUsed -= (lastHeapUsed = process.memoryUsage().heapUsed);
for (const i in [...Array(MAX_TEST_SIZE)]) {
if (process.argv.includes('--baseline')) {
// SCENARIO 1: vanillaJS that uses a.length
// const b = a.length > 0 ? a[a.length - 1] : null;
const c = a[a.length - 1];
b[i] = c;
}
else if (process.argv.includes('--pop')) {
// SCENARIO 2: use .slice().pop()
// const b = a.slice().pop();
const c = a.slice().pop();
b[i] = c;
}
else {
// SCENARIO 3: use .slice(-1)
// const [b] = a.slice(-1);
const [c] = a.slice(-1);
b[i] = c;
}
// simple-person's heap tracking, but we have to account for GC threads
heapUsed = process.memoryUsage().heapUsed;
if (lastHeapUsed > heapUsed) {
totalHeapUsed += (lastHeapUsed - heapUsed);
// console.log("used-heap", Math.round((lastHeapUsed - heapUsed)/1024/1024*10)/10 + "MB");
}
lastHeapUsed = heapUsed;
}
performance.mark('end');
performance.measure('evaltime', 'start', 'end');
totalHeapUsed += process.memoryUsage().heapUsed;
//observer is called at the end of execution since there isn't any other yielding
@colinbendell
Copy link
Author

colinbendell commented Jul 4, 2021

Use Case: const [value] = arraylist.slice(-1)

$ repeat 10 { node --max-heap-size=4024 --initial-heap-size=4024  --predictable-gc-schedule  --scavenge-task-trigger=100 --no-scavenge-task --huge-max-old-generation-size js-get-last-array-entry.js }
duration: 0.018s used-heap: 5.8MB
duration: 0.021s used-heap: 5.8MB
duration: 0.021s used-heap: 5.8MB
duration: 0.021s used-heap: 5.8MB
duration: 0.015s used-heap: 5.8MB
duration: 0.016s used-heap: 5.8MB
duration: 0.022s used-heap: 5.8MB
duration: 0.025s used-heap: 5.8MB
duration: 0.016s used-heap: 5.8MB
duration: 0.019s used-heap: 5.8MB

@colinbendell
Copy link
Author

colinbendell commented Jul 4, 2021

Use Case: const value = arraylist.slice().pop()

$ repeat 10 { node --max-heap-size=4024 --initial-heap-size=4024  --predictable-gc-schedule  --scavenge-task-trigger=100 --no-scavenge-task --huge-max-old-generation-size js-get-last-array-entry.js --pop }
duration: 23.869s used-heap: 28091.2MB
duration: 23.505s used-heap: 28091.2MB
duration: 24.786s used-heap: 28091.2MB
duration: 26.613s used-heap: 28091.2MB
duration: 25.420s used-heap: 28091.2MB
duration: 25.069s used-heap: 28091.2MB
duration: 25.915s used-heap: 28091.2MB 
duration: 23.455s used-heap: 28091.2MB
duration: 23.662s used-heap: 28091.2MB
duration: 23.538s used-heap: 28091.2MB

@colinbendell
Copy link
Author

colinbendell commented Jul 4, 2021

Use Case: const value = arraylist[arraylist.length - 1]

$ repeat 10 { node --max-heap-size=4024 --initial-heap-size=4024  --predictable-gc-schedule  --scavenge-task-trigger=100 --no-scavenge-task --huge-max-old-generation-size js-get-last-array-entry.js --baseline }
duration: 0.004s used-heap: 2.8MB
duration: 0.008s used-heap: 2.8MB
duration: 0.005s used-heap: 2.8MB
duration: 0.003s used-heap: 2.8MB
duration: 0.003s used-heap: 2.8MB
duration: 0.003s used-heap: 2.8MB
duration: 0.006s used-heap: 2.8MB
duration: 0.003s used-heap: 2.8MB
duration: 0.003s used-heap: 2.8MB
duration: 0.006s used-heap: 2.8MB

@colinbendell
Copy link
Author

To summarize:

  • .slice(-1) - 2.1x more memory, 4.2x slower than vanilla JS version
  • .slice().pop() - 10,000x more memory, 5,600x slower than vanilla JS
  • .slice().pop() - 4,800x more memory, 1,300x slower than .slice(-1)

@duncan
Copy link

duncan commented Jul 5, 2021

I tried to run this and got a TypeError: Cannot read property 'kind' of undefined from line 27

@colinbendell
Copy link
Author

colinbendell commented Jul 5, 2021

I tried to run this and got a TypeError: Cannot read property 'kind' of undefined from line 27

What version of node are you running? I've only tested with 16.1.0

@duncan
Copy link

duncan commented Jul 5, 2021

That was it. I was on 15.x there. Upgraded to 16.4.1 and all is happy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment