Я наткнулся на статью Нареша Джоши о копировании и клонировании и был удивлён ситуацией с производительностью. У клонирования есть проблемы с финальными полями. А учитывая тот факт, что интерфейс Cloneable не предоставляет метод clone, то для вызова clone вам необходимо будет знать конкретный тип класса.


Вы можете написать такой код:


    ((Cloneable)o).clone(); // не работает

Если интерфейс Cloneable сломан, то у механизма клонирования могут быть некоторые преимущества. При копировании памяти он может оказаться эффективнее, чем копирование поля за полем. Это подчёркивает Джош Блох, автор Effective Java:


Даг Ли пошёл ещё дальше. Он сказал мне, что теперь клонирует только при копировании массивов. Вам следует использовать клонирование копирования массивов, потому что в целом это самый быстрый способ. Но у Дага типы больше не реализуют Cloneable. Он с ним завязал. И я не считаю это необоснованным.

Но это было в 2002-м, разве ситуация не изменилась? Со времён Java 6 у нас есть Arrays.copyOf, что насчёт него? Какова производительность копирования объекта?
Есть только один способ выяснить: прогнать бенчмарки.


TL;DR


  • Клонирование работает быстрее при копировании массива, это заметно на маленьких массивах.
  • Arrays.copyOf и clone примерно одинаково работают
  • Клонирование работает медленнее для маленьких объектов, меньше восьми полей, но в любом случае быстрее.
  • При клонировании не работает escape analysis, и потенциально оно может помешать применению других оптимизаций.

image


Массивы


[UPD]Andrei Paguin указал в комментариях что с бенчмарком есть проблема.


замените “size” на “original.length” в бенчмарке Arrays.copyOf().

И тут я понял, что да… это объясняет почему jit может понять что мы копируем ровно такую же длину. Поэтому я изменил выводы и статью


Давайте быстро рассмотрим clone и Arrays.copyOf применительно к массивам.


Бенчмарк int array выглядит так:


    @Benchmark
    @CompilerControl(CompilerControl.Mode.DONT_INLINE)
    public int[] testCopy() {
        return Arrays.copyOf(original, original.length); // здесь было size
    }

    @Benchmark
    @CompilerControl(CompilerControl.Mode.DONT_INLINE)
    public int[] testClone() {
        return original.clone();
    }

Мы создали массив из случайных числовых значений, затем выполнили clone или Arrays.copyOf. Обратите внимание: мы вернули результат копирования, чтобы гарантировать, что код будет выполнен. В главе про escape analysis мы увидим, как невозвращение массива может радикально повлиять на бенчмарк.


Наряду с int array есть версия для byte array, long array и Object array. Я использую флаг DONT_INLINE, чтобы при необходимости легче было анализировать сгенерированный asm.


mvn clean install
java -jar target/benchmark.jar -bm avgt -tu ns -rf csv

  • здесь будут обновленные результаты —

    Объекты


    Теперь разберёмся с клонированием объектов с 4, 8, 16 и 32 полями. Бенчмарки ищут объекты с 4 полями:


Поделиться с друзьями
-->

Комментарии (5)


  1. izzholtik
    27.07.2017 16:33
    +3

    Простите, но это нечитаемо. Оригинальная статья — та ещё каша, и бездумный и почти дословный перевод явно не пошёл ей на пользу.


  1. apangin
    27.07.2017 16:53
    +1

    Как видите, clone по сравнению с Arrays.copyOf обходится примерно на 10 % дешевле при маленьких массивах
    Бенчмарк неправильный, соответственно, и выводы ложные.
    Подсказка: в вызове Arrays.copyOf надо заменить size на original.length.


    1. apangin
      27.07.2017 16:57
      +2

      Кроме того, на больших массивах возникают ещё всякие неочевидные артефакты бенчмаркинга вроде такого.


    1. apangin
      27.07.2017 20:40
      +2

      Указал автору оригинальной статьи на ошибку, которую он, к чести, оперативно исправил. Выводы в статье поменялись. Неплохо бы теперь и перевод обновить, чтоб соответствовал новой редакции.


  1. DigitalSmile
    27.07.2017 17:28
    +3

    Хоть бы написали версию java, я уж не говорю о качестве бенчмарков и выводов на их основе…