Клонирование объектов в Node.js: Быстрее, глубже, нежнее!

    Не так давно, прочитав статью idoroshenko «Почему eval — это не всегда плохо», я задумался, можно ли использовать подход с генерацией тела функции для клонирования объектов. Даже написал небольшую библиотеку для этого. Бенчмарки давали невероятные результаты, но применимость этого подхода ограничивалась лишь множественным клонированием одинаковых объектов.

    Поэтому и у меня возник вопрос: неужели в v8 нет другой возможности избежать расходов, связанных со множественным пересозданием скрытых классов? Ведь это составляет основные траты ресурсов, когда мы клонируем объекты. Как оказалось, такая возможность действительно есть: в самом v8 у объектов существует метод v8::Object::Clone. Этот метод клонирует объекты в широком смысле этого слова, то есть собственно объекты, а также массивы, даты, регулярные выражения, функции и т.д., при этом сохраняя все их свойства, в том числе нестандартные (например, именованные свойства массивов) и даже скрытые.

    Была только одна маленькая проблема. Этот метод использовался только в недрах node.js, и не был открыт наружу, для javascript'а.

    Недолго думая, я полез в документацию node.js по созданию расширений на c++ и написал пробную версию модуля, который просто раскрывает эту функцию.

    Получив ускорение для разных объектов примерно в 10-100 раз, я понял, что у этой техники есть большой потенциал, и начал его воплощать в жизнь в модуле node-v8-clone (npm), стараясь этот потенциал не растерять по пути, применяя смесь TDD и benchmark driven development. Это позволило следить за скоростью при разработке и исправлении проблем, а также следить за регрессиями при оптимизациях. Заодно, раз бенчмарки и тесты были готовы, я решил сравнить свой модуль с другими:


    Качество клонирования


    Одной из целей было добиться максимального качества клонирования. Пришлось поднапрячь фантазию, чтобы придумать достаточное количество неприятных для процесса клонирования ситуаций. В число таких входят, например, функции, замыкания, arguments, регулярные выражения с текущим состоянием и добавленными пользователем свойствами. Мой модуль с этими ситуациями справляется так:


    Как обстоят дела у конкурентов, можно оценить здесь.

    Мне кажется, получилось очень даже достойно.

    Скорость


    Вполне закономерно было бы предположить, что из-за поддержки высокого качества клонирования должна была упасть скорость. И скорость действительно упала, но не настолько, чтобы node-v8-clone потерял первенство в большинстве ситуаций.
    Например, вот результаты поверхностного клонирования объекта из 100 элементов {'_0': '_0', ..., '_99': '_99'} (в операциях в секунду):

    Глубокое клонирование 500 вложенных массивов, расположенных в 4 уровня вложенности, которые содержат 900 строк (тут также сравнивается оптимизированная версия клонирования из node-v8-clone, которая не проходит еще один тест, но существенно ускоряет работу глубоким клонированием массивов):

    А вот с небольшими массивами дела обстоят несколько хуже. Тут сказывается дороговизна обращения к c++-модулю, так что у простых алгоритмов, таких как for, появляется преимущество.


    Все результаты бенчмарков.

    Что дальше


    Собираюсь починить клонирование node.js-овских буферов. Сейчас они клонируются (a !== b), но указывают на одну и ту же область памяти и содержимое у них оказывается все еще связанным.
    Хотелось бы починить клонирование arguments. Когда у функции есть аргументы, объект arguments оказывается связанным с контекстом функции, и при клонировании они тоже оказываются связанными между собой.
    Хотелось бы придумать еще больше каверзных входящих данных, например файловые дескрипторы, модули, таймеры… Я не рассчитываю, что у меня получится их по-настоящему клонировать, но как минимум хотелось бы понимать, как будет вести себя с ними этот модуль.

    Буду рад любым отзывам, предложеним, багам и патчам. Ну и fork me on GitHub :)
    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 15

      +2
      О, отлично, только недавно озаботился, по алгоритму пришлось много клонирования делать. ваш модуль как раз в тему, попробую на праздниках
        0
        Почему бы Вам тогда не отправить pull requrest в github.com/joyent/node?
          0
          Да, не мешало бы. Есть идеи, как это встроить в текущий API?
          Но скорей всего всё равно завернут, там царит политика: то, что можно сделать модулем, нужно делать модулем.
            0
            Как вариант то через свойство
            Object.prototype.clone()
            или
            Object.extend(source)
            или
            Object.prototype.extend()

            Посмотрим :)
            P.S. хотя вполне могут и не добавить.
              +2
              Ну Object тем более не дадут трогать. Это уже будет отклонение от es5. Наверное стоит сначала обсудить этот вопрос с @bnoordhuis.
                0
                Ну тогда только если через Harmony Proxy — wiki.ecmascript.org/doku.php?id=harmony:proxies
                обернуть нативный Object чтобы на выходе была мембрана.
                  0
                  Мда… поговорил — патч не пройдет :(
                    0
                    Тем не менее, спасибо, что озаботились.
              0
              Может быть в модуль util?
                0
                удалил. копия верхнего коммента:) нужно было обновлять, вкладка сутки провисела
                  0
                  Вы, кстати, не хотите опубликовать github.com/zloirock/class в виде npm-модуля?
                    0
                    немного обкатаю, напишу доки, полностью покрою тестами и опубликую:)
                      0
                      Хотя бы просто package.json добавьте, чтобы можно было через «npm install git://» его поставить.
              +2
              Из названия топика можно понять, что клонирование объектов в Node.js — это нечто очень сексуальное)))
                0
                Кстати, для рисования графиков на скорую руку был слеплен модуль benchmark.js-plot.

                Only users with full accounts can post comments. Log in, please.