Эта волна оптимизации производительности слишком взрывоопасна!

Java JVM
Эта волна оптимизации производительности слишком взрывоопасна!

Здравствуйте, меня зовут почему.

Нет, это не я. Я все еще моложе и красивее его.

Это главный герой сегодняшней статьи.

Его зовут Бретт Вулдридж, возможно, вы его не знаете.

Но я покажу вам его скриншот с github, вы должны знать проект с открытым исходным кодом, который он написал:

видеть это?

Он отец знаменитого HikariCP.

И если вы посмотрите на его профиль на github, то очень приятно написать:

Father of an angel who fell to Earth and somehow into my life.

Отец ангела, упавшего на землю, она вошла в мою жизнь неосознанно.

На фотографии должен быть его ребенок, а рядом с ним он показал старую отцовскую улыбку.

Картинка в начале статьи - это то, что я нашел из этого отчета:

блог Просто о go.org/2017/02/21/…

Первый заданный вопрос таков:

Вы создали один из самых популярных пулов соединений на Java, HikariCP. Так что же делает вашу библиотеку такой популярной?

В следующем разделе я буду использовать вид от первого лица, чтобы рассказать вам, как этот старик ответил на этот вопрос.

Зачем писать HikariCP?

Что, почему вы просите меня написать HikariCP?

О боже, это не потому, что я не нашла хорошего парня.

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

Не говорите мне, выглядит неплохо.

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

Интересно, эта штука дура?

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

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

Независимо от того, сколько кода у вас есть, вы можете прочитать его с терпением.

Магия — это логика кода.

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

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

Наконец, на расстоянии 108 000 миль я увидел место, где разблокировался замок.

Я, наверное, был таким:

Потому что я знаю, что у меня нет возможности узнать, где в коде скрывается тупик.

Даже если я решу текущую проблему в соответствии с тем, как написан проект, рано или поздно я столкнусь с другими проблемами.

Поэтому я рискнула и решила...

Найдите еще один в сети.

На этот раз я хорошо усвоил и, найдя новый пул соединений, первым делом прочитал его код.

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

Семантика только что обнаруженной блокировки пула соединений действительно стала чище, но объем кода по-прежнему более чем в два раза превышает ожидаемый.

Кроме того, все рассмотренные мной пулы соединений нарушают контракт JDBC по-разному.

Например, одна из самых распространенных проблем, которые я нахожу, заключается в следующем.

Когда ссылка израсходована и возвращена в пул. Некоторые пулы не очищают сообщения в этой ссылке, такие как автоматическая фиксация, уровень изоляции транзакций и т. д., поэтому, когда следующий потребитель снова получит эту ссылку, это будет «грязная» ссылка.

Я подумал:

Действительно? Это текущее состояние пула соединений в экосистеме Java? Нет, я собираюсь сделать это сам. Поэтому из нужды и разочарования я создал HikariCP.

Вернемся к исходному вопросу.

Как упоминалось выше, до того, как я написал HikariCP, на самом деле существовало много зрелых пулов соединений, так как же HikariCP стал популярным?

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

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

Где-то в 2015 году команда инженеров Wix написала блог об использовании HikariCP.

Эту волну я просто катапультировал и взлетел. HikariCP также попал в поле зрения каждого.

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

В моем случае я планирую написать больше об этих аспектах HikariCP.

Почему исполнение такое классное?

Как упоминалось ранее, преимуществом HikariCP является его высокая производительность.

Так почему же его производительность такая потрясающая?

На самом деле ответ написан на домашней странице HikariCP на github:

GitHub.com/Бретт Шерсть Доктор…

входhow we do it hereПеред этим кратко расскажем о названии этого проекта.

Вы можете увидеть большой китайский иероглиф: свет.

Что касается источника этого имени, то оно действительно упоминалось в вышеупомянутом отчете:

HikariCP, что в переводе с английского означает «свет», в контексте HikariCP — это каламбур. В этом проекте под «легким» понимается не только высокая скорость, но и небольшой объем кода.

Хикари произносится как Хи-ка-ли.

Помните об этом.

