なんとなく「hotなコードでのdeleteは避けたほうがいい」って話は聞いたことあったけど、ぶっちゃけ半信半疑だったんだよね💭
でもちょっとしたレイテンシ上昇が気になって、自分でベンチマークを作って試してみたら…結果はなかなかハッキリしてたんだ✨
どんな実験したの?
主に3つの方法で「キャッシュっぽいオブジェクトからデータを消す」のを試したよ👀
delete obj.prop
→ プロパティ自体が本当に消えるobj.prop = null
またはundefined
→ プロパティは残ったまま「空っぽ」状態(これを「トゥームストーン」って呼んでる)Map.delete(key)
→ “欠如”がちゃんと扱われる
あと配列でも試した。
delete arr[i]
で穴あけるのと、splice
で詰めるの、どっちが速いかっての。
ベンチマークの結果(ざっくり)
方法 | 変更時間(ミリ秒) | 読み込み時間(ミリ秒) | 読み込み回数/秒 | メモリ変化(MB) |
---|---|---|---|---|
delete property | 38.36 | 25.33 | 約7.9億 | 228.6 |
assign null | 0.88 | 8.32 | 約24億 | 9.5 |
assign undefined | 0.83 | 7.80 | 約25.6億 | -1.1 |
Map.delete baseline | 19.58 | 104.24 | 約1.9億 | 45.4 |
配列の場合は、
方法 | 変更時間(ミリ秒) | 読み込み時間(ミリ秒) | 読み込み回数/秒 |
---|---|---|---|
delete arr[i] | 2.40 | 4.40 | 約45億 |
splice (dense) | 54.09 | 0.12 | 約843億 |
ここで注目してほしいこと✨
- トゥームストーン(
null
やundefined
)はdelete
より断然速い。 読み込みはだいたい3倍速くて、書き換えも40倍くらい速かったんだ🥺 null
とundefined
の違いは性能にはほぼ関係なし。好きな方を選べばOK👍delete
は重い。エンジンがオブジェクトの形を変えたり、辞書モードに落ちたりして余計に遅くなる。Map
は「使い方次第」。今回のテストは半分ミスヒットさせて遅く見えたけど、実際はキーがよく当たるなら問題なし。- 配列の穴(
delete arr[i]
)は密度を壊してしまって、そこからの繰り返しが遅くなる。splice
で詰めるほうがイテレーションは速いよ。
なんでこうなるの?
delete
は単に穴を開けるだけじゃなくて、そのオブジェクトの「形(hidden class)」を変えちゃうんだって。
そうすると、JavaScriptエンジンの速読み機能(インラインキャッシュ)が使えなくなって、急に重くなる感じ。
逆にnull
やundefined
は「その場所は空だけど形は変えない」からサクサク動く。
ただし、元々存在しないプロパティをundefined
にすると形が変わっちゃうから注意⚠️
配列も穴ができると、密に詰まってた状態から穴あき状態に変わって、それ以降の処理が遅くなるんだ。
delete
と undefined
はやっぱり違うよ
const x = { a: 1, b: undefined, c: null };
delete x.a;
console.log("a" in x); // false
console.log(Object.keys(x)); // ['b', 'c']
console.log(JSON.stringify(x)); // {"c":null}
delete
→ プロパティ自体なくなる= undefined
→ プロパティは残ってるけど、JSON.stringify
では無視される= null
→ プロパティ残る&null
としてシリアライズされる
だから、「本当に存在してない」ことが大事な場合は、deleteかMapを使うべき⚡
でも、パフォーマンス的にはホットパスでのdeleteはなるべく避けて、後回しにするかMapを使うのがいいと思う。
今はどうしてるか?
わたしはホットパス(頻繁にアクセスするところ)は「消える」可能性のあるプロパティを最初から用意しておいて、単純にundefined
に切り替えるだけにしてる。
その上で「消えてるかどうか」は別のフラグで管理したりしてね。
ほんとに「ないこと」が意味を持つ場面は、deleteを遅延処理に回したり、Mapに任せたり。
配列は穴ができるのが嫌だから、消すならsplice
や作り直しで密にしておくのが好きだなあ✨
なんかよく言われること、実際に自分で試すと「なるほど」って納得できるね〜😆
たまには自分で数字とってみるのもアリだなって思ったよ💡
コメント
ロバート
最後のポイントを強調すべきで、「ループ内でdeleteは避けて!」が一番大事だよ、新人は数字だけ見て誤解しがちだけど実際はほとんど無視できる差だからね。
クリス
いい投稿! 久しぶりにこの話題を考えたよ。 プロトタイプチェーンが絡むときはdeleteより墓標(tombstone)の方が便利だった記憶があるな。
キンバリー
うーん、大学で関数型コードを書いてた影響かもだけど、JSでdelete使ったことほぼないね。 オブジェクトは不変扱いするとパフォーマンスも保守性も良くなると思ってる。
リリー
V8などではオブジェクトのキーの追加・削除で内部クラスが変わるから、順序違いの同じキーでも別クラス扱いになり最適化が効かなくなるんだよね。 動的キーならMapを使うのがベター。
サラ
ここだけ意味わからないのは自分だけ?
グレース
モダンJSエンジンのことを知ってからはdeleteなんて忘れたよ。 普通のオブジェクトをマップ代わりに使うなって話。 構造が安定してる時だけオブジェクトを使うべきで、そうじゃないなら適切なデータ構造を使わないとJIT最適化が外れて1998年並みの遅さになるよ。
ハンナ
deleteが悪手とされるのには理由があるんだよ。 使うとコードの質が疑われるレベルだから、ほんとに避けるべき。
レオ
この考えには懐疑的だな。 deleteと代入は目的も挙動も違うし、状況に合った方法を使うのが正解。 パフォーマンスを極限まで求めないなら過剰最適化は避けたほうがいいよ。
エイダン
ありがとう👍ちゃんとベンチマークしてて最高!
ベン
これはお宝だね。 Mediumの記事より100倍わかりやすくて読みやすいよ。