Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Отимизируем, оптимизируем и еще раз оптимизируем

Anna | 5.06.2014 | нет комментариев

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

Date

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

    public boolean isValid(Date start, Date end) {
        Date now = new Date();
        return start.before(now) && end.after(now); 
    }

Казалось бы — абсолютно явственное и верное решение. В тезисе, да, за исключением 2-х моментов:

  • Применять Date сегодня в java — теснее, вероятно, моветон, рассматривая тот факт, что примерно все способы в нем теснее Deprecated.
  • Нет смысла создавать новейший объект даты, если абсолютно дозволено обойтись примитивом long:

 

    public boolean isValid(Date start, Date end) {
        long now = System.currentTimeMillis();
        return start.getTime() < now && now < end.getTIme(); 
    }
SimpleDateFormat

Дюже Зачастую в веб планах появляется задача перевести строку в дату либо напротив дату в строку. Задача достаточно нормальная и Почаще каждого выглядит так:

    return new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z").parse(dateString);

Это верное и стремительное решение, но если серверу доводится парсить строку на всякий пользовательский реквест в всяком из сотен потоков — это может ощутимо бить по продуктивности сервера в виду достаточно массивного конструктора SimpleDateFormat, да и помимо самого форматера создается уйма других объектов в том числе и не легкий Calendar (размер которого > 400 байт).

Обстановку дозволено было бы легко решить, сделав SimpleDateFormat статическим полем, но он не является потокобезопасным. И в конкурентной среде легко дозволено словить NumberFormatException.

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

Но решения есть и их как минимум 2:

  • Ветхий, добродушный ThreadLocal — cоздаем SimpleDateFormat для всякого потока 1 раз и переиспользуем для всякого дальнейшего запроса. Данный подход поможет ускорить парсинг даты в 2-4 раза за счет избежания создания объектов SimpleDateFormat на всякий запрос.
  • Joda и ее потокобезопасный аналог SimpleDateFormat — DateTimeFormat. Хоть йода в целом и неторопливей дефолтного Java Date API в парсинге дат они идут наравне. Несколько тестов дозволено взглянуть здесь.

Random

В моих планах частенько появляется задача воротить пользователю случайную сущность. Обыкновенно сходственного рода код выглядит так:

    return items.get(new Random().nextInt(items.size()));

Отменно, легко, стремительно. Но, если обращений к способу много — это обозначает непрерывное создания новых объектов Random. Чего легко дозволено избежать:

    private static final Random rand = new Random();
    ...
    return items.get(rand.nextInt(items.size()));

Казалось бы, вот оно — безукоризненное решение, но и здесь не все так легко. Не смотря на то, что Random является потокобезопасным, в многопоточной среде он может трудиться медлительно. Но Sun Oracle об этом теснее позаботились:

     return items.get(ThreadLocalRandom.current().nextInt(items.size()));

Как заявлено в документации — это и есть самое оптимальное решение для нашей задачи. ThreadLocalRandomзначительно результативней Random в многопоточной среде. К сожалению, данный класс доступен только начиная с 7-й версии (позже багофикса, здравствуй TheShade). По сути, это решение такое же, как и с SimpleDateFormat, только со своим персональным классом.

Not null

Многие разработчики чураясь null значений, пишут что-то сходственное:

public Item someMethod() {
    Item item = new Item();
    //some logic
    if (something) {
        fillItem(item);
    }
    return item;
}

В выводе, даже если something никогда не станет true, большое число объектов все равно будет сделано (при условии что способ вызывается Зачастую).

Regexp

Если Вы пишете веб приложение, примерно наверно Вы используете регулярные выражения. Классический код:

public Item isValid(String ip) {
    Pattern pattern = Pattern.compile("xxx");
    Matcher matcher = pattern.matcher(ip);
    return matcher.matches();
}

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

private final Pattern pattern = Pattern.compile("xxx");
public Item isValid(String ip) {
    Matcher matcher = pattern.matcher(ip);
    return matcher.matches();
}

Безупречно было бы так же перенести создание матчера вне способа, но к сожалению он не является потокобезопасным и доводится его непрерывно создавать. Что касается однопоточной среды, то здесь решение есть:

private final Pattern pattern = Pattern.compile("xxx");
private final Matcher matcher = pattern.matcher("");
public Item isValid(String ip) {
    matcher.reset(ip);
    return matcher.matches();
}

Что безупречно подходит для… верно, ThreadLocal’a.

Truncate Date

Еще одна достаточно частая задача — урезание даты по часам, дням, неделям. Существует большое уйма методов это сделать, начиная от апачевских DateUtils, до собственных велосипедов:

    public static Date truncateToHours(Date date) {
        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
        calendar.setTime(date);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        return calendar.getTime();
    }

Скажем, вовсе незадолго, анализируя код map фазы хадупа, наткнулся на такие 2 cтроки кода, которые потребляли 60% CPU:

key.setDeliveredDateContent(truncateToHours(byPeriodKey.getContentTimestamp()));
key.setDeliveredDateAd(truncateToHours(byPeriodKey.getAdTimestamp()));

Для меня самого это стало огромный неожиданностью, но профайлер не врет. К счастью способ map оказался потокобезопасным, и создание объекта календаря удалось перенести вне способа truncateToHours(). Что увеличило скорость работы map способа в 2 раза.

HashCodeBuilder

Не знаю отчего, но некоторые разработчики для генерации способа hashcode() и equals() применяют апачевские вспомогательные классы. Вот скажем:

    @Override
    public boolean equals(Object obj) {
        EqualsBuilder equalsBuilder = new EqualsBuilder();
        equalsBuilder.append(id, otherKey.getId());
        ...
    }

    @Override
    public int hashCode() {
        HashCodeBuilder hashCodeBuilder = new HashCodeBuilder();
        hashCodeBuilder.append(id);
        ...
    }

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

Завершение

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

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB