Статьи

Создайте свой собственный Java секундомер

После реализации некоторых алгоритмов связывания записей я обнаружил необходимость оптимизировать код для повышения скорости.

Преждевременная оптимизация

 Я строго следую принципу преждевременной оптимизации: сначала сделай все правильно, а потом быстро; таким образом, мой код склонен к ускорению даже на 10000%, как только я обнаружу узкое место и введу кэш или специализированную структуру данных. Например, я всегда реализую объекты Java hashCode () как:

return 0;

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

Другое строгое правило, обеспечивающее оптимизацию в будущем, — это избегание примитивной одержимости : никогда не передавайте языковые структуры данных, такие как массивы или списки ArrayLists, а только оборачивайте их обычными старыми объектами Java. У Primitive Obsession есть доменные соображения — обращение к UserProfile (или другому термину домена) в среде OO обычно лучше, чем к ArrayList строк, потому что новый тип является аттрактором для новой логики в форме методов; однако перенос позволяет не только добавить имя, интерфейс или методы, но и изменить внутреннюю структуру данных по функциональным или нефункциональным причинам. Набор работает лучше, чем список, если вы хотите заимствовать код для удаления дубликатов, но также для O (1) содержит () выполнения.

Профилирование

Чтобы выяснить, где заменить структуры данных или кешировать результаты или переопределить алгоритм, вам нужно профилировать код и выяснить узкое место вашего типичного варианта использования. Это динамическая операция — она ​​состоит из запуска кода и отслеживания того, на что тратится время — во время поиска этого объекта или при доступе к базе данных?

Существуют автоматизированные инструменты для профилирования, но в зависимости от того, насколько они сложны в установке и использовании, вы можете развернуть свои собственные. Другой вариант использования, в котором я бы предпочел индивидуальное решение, — это то, что профилирование является важным аспектом вашего приложения — вы хотите измерять время на производстве или сообщать о них по какому-либо сетевому каналу. Чем сложнее ваши потребности, тем лучше индивидуальное решение сможет соответствовать внешнему инструменту.

В случае Eclipse TPTP я быстро прекратил скачивать и устанавливать его; это не всегда так для некоторых инструментов Eclipse, таких как покрытие кода, которое требует двух щелчков мыши для установки и запуска в ваших проектах.
Затем я создал свои собственные объекты StopWatch, которые можно вводить или получать к ним доступ везде, где необходимо измерить время выполнения. Я обычно удаляю объект StopWatch после достижения приемлемого времени выполнения, так же как я делал бы с абстракцией, которая изжила себя.

Секундомер

Что мы хотим от секундомера? Ну, это должно быть в состоянии:

  • измерять различные интервалы, даже если они перекрываются друг с другом (например, один содержится в большем).
  • Он должен принимать сообщения начала и остановки .
  • Он должен принять сообщение паузы : в случае, если мы находимся в цикле, мы хотим суммировать все времена относительно определенной операции, с последовательностью пар начала / паузы.
  • Он должен сообщать об общем прошедшем времени для операции, когда мы отправляем ему сообщение о мерах .

Эти требования создают следующий интерфейс:

public interface StopWatch {
    StopWatch start(String label);

    StopWatch pause(String label);

    StopWatch measure(String label);

    StopWatch reset(String label);
}

Простая реализация может просто сообщать в стандартный вывод, но в принципе она может даже отправлять вам сообщения электронной почты:

import java.util.HashMap;

public class LoggingStopWatch implements StopWatch {

    private HashMap<String, Long> startingTimes;
    private HashMap<String, Long> measures;
    public static StopWatch lastCreated = new LoggingStopWatch();

    public LoggingStopWatch() {
        this.startingTimes = new HashMap<String,Long>();
        this.measures = new HashMap<String,Long>();
        lastCreated = this;
    }
    @Override
    public StopWatch start(String label) {
        startingTimes.put(label, System.currentTimeMillis());
        if (measures.get(label) == null) {
            measures.put(label, 0L);
        }
        return this;
    }

    @Override
    public StopWatch pause(String label) {
        Long start = startingTimes.get(label);
        if (start == null) {
            throw new RuntimeException("Calling pause(\"" + label + "\" without calling start() at least once.");
        }
        long newMeasure = System.currentTimeMillis() - startingTimes.get(label);
        measures.put(label, measures.get(label) + newMeasure);
        startingTimes.remove(label);
        return this;
    }

    @Override
    public StopWatch measure(String label) {
        if (startingTimes.containsKey(label)) {
            pause(label);
        }
        System.out.println("STOPWATCH " + label + ": " + measures.get(label));
        return this;
    }

    @Override
    public StopWatch reset(String label) {
        measures.remove(label);
        return this;
    }
}

Я добавил статическое поле, чтобы сделать последний созданный секундомер доступным даже там, где вы его не вводили. Это не долгосрочное решение, но приемлемо при выполнении ручных тестов и поиске этого проблемного метода, который занимает 90% общего времени выполнения (возможно, некоторый hashCode () :). Фактически, следующий абзац требует введения StopWatch для работы.

У меня также есть другая реализация, SilentStopWatch, которая создается как Test Double для секундомера и создается всякий раз, когда (как в тестах) нет необходимости в профилировании, и мы вообще не хотим касаться стандартного вывода.

public final class SilentStopWatch implements StopWatch {
    @Override
    public StopWatch start(String label) { return this; }

    @Override
    public StopWatch pause(String label) { return this; }

    @Override
    public StopWatch measure(String label) { return this; }

    @Override
    public StopWatch reset(String label) { return this; }
}

Объекты, поддерживающие как профилирующие, так и непрофилирующие варианты использования, имеют несколько конструкторов, по умолчанию SilentStopWatch, где один не предоставлен.

    private int[] thresholds;
    private StopWatch stopWatch;

        public AgglomerativeHierarchicalClustering(int[] thresholds) {
                this.thresholds = thresholds;
                this.stopWatch = new SilentStopWatch();
        }

        public AgglomerativeHierarchicalClustering(int[] thresholds, StopWatch stopWatch) {
                this.thresholds = thresholds;
                this.stopWatch = stopWatch;
        }

Вывод

Индивидуальное решение для профилирования может соответствовать вашему контексту — либо потому, что приложение достаточно простое, вы можете внедрять объекты StopWatch, либо потому, что ваши потребности в профилировании идут дальше, чем обеспечивает ваша платформа. Понятия преждевременной оптимизации и примитивных навязчивых идей должны быть приняты, чтобы отложить внедрение профилирующих и причудливых алгоритмов и структур данных.