Я помню интервью, в котором собеседник упомянул об этом пуле соединений, но не знал, как его прочитать.

Он сказал: H открыл пул соединений в конце CP, я забыл, как его читать.

Но я сразу среагировал.

Я сказал: «Ну, я знаю, о каком пуле соединений вы говорите», — продолжаете вы.

На самом деле, я не знал, как читать это в то время, поэтому я был смущен.

Что ж, теперь давайте посмотрим, почему производительность такая классная.

Ответ, автор написал его в github:

GitHub.com/Бретт Шерсть Доктор…

Во-первых, очень интересно название этой статьи:

wath mean is Down the Rabbit Hole?

Дословный перевод — «в кроличью нору».

Я не думал, что это так просто, поэтому я пошел проверить это:

Ой,down the rabbit holeОказалось, что это метафора путешествия в неизвестность. Из известной книги "Алиса в стране чудес".

Обычно мы используемdown the rabbit holeИспользуется для описания попадания во все более странную, сбивающую с толку или неожиданную ситуацию, когда одна вещь побуждает к другой, одна за другой, и, таким образом, все глубже и глубже, без выхода.

Немного английского сленга для всех.

После того, как вы узнаете значение названия, после того, как вы прочитаете статью, написанную автором, вы еще раз посмотрите на название этой «кроличьей норы», и вы обнаружите, что оно очень уместно.

Прочитав полный текст и разобравшись, я обнаружил, что есть четыре причины, по которым автор хочет выразиться так быстро:

  • Оптимизация уровня байт-кода — попробуйте использовать встроенные средства JIT
  • Оптимизация на уровне байт-кода — используйте инструкции, которые легче оптимизировать с помощью JVM.
  • Оптимизация на уровне кода — замена ArrayList обновленным FastList
  • Оптимизация на уровне кода — использование неблокирующего ConcurrentBag

Давайте посмотрим их один за другим.

Оптимизация уровня байт-кода

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

Просто переведите ключевые моменты:

  • Чтобы сделать HikariCP быстрее, я провел оптимизацию на уровне байт-кода.
  • Я использовал все известные мне приемы, чтобы воспользоваться преимуществами JIT-оптимизации, чтобы помочь вам.
  • Я просмотрел вывод байт-кода компилятора и даже вывод ассемблера JIT, чтобы ограничить количество критических программ меньшим, чем встроенный порог JIT.

Здесь автор упомянул встроенную оптимизацию JIT.

Что такое встроенный?

Встраивание на самом деле является действием.

Выберите вызываемый метод и скопируйте его содержимое в вызываемое место.

В качестве простого примера предположим, что код выглядит так:

int result = add(a,b);

private int add(int x,int y){
    return x+y;
}

Тогда после встроенной JIT-оптимизации код станет таким:

int result= a + b;

Таким образом, накладные расходы на вызов метода добавления сохраняются.

Встраивание, также известное как оптимизация дочерних матерей, заложило очень хорошую основу для других методов оптимизации, поэтому помимо примера, написанного выше, существует множество более продвинутых проявлений, таких как escape-анализ, зацикливание Expand, снятие блокировки:

Итак, каковы накладные расходы на вызов?

Я думаю, что это не более чем эти шаги:

  • Первое, что нужно сделать, это установить параметры, которые должен передать вызов метода, верно?
  • С параметрами нужно запрашивать, какой метод вызывать, верно?
  • Затем, если есть такие методы, как локальные переменные или оценка, вам нужно создать новые кадры стека вызовов, создать новые структуры данных времени выполнения, верно?
  • Наконец, может возникнуть необходимость вернуть результат вызывающей стороне, верно?

Некоторые друзья скажут, а для? Не кажется ли это большими расходами?

Да, он действительно невелик, но когда маленькая точка оптимизации умножается на огромное количество вызовов, конечный результат очень впечатляет.

Я думаю, все это понимают.

Автор также указал в статье:

HikariCP включает в себя множество микрооптимизаций, которые почти неизмеримы сами по себе, но в сочетании улучшают общую производительность.

Даже в миллионах вызовов уровень оптимизации измеряется миллисекундами.

Может быть, это босс.

