{"id":31770994,"url":"https://github.com/ishtms/v8-perf","last_synced_at":"2025-10-10T03:18:35.736Z","repository":{"id":312640214,"uuid":"1046014010","full_name":"ishtms/v8-perf","owner":"ishtms","description":"Microbenchmarks exploring V8 performance quirks in real-world code.","archived":false,"fork":false,"pushed_at":"2025-08-28T04:33:14.000Z","size":5,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-09-24T05:48:04.629Z","etag":null,"topics":["benchmark","bits","nodejs","optimisation","performance","v8"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ishtms.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-08-28T04:07:34.000Z","updated_at":"2025-08-28T08:39:37.000Z","dependencies_parsed_at":"2025-09-01T05:55:55.262Z","dependency_job_id":null,"html_url":"https://github.com/ishtms/v8-perf","commit_stats":null,"previous_names":["ishtms/v8-perf"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ishtms/v8-perf","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ishtms%2Fv8-perf","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ishtms%2Fv8-perf/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ishtms%2Fv8-perf/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ishtms%2Fv8-perf/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ishtms","download_url":"https://codeload.github.com/ishtms/v8-perf/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ishtms%2Fv8-perf/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279002582,"owners_count":26083420,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-10T02:00:06.843Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["benchmark","bits","nodejs","optimisation","performance","v8"],"created_at":"2025-10-10T03:18:31.645Z","updated_at":"2025-10-10T03:18:35.728Z","avatar_url":"https://github.com/ishtms.png","language":"JavaScript","readme":"# I stopped “deleting” and my hot paths calmed down\n\nI stumbled on this while chasing a latency spike in a cache layer. The usual JS folklore says: “don’t use delete in hot code.” I’d heard it before, but honestly? I didn’t buy it. So I hacked up a quick benchmark, ran it a few times, and the results were… not subtle.\n\nSince I already burned the cycles, here’s what I found. Maybe it saves you a few hours of head-scratching in production. (maybe?)\n\n# What I tested\n\nThree ways of “removing” stuff from a cache-shaped object:\n\n- `delete obj.prop` — property is truly gone.\n- `obj.prop = null` **or** `undefined` — tombstone: property is still there, just empty.\n- `Map.delete(key)` — absence is first-class.\n\nI also poked at arrays (`delete arr[i]` vs `splice`) because sparse arrays always manage to sneak in and cause trouble.\n\nThe script just builds a bunch of objects, mutates half of them, then hammers reads to see what the JIT does once things settle. There’s also a “churn mode” that clears/restores keys to mimic a real cache.\n\nRun it like this:\n\n    node benchmark.js\n\nTweak the knobs at the top if you want to model your workload.\n\n# My numbers (Node v22.4.1)\n\n    Node v22.4.1\n\n    Objects: 2,00,000, Touch: 50% (1,00,000)\n    Rounds: 5, Reads/round: 10, Churn mode: true\n    Map miss ratio: 50%\n\n    Scenario             Mutate avg (ms)   Read avg (ms)   Reads/sec       ΔRSS (MB)\n    --------------------------------------------------------------------------------\n    delete property      38.36             25.33           7,89,65,187     228.6\n    assign null          0.88              8.32            24,05,20,006    9.5\n    assign undefined     0.83              7.80            25,63,59,031    -1.1\n    Map.delete baseline  19.58             104.24          1,91,85,792     45.4\n\n    Array case (holes vs splice):\n\n    Scenario             Mutate avg (ms)   Read avg (ms)   Reads/sec\n    ----------------------------------------------------------------\n    delete arr[i]        2.40              4.40            45,46,48,784\n    splice (dense)       54.09             0.12            8,43,58,28,651\n\n# What stood out\n\n**Tombstones beat the hell out of** `delete`. Reads were \\~3× faster, mutations \\~40× faster in my runs.\n\n`null` **vs** `undefined` **doesn’t matter.** Both keep the object’s shape stable. Tiny differences are noise; don’t overfit.\n\n`delete` **was a hog.** Time and memory spiked because the engine had to reshuffle shapes and sometimes drop into dictionary mode.\n\n**Maps look “slow” only if you abuse them.** My benchmark forced 50% misses. With hot keys and low miss rates, `Map#get` is fine. Iteration over a `Map` doesn’t have that issue at all.\n\n**Arrays reminded me why I avoid holes.** `delete arr[i]` wrecks density and slows iteration. `splice` (or rebuilding once) keeps arrays packed and iteration fast.\n\n# But... why?\n\nWhen you reach for `delete`, you’re not just clearing a slot; you’re usually forcing the object to change its shape. In some cases the engine even drops into dictionary mode, which is a slower, more generic representation. The inline caches that were happily serving fast property reads throw up their hands, and suddenly your code path feels heavier.\n\nIf instead you tombstone the field, set it to undefined or null; the story is different. The slot is still there, the hidden class stays put, and the fast path through the inline cache keeps working. There’s a catch worth knowing: this trick only applies if that field already exists on the object. Slip a brand new undefined into an object that never had that key, and you’ll still trigger a shape change.\n\nArrays bring their own troubles. The moment you create a hole - say by deleting an element - the engine has to reclassify the array from a tightly packed representation into a holey one. From that point on, every iteration carries the tax of those gaps.\n\n# And...\n\n`delete` and `undefined` are not the same thing:\n\n    const x = { a: 1, b: undefined, c: null };\n\n    delete x.a;\n    console.log(\"a\" in x); // false\n    console.log(Object.keys(x)); // ['b', 'c']\n\n    console.log(JSON.stringify(x)); // {\"c\":null}\n\n- `delete` → property really gone\n- `= undefined` → property exists, enumerable, but `JSON.stringify` skips it\n- `= null` → property exists, serializes as `null`\n\nSo if presence vs absence matters (like for payloads or migrations), you either need `delete` off the hot path, or use a `Map`.\n\n# How I apply this now?\n\nI keep hot paths predictable by predeclaring the fields I know will churn and just flipping them to `undefined`, with a simple flag or counter to track whether they’re “empty.” When absence actually matters, I batch the `delete` work somewhere off the latency path, or just lean on a `Map` so presence is first-class.\n\nAnd for arrays, I’d rather pay the one-time cost of a splice or rebuild than deal with holes; keeping them dense makes everything else faster.\n\n# FAQ I got after sharing this in our slack channel\n\n**Why is Map slow here?**\n\nBecause I forced \\~50% misses. In real life, with hot keys, it’s fine. Iterating a `Map` doesn’t have “misses” at all.\n\n**Why did memory go negative for undefined?**\n\nGC did its thing. ΔRSS is not a precise meter.\n\n**Should I pick null or undefined?**\n\nDoesn’t matter for performance. Pick one for team sanity.\n\n**So we should never delete?**\n\nNo. Just don’t do it inside hot loops. Use it when absence is part of the contract.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fishtms%2Fv8-perf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fishtms%2Fv8-perf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fishtms%2Fv8-perf/lists"}