151
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

Organization

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

この投稿では、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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
151
Help us understand the problem. What are the problem?