LoginSignup
154

More than 3 years have passed since last update.

JavaScriptのディープコピー速さ比較 〜7つの手法/ライブラリを比べてみた〜

Last updated at Posted at 2020-10-08

この投稿では、JavaScript(Node.js)でディープコピーするにあたって使えるコードスニペットやライブラリの処理速度を比較した結果をお見せします。

比較対象

  1. JSON.stringify/JSON.parse
  2. Nodeビルトインモジュールv8のserialize/deserialize
  3. lodashのcloneDeep
    npm GitHub Repo stars
  4. deepcopy - deep copy data
    npm GitHub Repo stars
  5. clone - offers foolproof deep cloning of objects, arrays, numbers, strings, maps, sets, promises, etc. in JavaScript.
    npm GitHub Repo stars
  6. clone-deep - Recursively (deep) clone JavaScript native types, like Object, Array, RegExp, Date as well as primitives.
    npm GitHub Repo stars
  7. rfdc - Really Fast Deep Clone
    npm GitHub Repo stars

比較結果

比較結果の棒グラフです。棒が長いほうが速いです。

JavaScriptディープコピーベンチマーク.002.png

JavaScriptディープコピーベンチマーク.003.png

JavaScriptディープコピーベンチマーク.004.png

検証に用いたコード

ベンチマークを出すのに用いたコードを示しておきます。GitHubにも置いてあるので、検証してみたい方はそちらをお使いください。

「32プロパティ持つオブジェクト」の検証コード


const Benchmark = require('benchmark')
const report = require('beautify-benchmark')
const v8 = require('v8')
const _ = require('lodash')
const deepcopy = require('deepcopy')
const clone = require('clone')
const cloneDeep = require('clone-deep')
const rfdc = require('rfdc')()

const value = {}
for (let i = 0; i < 32; i++) {
  value[`prop${i}`] = 0
}
console.log(value)

const json = () => JSON.parse(JSON.stringify(value))
const v8_ = () => v8.deserialize(v8.serialize(value))
const lodash = () => _.cloneDeep(value)
const deepcopy_ = () => deepcopy(value)
const clone_ = () => clone(value)
const cloneDeep_ = () => cloneDeep(value)
const rfdc_ = () => rfdc(value)

const suite = new Benchmark.Suite()
suite
  .add(`json`, json)
  .add(`v8`, v8_)
  .add(`lodash`, lodash)
  .add(`deepcopy`, deepcopy_)
  .add(`clone`, clone_)
  .add('clone-deep', cloneDeep_)
  .add('rfdc', rfdc_)
  .on('cycle', event => report.add(event.target))
  .on('complete', () => report.log())
  .run()

「32要素持つArray」の検証コード

const Benchmark = require('benchmark')
const report = require('beautify-benchmark')
const v8 = require('v8')
const _ = require('lodash')
const deepcopy = require('deepcopy')
const clone = require('clone')
const cloneDeep = require('clone-deep')
const rfdc = require('rfdc')()

const value = new Array(32).fill(0)
console.log(value)

const json = () => JSON.parse(JSON.stringify(value))
const v8_ = () => v8.deserialize(v8.serialize(value))
const lodash = () => _.cloneDeep(value)
const deepcopy_ = () => deepcopy(value)
const clone_ = () => clone(value)
const cloneDeep_ = () => cloneDeep(value)
const rfdc_ = () => rfdc(value)

const suite = new Benchmark.Suite()
suite
  .add(`json`, json)
  .add(`v8`, v8_)
  .add(`lodash`, lodash)
  .add(`deepcopy`, deepcopy_)
  .add(`clone`, clone_)
  .add('clone-deep', cloneDeep_)
  .add('rfdc', rfdc_)
  .on('cycle', event => report.add(event.target))
  .on('complete', () => report.log())
  .run()

「32段ネストしたオブジェクト」の検証コード

const Benchmark = require('benchmark')
const report = require('beautify-benchmark')
const v8 = require('v8')
const _ = require('lodash')
const deepcopy = require('deepcopy')
const clone = require('clone')
const cloneDeep = require('clone-deep')
const rfdc = require('rfdc')()

let node = {}
const value = node
for (let i = 0; i < 32; i++) {
  node.child = {}
  node = node.child
}
console.log(value)

const json = () => JSON.parse(JSON.stringify(value))
const v8_ = () => v8.deserialize(v8.serialize(value))
const lodash = () => _.cloneDeep(value)
const deepcopy_ = () => deepcopy(value)
const clone_ = () => clone(value)
const cloneDeep_ = () => cloneDeep(value)
const rfdc_ = () => rfdc(value)

const suite = new Benchmark.Suite()
suite
  .add(`json`, json)
  .add(`v8`, v8_)
  .add(`lodash`, lodash)
  .add(`deepcopy`, deepcopy_)
  .add(`clone`, clone_)
  .add('clone-deep', cloneDeep_)
  .add('rfdc', rfdc_)
  .on('cycle', event => report.add(event.target))
  .on('complete', () => report.log())
  .run()

注意点

各コピー方法によって、MapやDateのようなビルトインオブジェクトそのままコピーできたり、関数までコピーできたり、再帰的なオブジェクトまでコピーできたり、と仕様がまちまちなのでご注意ください。

例えば、JSON.stringify+JSON.parseは再帰的なオブジェクトに使おうとするとエラーになることが知られています。

構造化複製アルゴリズム(structured clone algorithm)に準拠しているものとしては、v8モジュールのserialize/deserializeです。lodashのcloneDeepは、ゆるやかに準拠しているとしています。これは、MapDateErrorなどもコピーできますが、関数やシンボルは無理です。再帰的なオブジェクトにも対応しています。

deepcopyはかなり頑張っていて、関数やシンボルまでコピーできるそうです。

所感

  • JSONはものすごく遅いわけではないので、オブジェクトの性質によっては使い所は大いにありそう。
  • 構造化複製アルゴリズムを使いたいなら、v8よりlodashが良さそう。
  • rfdcめっちゃ速い。

最後までお読みくださりありがとうございました。Twitterでは、Qiitaに書かない技術ネタなどもツイートしているので、よかったらフォローお願いします:relieved:Twitter@suin

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
154