После реализации некоторых алгоритмов связывания записей я обнаружил необходимость оптимизировать код для повышения скорости.
Преждевременная оптимизация
Я строго следую принципу преждевременной оптимизации: сначала сделай все правильно, а потом быстро; таким образом, мой код склонен к ускорению даже на 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, либо потому, что ваши потребности в профилировании идут дальше, чем обеспечивает ваша платформа. Понятия преждевременной оптимизации и примитивных навязчивых идей должны быть приняты, чтобы отложить внедрение профилирующих и причудливых алгоритмов и структур данных.