Я думаю, что стремление к максимальной производительности — это не что иное, как это.

Далее поговорим о другой оптимизации на уровне байт-кода:

invokevirtual vs invokestatic

Я думаю, что эта волна оптимизации просто витает в воздухе.

Автор приводит пример.

Прокси-объекты Connection, Statement и ResultSet ранее были получены с помощью фабричного метода singleton.

Что-то вроде этого:

ROXY_FACTORY — это статическое поле.

Байт-код приведенного выше кода выглядит так:

Как видно из байт-кода, сначала идет вызов getstatic для получения значения статического поля PROXY_FACTORY.

Также есть вызов инструкции invokevirtual, которая соответствует методу getProxyPreparedStatement() экземпляра ProxyFactory:

15: invokevirtual #69  // Method com/zaxxer/hikari/proxy/ProxyFactory.getProxyPreparedStatement:(Lcom/zaxxer/hikari/proxy/ConnectionProxy;Ljava/sql/PreparedStatement;)Ljava/sql/PreparedStatement;

Есть ли место для оптимизации в этом месте?

Автор изменил код, чтобы он выглядел так:

Где ProxyFactory генерируется Javassist.

Итак, вы смотрите исходный код ProxyFactory, все пустые реализации,

Его реальная логика реализации — это класс, соответствующий исходному коду, который подробно не показан, если вам интересно, то можете глянуть:

com.zaxxer.hikari.util.JavassistProxyFactory

Затем положитеgetProxyPreparedStatementМетод сделан статическим.

Тогда байт-код становится таким:

Случилось что-то волшебное:

  • директива getstatic исчезла
  • invokevirtual был заменен вызовом invokestatic, который легче оптимизировать с помощью JVM.
  • Наконец, на первый взгляд можно не заметить, что размер стека уменьшен с 5 до 4. Это связано с тем, что в случае invokevirtual экземпляр ProxyFactory неявно передается в стек (также известный как этот объект), и при вызове getProxyPreparedStatement() происходит дополнительное извлечение из стека.

Пункты 1 и 3 не должны быть проблемой. Все могут понять, что происходит.

А вот этот второй момент: invokevirtual заменяется вызовом invokestatic, который проще оптимизировать с помощью JVM.

Серьезно, это выглядело так, когда я впервые увидел это:

Зачем?

Я до сих пор помню, что делают invokevirtual и invokestatic.

Но будет ли invokestatic работать немного лучше?

Поэтому я взял этот вопрос, чтобы прочитать «Углубленное понимание виртуальной машины JVM», но не нашел прямого ответа.

Но есть еще сюрпризы. Только что написал эту статью:"Отчет! В книге ошибка"

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

Хотя ответ прямо в книге не написан, в соответствующей части есть такой отрывок:

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

И invokestatic может быть преобразован из символической ссылки в прямую ссылку при загрузке класса.

Таким образом, invokestatic действительно лучше, чем invokevirtual.

Затем проблема возникает снова.

Как происходит процесс загрузки класса?

Загрузите, проверьте, подготовьте, проанализируйте, инициализируйте.

В каком процессе invokestatic что-то делает?

Это должно быть фаза разбора, друзья мои.

Фаза синтаксического анализа — это процесс, в котором JVM заменяет символические ссылки в пуле констант прямыми ссылками.

Отстранись, скажи это в ответ.

Вышеизложенное является лишь моим предположением, я полагаю, что я не единственный, у кого есть сомнения относительно invokevirtual vs invokestatic после прочтения статьи автора о "кроличьей норе".

Итак, я пошел, чтобы проверить вокруг.

Конечно же. (Извините за грубость, но я долго искал.)

Нашел эту ссылку, первая половина ссылки точно такая же, как мой вопрос:

GitHub.com/Бретт Шерсть Доктор…

Ответ автора следующий:

Последний абзац «Дополнительно» понять несложно.

Как упоминалось ранее, статические вызовы имеют на один стек меньше и на одну операцию push/pull во время выполнения меньше, что еще больше повышает производительность.

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

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

Установка и очистка этой ловушки увеличивает нагрузку на вызов.

