structuredClone 遅かった
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 0
◆ structuredClone は高速にクローンできると期待してたけど遅かった
◆ データが小さめだと JSON 化してパースするよりも遅い
◆ JSON 化できるデータのみが対象なら JavaScript で再帰的にオブジェクトを見てコピーしたほうが速い
◆ データが小さめだと JSON 化してパースするよりも遅い
◆ JSON 化できるデータのみが対象なら JavaScript で再帰的にオブジェクトを見てコピーしたほうが速い
Node.js で structuredClone が使えるようになると以前書きましたがブラウザでも Chrome 98 から使えるようになってました
https://chromestatus.com/feature/5630001077551104
クローンのための関数なので JSON 化してパースする
よりはるかに速いよねと期待してたのですが 使ってみると思った以上に遅かったです
こんな感じのコードで試しました
clone0: JSON 化してパース
clone1: structuredClone
clone2: JavaScript で再帰的にオブジェクトを見てコピー
となってます
それぞれクローン時にコピーされる対象が多少は違いますが基本的な JSON 化できる部分に限れば同じになるはずです
JavaScript の方法では プリミティブ値はそのまま代入でコピーし Symbol も対象になります
関数は参照をコピーするので実体は同じです
配列やオブジェクトの場合は再帰的に上記の処理を行い getter/setter や prototype は無視します
対象にするデータは小さめだけどちょっとは複雑な構造で
というデータにしています
このデータに対して 1 万回のクローンをそれぞれの方法でやってみて比較します
単純に for 文でループだと最適化都合で順番の影響があるときもあるので 直接何度か繰り返して書いてます
結果はこうなりました
なんと structuredClone は JSON 化してパースよりも遅いです
最速は JavaScript で再帰してコピーする方法です
それぞれの方法で最初だけ遅いとか structuredClone の方法で ループの最初の 1, 5, 9 回目が少し遅いとかは Chrome の最適化都合でよくあることです
データによるところがあると思いますし 大きいデータになると結果が変わるということはありえそうです
実際のデータを想定して Github API のデータを使ってみます
リポジトリ検索の結果です
https://api.github.com/search/repositories?q=js
私が取得したタイミングのデータでは 175KB ありました
データ量が多いのでループ回数は 100 回にして 再度計測してみました
JSON 化してパースが最も遅くなりました
最速が JavaScript で処理というのは一緒です
structuredClone 関数が追加されれば高速なクローンが手軽に扱えると期待していましたが 小さいデータでは JSON 化してパースするよりも遅いという結果でした
グローバル関数なので気軽にクローンに使えますが 高速なものではないと覚えておいたほうがいいかもです
JSON 化できるものという条件で書いたので JavaScript で再帰してコピーする方法では循環参照を考慮してないです
入れると無限に再起してコールスタックの上限を超えたとエラーになります
そこもチェックするようにするともう少し JavaScript で再帰してコピーする方法の速度が落ちますが Github API のデータの場合で 2,3ms 程度だったので結果としてはほぼ変わりなしでした
https://chromestatus.com/feature/5630001077551104
クローンのための関数なので JSON 化してパースする
JSON.parse(JSON.stringify(obj))
よりはるかに速いよねと期待してたのですが 使ってみると思った以上に遅かったです
こんな感じのコードで試しました
<!DOCTYPE html>
<script>
const clone0 = x => JSON.parse(JSON.stringify(x))
const clone1 = x => structuredClone(x)
const clone2 = x => {
if (typeof x === "object") {
if (x === null) return x
if (Array.isArray(x)) return x.map(clone2)
const obj = {}
for (const [k, v] of Object.entries(x)) {
obj[k] = clone2(v)
}
return obj
} else {
return x
}
}
const data = {
a: 1,
b: null,
c: true,
d: "a",
e: [1],
f: {
g: 1,
h: [1, { i: 3, j: "a", k: [[[4]]] }],
l: {},
},
m: { n: { o: { p: 0 } } },
}
const measure = (f) => {
const before = performance.now()
for (let i = 0; i < 10000; i++) {
f(data)
}
const after = performance.now()
return +(after - before).toFixed(3)
}
const results = [[], [], []]
for (let i = 0; i < 3; i++) {
results[0].push(measure(clone0))
results[1].push(measure(clone1))
results[2].push(measure(clone2))
results[0].push(measure(clone0))
results[1].push(measure(clone1))
results[2].push(measure(clone2))
results[0].push(measure(clone0))
results[1].push(measure(clone1))
results[2].push(measure(clone2))
results[0].push(measure(clone0))
results[1].push(measure(clone1))
results[2].push(measure(clone2))
}
console.log(results)
</script>
clone0: JSON 化してパース
clone1: structuredClone
clone2: JavaScript で再帰的にオブジェクトを見てコピー
となってます
それぞれクローン時にコピーされる対象が多少は違いますが基本的な JSON 化できる部分に限れば同じになるはずです
JavaScript の方法では プリミティブ値はそのまま代入でコピーし Symbol も対象になります
関数は参照をコピーするので実体は同じです
配列やオブジェクトの場合は再帰的に上記の処理を行い getter/setter や prototype は無視します
対象にするデータは小さめだけどちょっとは複雑な構造で
const data = {
a: 1,
b: null,
c: true,
d: "a",
e: [1],
f: {
g: 1,
h: [1, { i: 3, j: "a", k: [[[4]]] }],
l: {},
},
m: { n: { o: { p: 0 } } },
}
というデータにしています
このデータに対して 1 万回のクローンをそれぞれの方法でやってみて比較します
単純に for 文でループだと最適化都合で順番の影響があるときもあるので 直接何度か繰り返して書いてます
結果はこうなりました
[
[
68.2,
46.8,
40.1,
40.1,
41.6,
42.4,
41.4,
41.7,
44.2,
42.2,
43.1,
41.9
],
[
128.7,
78,
73.6,
75.6,
79.3,
74.7,
75.7,
74.9,
79.5,
75.7,
76.7,
77.7
],
[
35.4,
22.6,
22.9,
22.2,
23.5,
22,
21.1,
23.1,
22.9,
20.9,
23.1,
21.8
]
]
なんと structuredClone は JSON 化してパースよりも遅いです
最速は JavaScript で再帰してコピーする方法です
それぞれの方法で最初だけ遅いとか structuredClone の方法で ループの最初の 1, 5, 9 回目が少し遅いとかは Chrome の最適化都合でよくあることです
データによるところがあると思いますし 大きいデータになると結果が変わるということはありえそうです
実際のデータを想定して Github API のデータを使ってみます
リポジトリ検索の結果です
https://api.github.com/search/repositories?q=js
私が取得したタイミングのデータでは 175KB ありました
データ量が多いのでループ回数は 100 回にして 再度計測してみました
[
[
106.7,
105.1,
102.4,
101.2,
104.7,
103.9,
103.5,
103,
100.9,
101.9,
104.3,
101.6
],
[
64.1,
64.3,
78.5,
63.1,
63.6,
65.3,
79.1,
63.5,
64,
63.7,
64.1,
77.1
],
[
34.5,
30.4,
30.5,
29.9,
30.3,
29.8,
32.3,
30.1,
30,
31.1,
29.9,
32.5
]
]
JSON 化してパースが最も遅くなりました
最速が JavaScript で処理というのは一緒です
structuredClone 関数が追加されれば高速なクローンが手軽に扱えると期待していましたが 小さいデータでは JSON 化してパースするよりも遅いという結果でした
グローバル関数なので気軽にクローンに使えますが 高速なものではないと覚えておいたほうがいいかもです
JSON 化できるものという条件で書いたので JavaScript で再帰してコピーする方法では循環参照を考慮してないです
入れると無限に再起してコールスタックの上限を超えたとエラーになります
そこもチェックするようにするともう少し JavaScript で再帰してコピーする方法の速度が落ちますが Github API のデータの場合で 2,3ms 程度だったので結果としてはほぼ変わりなしでした