Метод кэширования приводит к неизменным объектам - PullRequest
7 голосов
/ 22 февраля 2012

Предположим, у меня есть простой интерфейс, представляющий комплексное число, экземпляры которого будут неизменными. Для краткости я опустил очевидные методы plus, minus, times и divide, которые просто создавали бы и возвращали новый неизменный экземпляр.

public interface Complex {

    double real();

    double imaginary();

    double absolute();

    double angle();

}

Теперь вопрос в том, как лучше всего реализовать это как неизменный класс? Самым простым и понятным подходом «я забочусь о производительности только тогда, когда это проблема» будет сохранение реальных и воображаемых частей в качестве конечных полей и вычисление абсолютного значения и угла при каждом вызове этих методов. Это делает класс небольшим и простым, но, очевидно, последние два метода каждый раз возвращают один и тот же результат.

public final class NonCachingComplex implements Complex {

    private final double real;
    private final double imaginary;

    public NonCachingComplex(double real, double imaginary) {
        this.real = real;
        this.imaginary = imaginary;
    }

    @Override public double real() {
        return real;
    }

    @Override public double imaginary() {
        return imaginary;
    }

    @Override public double absolute() {
        return Math.sqrt((real * real) + (imaginary * imaginary));
    }

    @Override public double angle() {
        return absolute() == 0 ? 0 : (Math.acos(real / absolute()) * Math.signum(imaginary));
    }
}

Так почему бы не сохранить абсолютное значение и угол в поле при создании? Ну, очевидно, объем памяти класса теперь немного больше, и подсчет результатов для каждого созданного экземпляра также может оказаться неэффективным, если эти два метода вызываются редко.

public final class EagerCachingComplex implements Complex {

    private final double real;
    private final double imaginary;

    private final double absolute;
    private final double angle;

    public EagerCachingComplex(double real, double imaginary) {
        this.real = real;
        this.imaginary = imaginary;
        this.absolute = Math.sqrt((real * real) + (imaginary * imaginary));
        this.angle = absolute == 0 ? 0 : (Math.acos(real / absolute()) * Math.signum(imaginary));
    }

    // real() and imaginary() stay the same...

    @Override public double absolute() {
        return absolute;
    }

    @Override public double angle() {
        return angle;
    }
}

Третья возможность, которую я придумал, заключается в том, чтобы вычислять абсолютное значение и угол лениво в первый раз, когда они требуются. Но, как вы можете видеть, это делает код немного загроможденным и подверженным ошибкам. Кроме того, я не уверен, что использование модификатора volatile действительно правильно в этом контексте.

public final class LazyCachingComplex implements Complex {

    private final double real;
    private final double imaginary;

    private volatile Double absolute;
    private volatile Double angle;

    public LazyCachingComplex(double real, double imaginary) {
        this.real = real;
        this.imaginary = imaginary;
    }

    // real() and imaginary() stay the same...

    @Override public double absolute() {
        if (absolute == null) {
            absolute = Math.sqrt((real * real) + (imaginary * imaginary));
        }
        return absolute;
    }

    @Override public double angle() {
        if (angle == null) {
            angle = absolute() == 0 ? 0 : (Math.acos(real / absolute()) * Math.signum(imaginary));
        }
        return angle;
    }

}

Итак, мой вопрос: какой из этих трех подходов является лучшим? Есть ли какой-то другой, еще лучший подход? Стоит ли вообще заботиться о производительности, придерживаться первого подхода и думать об оптимизации только тогда, когда производительность становится реальной проблемой?

Ответы [ 2 ]

9 голосов
/ 22 февраля 2012

Я бы пошел на NonCachingComplex почти каждый раз.

Причины:

  • Это самое простое - поэтому вы должны сначала написать это таким образом и только усложнить ситуацию, если докажете, что это необходимо с помощью бенчмаркинга. Избегайте преждевременной оптимизации и всего такого!
  • Формулы для вычисления absolute () и angle (), вероятно, не достаточно дороги, чтобы оправдать кэширование . Операции с плавающей запятой на современных процессорах выполняются очень быстро, часто даже быстрее, чем извлечение значения из памяти.
  • Самый низкий объем занимаемой памяти - это полезно не только для снижения общего потребления памяти вашего кода, но и для повышения производительности, потому что больше ваших данных поместится в высокоскоростных кэшах процессоров. Это может иметь большое значение для определенных размеров рабочего набора.

Из других, LazyCachingComplex особенно плох, поскольку он использует упакованные значения для абсолютного и углового значений (что подразумевает дополнительную разыменование памяти для доступа плюс две дополнительные накладные расходы на объект). Я думаю, маловероятно, что когда-либо увидит выигрыш в производительности от этого.

Обратите внимание, что если вы действительно заботитесь о производительности, то вы также не будете использовать интерфейс Complex - лучшая производительность - это непосредственное создание окончательного класса Complex и обращение к этому классу непосредственно в коде. Вызовы методов через интерфейс (немного) дороже, чем вызовы методов в конечном классе.

2 голосов
/ 22 февраля 2012

Я уверен, что это не тот ответ, который вы ищете, но я бы сказал, что это зависит;)

Лично я не думаю, что ленивый инициатор вообще плох. Но я бы сравнил ваши возможности с потребностями вашего приложения - действительно ли производительность вызывает беспокойство? Используете ли вы значения абсолютного и углового значения несколько раз для одного и того же объекта? Является ли вычисление этих полей действительно таким медленным?

Мое эмпирическое правило, как правило, заключается в том, чтобы сохранить код как можно более чистым (минимальным) и добавлять сложность только тогда, когда показано, что это требуется. Я бы придерживался вашей версии без кэширования до тех пор.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...