Как дела, ты остолбенел?

На самом деле, я лично понимаю, что он сказал, говоря о динамической диспетчеризации Java и говоря о технологии JVM CHA (анализ иерархии классов, анализ отношений наследования типов).

Ответ написан на странице 417 «Углубленного понимания виртуальной машины Java (третье издание)», переверните ее:

Вы должны спросить меня, что является доказательством, тогда эти два слова повторяются, какое совпадение вы говорите?

invokevirtual вызывает виртуальные методы.Согласно книге, вышеупомянутыйtrapПо сути, это и есть «дверь эвакуации»:

Предложение «Эта установка ловушки и очистка добавляют немного больше служебных данных к вызову (установка и очистка ловушки добавляют немного служебной информации к вызову») на самом деле соответствует этому:

Теперь вы знаете, почему invokestatic легче оптимизировать для JVM, чем invokevirtual?

Оптимизация относится к встраиванию.

invokestatic вызывает статические методы. Для невиртуальных методов JVM может напрямую встроить их. Такое встраивание на 100 % безопасно.

И invokevirtual вызывает виртуальные методы.Для встраивания виртуальных методов вы должны использовать механизм CHA для установки двери выхода.

Хотя все это встроено, производительность не снижается.

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

Эта волна действует в атмосфере.

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

Оптимизация уровня кода

Самое известное на уровне кода то, что FastList заменяет ArrayList.

Сначала я посмотрел журнал коммитов проекта, 15 января 2014 года автор сделал коммит:

Мы должны быть знакомы со второй половиной замечаний, которые были упомянуты ранее.

Предыдущий коммит заменил ArrayList на FastList.

Java ArrayList выполняет проверку диапазона каждый раз, когда вызывается get(int index). В проекте HikariCP индекс может быть гарантированно в правильном диапазоне, поэтому эта проверка бессмысленна, поэтому она убрана:

Другой пример: метод remove(Object o) в ArrayList сканирует от начала до конца.

Предположим, что для удаления последнего элемента нужно перебрать весь массив.

По совпадению, например, в заявлении HikariCP, согласно нашим привычкам кодирования, удаление (закрыть) должно сначала удалить последнее.

Итак, FastList оптимизирует метод remove(Object element) и превращает порядок поиска в обратный поиск:

В целом, точкой оптимизации FastList являются упомянутые выше методы получения и удаления.

Далее рассмотрим еще одну оптимизацию на уровне кода:

Автор делает несколько выводов:

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

Авторское вступление очень простое, на самом деле в нем еще много чего есть.

Важный трюк заключается в том, что ConcurrentBag выполняет предварительное выделение соединения через ThreadLocal.

ThreadLocal в определенной степени позволяет избежать конкуренции общих ресурсов.

Если вы посмотрите на код самостоятельно, вы должны в основном смотреть на методы добавления (бездействующие соединения добавляются в очередь), заимствовать (получать соединения) и требовать (освобождать соединения).

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

О, вы не хотите читать другие статьи, вы просто хотите подождать, пока я вам скажу? Хорошо, сначала должен, должен. Поленитесь, статья слишком длинная и ее никто не читает.

борьба

В процессе написания статьи тоже увидел такой вопрос, мне мало интересно, напишу.

GitHub.com/Бретт Шерсть Доктор…

Младший брат сказал:

Здравствуйте, я думаю, что ваш анализ пула баз данных Java очень ценен. Я случайно наткнулся на этот пул потоков druid от Alibaba (утверждается, что это самый быстрый пул базы данных в Java!). Судя по моему беглому взгляду, у него есть несколько интересных функций. Любые мысли по этому поводу. Благодарю.

Автор HikariCP быстро ответил:

По крайней мере, в своих тестах Друид был самым медленным в установлении и возврате соединений и третьим в скорости создания и закрытия операторов. Страница тестов в их вики не показывает, с какой конфигурацией они работают, но я подозреваю, что они отключили заимствованные тесты. Хотя я бы не сказал, что это «читерство», я не использую его в продакшене. Насколько я знаю, они также не предоставляют свой тестовый исходный код.

Это довольно интересно.

Хотя я не скажу, что это «читерство», это как: «Есть предложение, я не знаю, говорить его или нет».

Затем оно продолжило говорить.

Затем другой пользователь сети, который ест дыню, сказал:

Теория дизайна Druid сосредоточена на улучшении поведения при мониторинге и доступе к данным (например, автоматическая нарезка базы данных). Он предоставляет анализатор SQL для анализа пользовательских запросов SQL и собирает много данных для мониторинга. Итак, если вам нужно решение для мониторинга JDBC, вы можете попробовать Druid.

Автор HikariCP тоже сказал, что в этом предложении нет ничего плохого, но подчеркнул, что его HikariCP тоже оставляет дыру для мониторинга:

Это верный момент. Я хотел бы отметить, что HikariCP также предоставляет данные мониторинга, но предоставляемые метрики являются метриками «уровня пула», а не специфичными для времени выполнения запроса и т. д.

Все вышеперечисленные разговоры произошли в январе 2015 года.

Но через полтора года, 26.07.2016, проблема снова активировалась у одного человека:

Вэньшао, кто идет?

Этот человек — один из отцов Друида, которого во всех уголках мира зовут Шао Вэнь.

Может быть, вы не знаете Вэнь Шао, может быть, вы не знаете друида, написанного Вэнь Шао, но вы должны знать еще один шедевр Вэнь Шао:

Проблема немного больше, но это не мешает другим быть великими богами.

Чай можно подавать прямо:

Во-первых, Шао Вэнь сказал: Если вы настроите атрибут maxWait, друид будет использовать честные блокировки, что снижает производительность.

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

Затем он упомянул Taobao:

Нажмите на ссылку, заголовок такой:

Говоря о Tmall Double 11 в 2015 году.

Название переводится как:

Alibaba Group продала 5 миллиардов долларов за первые 90 минут распродажи в День холостяка.

Я также увидел Dad Ma, которого давно не видел в ссылке:

Я понимаю, что Вэнь Шаофан имеет в виду под этой ссылкой, что друид используется внутри Alibaba, Tmall Double Eleven — очень мощная сцена, и друид выдержал испытание такой сценой.

Автор HikariCP не ответил Вэнь Шао.

Пока очередная толпа, поедающая дыни, не подлила масла в огонь:

Автор HikariCP подумал, что это битва за объем данных.

Тогда добро пожаловать.

HikariCP — один из наиболее широко используемых пулов соединений в мире, используемый некоторыми из крупнейших компаний, ежедневно обслуживающий миллиарды пользователей.

А насчет друида, извините, я немного резковат: он редко встречается за пределами Китая.

Но на его ответ у кого-то быстро возникли сомнения:

Используется некоторыми крупнейшими компаниями, ежедневно обслуживающими миллиарды пользователей? Например?

Хотите данные? Плотно держаться:

  • На wix.com размещается более 109 миллионов веб-сайтов и обрабатывается более 1 миллиарда запросов в день.
  • У продуктов Atlassian миллионы клиентов.
  • HikariCP — это пул соединений по умолчанию для весенней загрузки.
  • HikariCP анализируется более 300 000 раз в месяц из центрального репозитория maven.

Эти компании используют:

После этого ответа ни одна из сторон не говорила.

Битва между двумя сторонами окончена.

Но есть еще люди, которые продолжают постить, я думаю, что этот приятель принадлежит к тем, кто трезво ест дыню:

Интересен ответ еще одного старика:

Хватит спорить, хватит спорить. Я здесь, чтобы изучать технологии, а не обсуждать разницу между «капиталистическими» и «коммунистическими» инструментами.

И я не думаю, что эта битва действительно имеет какой-то смысл.

С точки зрения технического подбора нет лучшего, есть только подходящее.

Друид и HikariCP имеют свои преимущества.

Последнее слово (пожалуйста, обратите внимание)

Хорошо, пожалуйста, следуйте за мной, когда увидите это, Чжоу Гэн очень устал и нуждается в положительном отзыве.

Спасибо за прочтение, настаиваю на оригинальности, очень приветствую и благодарю за внимание.

Категории