Русский
Русский
English
Статистика
Реклама

Jit

Перевод Взгляд на Tailwind CSS

23.05.2021 18:15:00 | Автор: admin

В этом году я видел много шумихи вокруг популярного фреймворка CSS, Tailwind CSS. И подумал, что поделюсь некоторыми мыслями и опасениями по поводу этого фреймворка UI. Я приобрёл небольшой опыт написания CSS с подходом utility-first (полезность прежде всего), когда начал свою карьеру в разработке интерфейсов, несколько лет назад.

К старту курса о Frontend-разработке делимся статьёй, автор которой решил, возможно, несколько изменить то, как воспринимается Tailwind, рассказать как о положительных, так и об отрицательных сторонах фреймворка, особенно в контексте работы с мультиязычными сайтами.


Вещи, которые я считаю интересными

Вам не нужно закрывать файл с HTML

Основной заголовок на официальном сайте Tailwind гласит:

Быстрое создание современных веб-сайтов, даже не покидая HTML.

Я согласен, что писать код в одном месте может быть быстрее, чем переключаться между разными файлами. Однако оставить в стороне свой HTML для меня не проблема. Это может помочь переключить контекст между разметкой и стилями. Разделение файлов HTML и CSS может помочь мне лучше сосредоточиться на выполнении поставленной задачи. Однако, когда разметка и стили смешиваются, когда вы работаете над сложным, многоязычным, отзывчивым сайтом и пользовательским интерфейсом с темами, всё может пойти наперекосяк.

Когда я работаю с Tailwind, это похоже на то, как если бы я держал две ручки: одну для набросков, а другую для раскрашивания. Одновременное написание разметки и CSS напоминает мне эти две ручки.

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

Проектные ограничения

В дизайн-системе мы обычно определяем цвет, интервал, размер и другие свойства. Работа разработчика сильно упрощается, когда есть пределы тому, что он может сделать. Tailwind накладывает хорошие ограничения. Идея определения пользовательских конфигурационных файлов очень полезна.

Именование классов CSS

О названиях классов CSS думать не нужно и это может ускорить реализацию макетов в вебе. Я нахожу это полезным в случае, когда ваш босс пишет вам, что есть срочный лендинг, который должен быть сделан сегодня, и у вас только 3 часа, чтобы написать его. Tailwind может помочь быстро достичь результата.

Однако, если вы придерживаетесь разделения разметки и стилей, разработка займёт гораздо больше времени. Ещё одно полезное применение Tailwind это соревнование или хакатон. Самое главное на таких событиях сделать работу и что-то получить за неё. Никому не будет дела до того, как вы написали CSS, верно?

То, с чем я не согласен

Tailwind не фреймворк utility-first

Подзаголовок на их веб-сайте сообщает, что CSS Tailwind:

Прежде всего утилитарный CSS-фреймворк, содержит такие классы, как...

Из увиденного я сделал вывод, что Tailwind это только утилитарный (utility-only) фреймворк. Может быть, название "только утилитарный" повлияет на то, как его воспримут новички? Я редко вижу какой-то сайт, использующий Tailwind и применяющий концепцию utility-first.

Длинный список классов может запутать

Обратите внимание на то, что я знаю о методе @apply. Рассмотрим пример из документации Tailwind:

<input  class="block appearance-none bg-white placeholder-gray-600 border border-indigo-200 rounded w-full py-3 px-4 text-gray-700 leading-5 focus:outline-none focus:border-indigo-400 focus:placeholder-gray-400 focus:ring-2 focus:ring-indigo-200"  placeholder="jane@example.com"/>

Это поле ввода с 17 классами CSS. Что проще: читать классы один за одним горизонтально или сканировать их сверху вниз? Вот так поле будет выглядеть стиль этого поля в файле CSS:

.c-input {  display: block;  appearance: none;  background-color: #fff;  @include placeholder(grey);  border: 1px solid rgba(199, 210, 254, var(--tw-border-opacity));  border-radius: 0.25rem;  padding: 0.75rem 1rem;  line-height: 1.25rem;  color: rgba(55, 65, 81, var(--tw-text-opacity));}.c-input:focus {  /* Focus styles.. */}

Я могу прочитать и понять, какие стили содержит это поле ввода, гораздо быстрее, чем в ситуации, когда сканирую HTML. Возможно, существуют плагины для редакторов кода, которые автоматически форматируют HTML-классы так, чтобы каждый из них располагался отдельно [на отдельной строке], но в DevTools браузера этого нет.

Я знаю о методе @apply, но каждый раз, когда я думаю о нём, я прихожу к выводу, что он противоречит основной концепции Tailwind. Вот тот же пример (поле ввода):

.c-input {  @apply block appearance-none bg-white placeholder-gray-600 border border-indigo-200 rounded w-full py-3 px-4 text-gray-700 leading-5 focus:outline-none focus:border-indigo-400 focus:placeholder-gray-400 focus:ring-2 focus:ring-indigo-200;}

Посмотрите на длину списка классов. Если в Tailwind в приоритете полезность, то почему в официальной документации Tailwind или в Tailwind UI мы редко видим @apply? Опять же, я вижу Tailwind как только утилитарный фреймворк.

Всегда нужно давать имена элементам

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

Привет, есть новости о bg-white w-full py-3 px-4?

На самом деле фраза будет такой:

Есть новости о дизайне поляризованной карты?

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

<person class="hair-brown length-[175] face-rounded"></person>

Код выше бессмыслица. Гораздо лучше такой код:

<person class="ahmad"></person>

Некоторые классы запутывают

Когда я только начинал использовать Tailwind, мне нужно было добавить класс, который отвечает за свойство align-items: center. Моей первой мыслью было воспользоваться align-center, но это не сработало.

Я посмотрел в документацию и впал в замешательство. Класс items-center добавит CSS-свойство align-items: center, где класс align-middle будет содержать vertical-align: middle. Чтобы запомнить их, требуется немного мышечной памяти.

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

Tailwind затрудняет настройку дизайна в браузере

Я занимаюсь как дизайном, так и frontend-разработкой, поэтому редактирование в браузере с помощью DevTools для меня крайне важно. С Tailwind работа в DevTools может стать сложнее. Скажем, например, я хочу изменить отступы для компонента. Когда я изменяю значение класса py-3, это влияет на каждый использующий его элемент страницы.

Единственный способ убрать его открыть меню .cls в DevTools, после чего класс можно будет переключить. Однако это не решает проблему. Что я могу сделать в этом случае? Добавить встроенный стиль через DevTools, а это мне не нравится. Проблема решится, если просто давать элементам названия. Добавлю к этому факт: общий размер файла полной сборки Tailwind составляет 12 МБ. Редактирование CSS в DevTools будет очень медленным.

Это означает, что разработка в браузере неэффективна. Недавно команда Tailwind выпустила компилятор JIT (just in time), удаляющий неиспользуемый CSS. Это мешает всей идее дизайна в браузере.

Я набрал back и получил длинный список всех доступных классов CSS. При JIT-компиляции таких подсказок не будет.

Tailwind неудобен для многоязычных сайтов

Чтобы вы больше понимали, добавлю: я работаю над веб-сайтами, которые должны работать как на английском (с направлением слева направо, LTR), так и на арабском (с направлением справа налево, RTL). Рассмотрим такой код:

/* LTR: left to right */.c-input {  padding-left: 2rem;}

В отдельном файле CSS для RTL стиль будет выглядеть так:

/* RTL: Right to left */.c-input {  padding-left: 0;  padding-right: 2rem;}

В приведённом выше примере padding следует развернуть в зависимости от направления страницы (слева направо или справа налево). Для этого уже есть плагины, но они решают проблему только с внешней стороны. Первый найденный мной делает следующее:

html[dir="rtl"] .ml-2 {   margin-right: 1rem; }

Для меня это не очень хорошее решение. Второй найденный плагин немного отличался от первого:

[dir="rtl"] .rtl\:text-2xl {  font-size: 1.5rem;  line-height: 2rem;}

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

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

Чтобы помочь в создании многоязычного веб-сайта, сейчас я использую Bi-App Sass. Вот пример:

.elem {  display: flex;  @include margin-left(10px);  @include border-right(2px solid #000);}

Код скомпилируется в два разных файла CSS. Файл ltr:

/* LTR Styles */.elem {  display: flex;  margin-left: 10px;  border-right: 2px solid #000;}

Подробнее о стилизации RTL читайте в этом руководстве от вашего покорного слуги.

Я не всегда работаю с шаблонами

Одна из проблем Tailwind заключается в том, что, если у вас есть список карточек и вы хотите изменить определённый набор классов, вам придётся вручную просматривать их в редакторе кода. Это не будет проблемой, если вы используете в своём продукте частичные файлы CSS (partial) или компоненты. Вы можете один раз написать HTML, и любое изменение будет отражено везде, где используется этот компонент.

Это не всегда так. Я работаю над простыми страницами index.html, где усилия в разделении на части или компоненты себя не оправдывают. В этом случае работа с Tailwind и редактирование CSS могут стать процессом, чреватым ошибками, поскольку вы даже не можете использовать функцию "Найти и заменить": она может пропустить некоторые другие элементы на странице.

Tailwind делает веб-сайты похожими

Команда Tailwind создала набор продуманных компонентов под названием Tailwind UI, которые легко настраиваются и готовы к использованию в вашем проекте.

Внешний вид и функциональность компонентов приятные, и мне это нравится. Хотя есть проблемы, если сайт использует Tailwind UI, есть большая вероятность, что я предскажу использовать его.

Обычно, когда вы работа Tailwind UI, это означает, что у вас нет времени на создание индивидуального дизайна, поэтому вам нужно что-то, что можно быстро запустить, верно? И это хороший вариант применения, за исключением одной детали: применение Tailwind приведёт к тому, что многие сайты будут выглядеть похожими друг на друга, подобно тому, что было много лет назад с Bootstrap.

Некоторые свойства или особенности CSS использовать невозможно

К примеру, не включено свойство clip-path, и я полностью понимаю причину. Предположим, мы хотим включить его как компонент. Где мы должны написать код? Здесь:

<article class="block appearance-none bg-white placeholder-gray-600 border border-indigo-200 rounded w-full py-3></article>

Либо примерно так включить его в конфигурацию Tailwind:

// tailwind.config.jsmodule.exports = {  theme: {    clipPath: {      // Configure your clip-path values here    }  }};

Или же сделать следующее:

.card {  @apply block appearance-none bg-white placeholder-gray-600 border border-indigo-200 rounded w-full py-3;  clip-path: inset(20px 20px 50px 20px);}

Заключительные мысли

Может ли Tailwind оборачивать CSS в свои классы во время компиляции?

Представьте себе, что Tailwind появляется только во время компиляции. То есть вы пишете обычный CSS с именованием и всё такое, а умный компилятор преобразует ваш CSS в утилитарные классы. Это было бы воплощением мечты.

Утилитарные классы мощный инструмент, если не перестараться

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

Фронтенд довольно часто выбирают как точку входа в IT. Но фронтенд это не только внешнее оформление сайтов, но и работа с базами данных, а также внешними API. Фронтенд требует системной, комплексной подготовки. Если вам или вашим знакомым интересно это направление разработки можете присмотреться к нашему курсу о Frontend-разработке, где мы касаемся не только вышеупомянутых тем, но и разработки отзывчивых сайтов при помощи flexbox, работаем с методологией БЭМ и затрагиваем другие аспекты фронтенда.

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Перевод Понимаем JIT в PHP 8

05.07.2020 04:21:30 | Автор: admin
Перевод статьи подготовлен в преддверии старта курса Backend-разработчик на PHP



TL;DR


Компилятор Just In Time в PHP 8 реализован как часть расширения Opcache и призван компилировать операционный код в инструкции процессора в рантайме.

Это означает, что с JIT некоторые операционные коды не должны интерпретироваться Zend VM, такие инструкции будут выполняться непосредственно как инструкции уровня процессора.

JIT в PHP 8


Одной из наиболее комментируемых фич PHP 8 является компилятор Just In Time (JIT). Он на слуху во множестве блогов и сообществ вокруг него много шума, но пока я нашел не очень много подробностей о работе JIT в деталях.

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

Упрощая вещи: когда JIT работает должным образом, ваш код не будет выполняться через Zend VM, вместо этого он будет выполняться непосредственно как набор инструкций уровня процессора.

В этом вся идея.

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

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

Как выполняется PHP-код?


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

Всякий раз, когда вы хотите выполнить код PHP, будь то фрагмент или целое веб-приложение, вам придется пройти через интерпретатор php. Наиболее часто используемые из них PHP FPM и интерпретатор CLI. Их работа очень проста: получить код php, интерпретировать его и выдать обратно результат.

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

  1. Код PHP читается и преобразуется в набор ключевых слов, известных как токены (Tokens). Этот процесс позволяет интерпретатору понять, в какой части программы написан каждый фрагмент кода. Этот первый шаг называется лексирование (Lexing) или токенизация (Tokenizing).
  2. Имея на руках токены, интерпретатор PHP проанализирует эту коллекцию токенов и постарается найти в них смысл. В результате абстрактное синтаксическое дерево (Abstract Syntax Tree AST) генерируется с помощью процесса, называемого синтаксическим анализом (parsing). AST представляет собой набор узлов, указывающих, какие операции должны быть выполнены. Например, echo 1 + 1 должно фактически означать вывести результат 1 + 1 или, более реалистично, вывести операцию, операция 1 + 1.
  3. Имея AST, например, гораздо проще понять операции и их приоритет. Преобразование этого дерева во что-то, что может быть выполнено, требует промежуточного представления (Intermediate Representation IR), которое в PHP мы называем операционный код (Opcode). Процесс преобразования AST в операционный код называется компиляцией.
  4. Теперь, когда у нас есть опкоды, происходит самое интересное: выполнение кода! PHP имеет движок под названием Zend VM, который способен получать список опкодов и выполнять их. После выполнения всех опкодов программа завершается.


Чтобы сделать это немного нагляднее, я составил диаграмму:


Упрощенная схема процесса интерпретации PHP.

Достаточно прямолинейно, как вы можете заметить. Но здесь есть и узкое место: какой смысл лексировать и парсить код каждый раз, когда вы его выполняете, если ваш php-код может даже не меняется так часто?

В конце концов, нас интересуют только опкоды, верно? Правильно! Вот зачем существует расширение Opcache.

Расширение Opcache


Расширение Opcache поставляется с PHP, и, как правило, нет особых причин его деактивировать. Если вы используете PHP, вам, вероятно, следует включить Opcache.

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

Вот схема того же процесса с учетом расширения Opcache:


Поток интерпретации PHP с Opcache. Если файл уже был проанализирован, php извлекает для него кэшированный операционный код, а не анализирует его заново.

Это просто завораживает, как красиво пропускаются шаги лексирования, синтаксического анализа и компиляции.
Примечание: именно здесь лучше всего себя проявляет функция предварительной загрузки PHP 7.4! Это позволяет вам сказать PHP FPM анализировать вашу кодовую базу, преобразовывать ее в опкоды и кэшировать их даже до того, как вы что-либо выполните.

Вы можете начать задумываться, а куда сюда можно прилепить JIT, верно?! По крайней мере я на это надеюсь, именно поэтому я и пишу эту статью

Что делает компилятор Just In Time?


Прослушав объяснение Зива в эпизоде подкастов PHP и JIT от PHP Internals News, мне удалось получить некоторое представление о том, что на самом деле должен делать JIT

Если Opcache позволяет быстрее получать операционный код, чтобы он мог переходить непосредственно к Zend VM, JIT предназначить заставить его работать вообще без Zend VM.

Zend VM это программа, написанная на C, которая действует как слой между операционным кодом и самим процессором. JIT генерирует скомпилированный код во время выполнения, поэтому php может пропустить Zend VM и перейти непосредственно к процессору. Теоретически мы должны выиграть в производительности от этого.

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

Реализация JIT в PHP использует библиотеку DynASM (Dynamic Assembler), которая отображает набор команд ЦП в конкретном формате в код сборки для многих различных типов ЦП. Таким образом, компилятор Just In Time преобразует операционный код в машинный код для конкретной архитектуры, используя DynASM.

Хотя одна мысль все-таки не давала мне покоя

Если предварительная загрузка способна парсить php-код в операционный перед выполнением, а DynASM может компилировать операционный код в машинный (компиляция Just In Time), почему мы, черт возьми, не компилируем PHP сразу же на месте, используя Ahead of Time компиляцию?!

Одна из мыслей, на которые меня натолкнул эпизода подкаста, заключалась в том, что PHP слабо типизирован, то есть часто PHP не знает, какой тип имеет переменная, пока Zend VM не попытается выполнить определенный опкод.

Это можно понять, посмотрев на тип объединения zend_value, который имеет много указателей на различные представления типов для переменной. Всякий раз, когда виртуальная машина Zend пытается извлечь значение из zend_value, она использует макросы, подобные ZSTR_VAL, которые пытаются получить доступ к указателю строки из объединения значений.

Например, этот обработчик Zend VM должен обрабатывать выражение меньше или равно (<=). Посмотрите, как он разветвляется на множество различных путей кода, чтобы угадать типы операндов.

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

Финальная компиляция после того, как типы были оценены, также не является хорошим вариантом, потому что компиляция в машинный код является трудоемкой задачей ЦП. Так что компиляция ВСЕГО во время выполнения плохая идея.

Как ведет себя компилятор Just In Time?


Теперь мы знаем, что не можем вывести типы, чтобы генерировать достаточно хорошую опережающую компиляцию. Мы также знаем, что компиляция во время выполнения стоит дорого. Чем же может быть полезен JIT для PHP?

Чтобы сбалансировать это уравнение, JIT PHP пытается скомпилировать только несколько опкодов, которые, по его мнению, того стоят. Для этого он профилирует коды операций, выполняемые виртуальной машиной Zend, и проверяет, какие из них имеет смысл компилировать. (в зависимости от вашей конфигурации).

Когда определенный опкод компилируется, он затем делегирует выполнение этому скомпилированному коду вместо делегирования на Zend VM. Это выглядит как на диаграмме ниже:


Поток интерпретации PHP с JIT. Если они уже скомпилированы, опкоды не выполняются через Zend VM.

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

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

Кстати, эта беседа Бенуа Жакемона о JIT от php ОЧЕНЬ помогла мне разобраться во всем этом.

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

Так что, вероятно, ваш прирост производительности не будет колоссальным


Я надеюсь, что сейчас гораздо понятнее, ПОЧЕМУ все говорят, что большинство приложений php не получат больших преимуществ в производительности от использования компилятора Just In Time. И почему рекомендация Зива для профилирования и эксперимента с различными конфигурациями JIT для вашего приложения лучший путь.

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

Это потому, что JIT оптимизирует операции с процессором, а в настоящее время большинство php-приложений в большей степени завязаны на вводе/выводе, нежели на чем-либо еще. Не имеет значения, скомпилированы ли операции обработки, если вам все равно придется обращаться к диску или сети. Тайминги будут очень похожи.

Если только...

Вы делаете что-то не завязанное на ввод/вывод, например, обработку изображений или машинное обучение. Все, что не касается ввода/вывода, получит пользу от компилятора Just In Time. Это также причина, по которой люди сейчас говорят, что они склоняются больше к написанию нативных функций PHP, написанных на PHP, а не на C. Накладные расходы не будут разительно отличаться, если такие функции будут скомпилированы в любом случае.

Интересное время быть программистом PHP

Я надеюсь, что эта статья была полезна для вас, и вам удалось лучше разобраться, что такое JIT в PHP 8. Не стесняйтесь связываться со мной в твиттере, если вы хотите добавить что-то, что я мог забыть здесь, и не забудьте поделиться этим со своими коллегами-разработчиками, это, несомненно, добавит немного пользы вашим беседам!-- @nawarian



PHP: статические анализаторы кода


Подробнее..

Перевод Как реализованы JIT-компиляторы

31.07.2020 16:11:33 | Автор: admin

В этой статье мы поговорим о подробностях реализации и работе разных JIT-компиляторов, а также о стратегиях оптимизации. Обсуждать будем достаточно подробно, однако многие важные концепции опустим. То есть в этой статье не будет достаточной информации, чтобы прийти к обоснованным заключениям при любых сравнениях реализаций и языков.

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

Небольшое примечание:

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


Основные вехи


  • Метатрассировка в работе Pypy
  • Как языки GraalVM поддерживают расширения на C
  • Деоптимизация
  • Инлайнинг и OSR
  • Океаны нод
  • Многоуровневое использование JIT

(Мета)трассировка


LuaJIT использует так называемую трассировку (tracing). Pypy выполняет метатрассировку, то есть использует систему для генерирования интерпретаторов трассировки и JIT. Pypy и LuaJIT это не образцовые реализации Python и Lua, а самостоятельные проекты. Я бы охарактеризовал LuaJIT как шокирующе быструю, и она сама себя описывает как одну из самых быстрых динамических языковых реализаций и я этому безоговорочно верю.

Чтобы понять, когда начинать трассировку, цикл интерпретации ищет горячие циклы (концепция горячего кода универсальна для всех JIT-компиляторов!). Затем компилятор трассирует цикл, записывая исполняемые операции для компилирования хорошо оптимизированного машинного кода. В LuaJIT компиляция выполняется на основе трасс с промежуточным представлением, похожим на инструкции, которое уникально для LuaJIT.

Как трассировка реализована в Pypy


Pypy начинает трассировать функцию после 1619 исполнений и компилирует ещё после 1039 исполнений, то есть нужно около 3000 исполнений функции, чтобы она начала работать быстрее. Эти значения тщательно подбирались командой Pypy, и вообще в мире компиляторов многие константы подбираются продуманно.

Динамические языки затрудняют оптимизацию. Нижеприведённый код может быть статически удалён более строгим языком, потому что False всегда будет ложным. Однако в Python 2 этого нельзя гарантировать до момента исполнения.

if False:  print("FALSE")

Для любой разумной программы это условие всегда будет ложным. К сожалению, значение False может быть переопределено, и выражение будет в цикле, его могут переопределить где-то в другом месте. Поэтому Pypy может создать защитника. Если защитник не справляется, JIT возвращается к циклу интерпретирования. Затем Pypy с помощью ещё одной константы (200), которая называется trace eagerness, решает, нужно ли компилировать остаток нового пути до конца цикла. Этот подпуть называется мостом (bridge).

Кроме того, Pypy предоставляет эти константы как аргументы, которые можно настраивать в ходе исполнения наряду с конфигурацией развёртки (unrolling), то есть расширения цикла, и инлайнинга! И вдобавок он предоставляет хуки, которые мы можем увидеть после завершения компилирования.

def print_compiler_info(i):  print(i.type)pypyjit.set_compile_hook(print_compiler_info)for i in range(10000):  if False:    passprint(pypyjit.get_stats_snapshot().counters)

Выше я написал программу на чистом Python с компилирующим хуком для вывода на экран типа применённой компиляции. Также код выводит в конце данные, в которых видно количество защитников. Для этого программы я получил одну компиляцию цикла и 66 защитников. Когда я заменил выражение if простым проходом вне цикла for, осталось только 59 защитников.

for i in range(10000):  pass # removing the `if False` saved 7 guards!

Добавив эти две строки в цикл for, я получил две компиляции, одна из которых относилась к типу мост!

if random.randint(1, 100) < 20:  False = True

Погоди, ты же говорил про метатрассировку!


Идею метатрассировки можно описать как напиши интерпретатор, и получи компилятор бесплатно!, или преврати свой интерпретатор в JIT-компилятор!. Писать компилятор трудно, и если можно получить его бесплатно, то идея крутая. Pypy содержит интерпретатор и компилятор, но в нём нет явной реализации традиционного компилятора.

В Pypy есть инструмент RPython (созданный для Pypy). Это фреймворк для написания интерпретаторов. Его язык относится к разновидности Python и обеспечивает статическую типизацию. На этом языке и нужно писать интерпретатор. Язык не предназначен для программирования на типизированном Python, потому что не содержит стандартных библиотек или пакетов. Любая программа на RPython является корректной Python-программой. Код на RPython транспилируется в С, а затем компилируется. Таким образом, метакомпилятор на этом языке существует в виде скомпилированной программы на С.

Приставка мета в слове метатрассировка означает, что трассировка выполняется при исполнении интерпретатора, а не программы. Он ведёт себя более-менее как любой другой интерпретатор, но может отслеживать свои операции и предназначен для оптимизации трасс с помощью обновления своего пути прохождения. С дальнейшей трассировкой путь интерпретатора становится более оптимизированным. Хорошо оптимизированный интерпретатор идёт по специфическому пути. А используемый в этом пути машинный код, полученный при компилировании RPython, может использоваться в окончательной компиляции.

Короче, компилятор в Pypy компилирует ваш интерпретатор, поэтому Pypy иногда называют метакомпилятором. Он компилирует не столько программу, которую вы исполняете, сколько путь оптимизированного интерпретатора.

Концепция метатрассировки может показаться непонятной, поэтому для иллюстрации я написал очень плохую программу, которая понимает только a = 0 и a++to.

# interpreter written with RPythonfor line in code:  if line == "a = 0":    alloc(a, 0)  elif line == "a++":    guard(a, "is_int") # notice how in Python, the type is unknown, but after being interpreted by RPython, the type is known    guard(a, "> 0")    int_add(a, 1)

Если я запущу этот горячий цикл:

a = 0a++a++

Трассы могут выглядеть так:

# Trace from numerous logs of the hot loopa = alloc(0) # guards can go awaya = int_add(a, 1)a = int_add(a, 2)# optimize trace to be compileda = alloc(2) # the section of code that executes this trace _is_ the compiled code

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

for line in code:  if traces.is_compiled(line):    run_compiled(traces.compiled(line))    continue  elif traces.is_optimized(line):    compile(traces.optimized(line))    continue  elif line == "a = 0"  # ....

Введение в JVM


Я четыре месяца писал на языке TruffleRuby на основе Graal, и влюбился в него.

Hotspot (названа так потому, что ищет горячие точки) это виртуальная машина, поставляемая со стандартными инсталляциями Java. В ней содержится несколько компиляторов для реализации многоуровневого компилирования. Кодовая база Hotspot в 250 000 строк открыта, в ней есть три сборщика мусора. Виртуалка отлично справляется с JIT-компиляцией, в некоторых бенчмарках она работает не хуже C++ impls (по этому поводу столько сра споров, погуглите). Хотя Hotspot не делает трассировку, она использует аналогичный подход: интерпретирует, профилирует, а затем компилирует. У этого подхода нет своего названия, он ближе всего к JIT на основе методов (оптимизация по методам), или к многоуровневому JIT.

Используемые в Hotspot стратегии вдохновили многих авторов последующих JIT-компиляторов, структур языковых виртуальных машин, и особенно Javascript-движков. Также Hotspot породила волну таких JVM-языков, как Scala, Kotlin, JRuby и Jython. JRuby и Jython забавные реализации Ruby и Python, которые компилируют исходный код в JVM-байткод, который потом исполняет Hotspot. Все эти проекты относительно успешно ускоряют языки Python и Ruby (Ruby больше, чем Python) без реализации всего инструментария, как в случае с Pypy. Ещё Hotspot уникальна тем, что она является JIT для менее динамических языков (хотя технически она представляет собой JIT для JVM-байткода, а не для Java).


GraalVM это JavaVM с частью кода на Java. Она может исполнять любой JVM-язык (Java, Scala, Kotlin и т.д.). На также поддерживает Native Image, чтобы работать с AOT-компилированным кодом через Substrate VM. Значительная доля Scala-сервисов Twitter работает на Graal, что говорит о качестве виртуальной машины, и кое в чём она лучше JVM, хотя и написана на Java.

И это ещё не всё! GraalVM также предоставляет Truffle: фреймворк для реализации языков с помощью создания AST-интерпретаторов (Abstract Syntax Tree). В Truffle нет явного шага, когда создается JVM-байткод, как в обычном языке JVM. Cкорее, Truffle просто воспользуется интерпретатором и будет общаться с Graal для создания машинного кода непосредственно с профилированием и так называемой частичной оценкой. Рассмотрение частичной оценки выходит за рамки статьи, если вкратце: этот метод исповедует философию метатрассировки напиши интерпретатор, и получи компилятор бесплатно!, но подходит к ней иначе.

TruffleJS Truffle-реализация Javascript, которая опережает движок V8 по ряду показателей, что действительно впечатляет, потому что V8 разрабатывался на много лет дольше, Google вложила в него кучу денег и сил, над ним работали офигенные специалисты. TruffleJS не лучше V8 (или любого другого JS-движка) по большинству критериев, но это знак надежды для Graal.

Неожиданно отличные стратегии JIT-компилирования


Интерпретирование C


У JIT-реализаций часто возникает проблема с поддержкой С-расширений. Стандартные интерпретаторы, такие как Lua, Python, Ruby и PHP, имеют API для С, что позволяет пользователям собирать на этом языке пакеты, значительно ускоряя исполнение. Многие пакеты написаны на С, например, numpy, функции стандартной библиотеки вроде rand. Все эти С-расширения крайне важны для того, чтобы интерпретируемые языки работали быстро.

По ряду причин поддерживать C-расширения трудно. Самая очевидная причина в том, что API разработан с учётом особенностей внутренней реализации. Более того, поддерживать С-расширения легче, когда интерпретатор написан на С, так что JRuby не может поддерживать С-расширения, но у него есть API для Java-расширений. В Pypy недавно появилась бета-версия поддержки С-расширений, хотя в её работоспособности я не уверен в связи с законом Хайрама. LuaJIT поддерживает С-расширения, в том числе и дополнительные фичи в своих С-расширениях (LuaJIT просто офигенная штука!)

Graal решает эту проблему с помощью Sulong, движка, который на GraalVM исполняет LLVM-байткод, преобразуя его в язык Truffle. LLVM это набор инструментов, и нам достаточно знать, что С можно скомпилировать в LLVM-байткод (у Julia тоже есть LLVM-бэкенд!). Это странно, но решение заключается в том, чтобы взять хороший компилируемые язык с более чем сорокалетней историей, и интерпретировать его! Конечно, он работает совсем не так быстро, как правильно скомпилированный С, но зато мы получаем несколько преимуществ.


LLVM-байткод уже довольно низкоуровневый, то есть применять JIT к этому промежуточному представлению не так неэффективно, как к С. Часть расходов нам возмещается тем, что байткод можно оптимизировать вместе с остальной частью Ruby-программы, а скомпилированную С-программу мы оптимизировать не сможем. Все эти удаления фрагментов памяти, инлайнинги, удаления мёртвых фрагментов кода и прочее можно применять к коду на С и Ruby, вместо того, чтобы вызывать бинарник на С из Ruby-кода. По некоторым параметрам С-расширения TruffleRuby работают быстрее С-расширений CRuby.

Чтобы эта система работала, вам следует знать, что Truffle является полностью независимым от языка, и накладные расходы на переключение между С, Java или любым другим языком внутри Graal будут минимальны.

Способность Graal работать с Sulong является частью их полиглот-возможностей, что обеспечивает высокую взаимозаменяемость языков. Это не только хорошо для компилятора, но ещё и доказывает возможность легко использовать несколько языков в одном приложении.

Вернёмся к интерпретируемому коду, он быстрее


Мы знаем, что JIT содержат в себе интерпретатор и компилятор, и что для ускорения работы они переходят от интерпретатора к компилятору. Pypy создаёт мостики для обратного пути, хотя с точки зрения Graal и Hotspot это деоптимизация. Мы говорим не о совершенно разных понятиях, но под деоптимизацией понимается возврат к интерпретатору в качестве сознательной оптимизации, а не решение неизбежностей динамического языка. Hotspot и Graal активно используют деоптимизацию, особенно Graal, потому что у разработчиков есть жёсткий контроль над компиляцией, и им нужно ещё больше контроля над компилированием ради оптимизаций (по сравнению, скажем, с Pypy). Также деоптимизация используется в JS-движках, о чём я буду много рассказывать, поскольку от неё зависит работа JavaScript в Chrome и Node.js.

Чтобы быстро применить деоптимизацию, важно убедиться в максимально быстром переключении между компилятором и интерпретатором. При самой наивной реализации интерпретатор придётся догонять компилятор, чтобы выполнить деоптимизацию. Дополнительные сложности связаны с деоптимизацией асинхронных потоков. Graal создаёт набор фреймов и сопоставляет его со сгенерированным кодом, чтобы вернуться к интерпретатору. С помощью безопасных состояний (safepoints) можно сделать так, что Java-поток встанет на паузу и скажет: Привет, сборщик мусора, мне нужно остановиться?, так что обработка потоков не требует больших накладных расходов. Получилось довольно грубо, но работает достаточно быстро, чтобы деоптимизация оказалась хорошей стратегией.


Аналогично примеру с мостиками в Pypy, деоптимизировать можно и партизанский патчинг (monkey patching) функций. Получается элегантнее, поскольку мы добавляем деоптимизирующий код не тогда, когда защитник терпит неудачу, а когда применяется партизанский патчинг.

Отличный пример JIT-деоптимизации: переполнение преобразования (conversion overflow), это неофициальный термин. Речь о ситуации, когда внутренне представлен/выделен определённый тип (например, int32), но его нужно преобразовать в int64. TruffleRuby делает это с помощью деоптимизаций, как и V8.

Скажем, если в Ruby задать var = 0, вы получите int32 (в Ruby это называется Fixnum и Bignum, но я буду использовать обозначения int32 и int64). Выполняя операцию с var, вам нужно проверить, не возникает ли переполнения значения. Но одно дело проверить, а компилировать код, который обрабатывает переполнения, получается дорого, особенно учитывая частоту числовых операций.

Даже не посмотрев на скомпилированные инструкции, можно увидеть, как как эта деоптимизация уменьшает объём кода.

int a, b;int sum = a + b;if (overflowed) {  long bigSum = a + b;  return bigSum;} else {  return sum;}int a, b;int sum = a + b;if (overflowed) {  Deoptimize!}

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

WET-код быстрый код. Инлайнинг и OSR


function foo(a, b) { return a + b;}for (var i = 0; i < 1000000; i++) { foo(i, i + 1);}foo(1, 2);

В V8 деоптимизируются даже такие тривиальности, как эти триггеры! С помощью опций вроде --trace-deopt и --trace-opt вы можете собрать много информации о JIT, а также модифицировать поведение. В Graal есть очень полезные инструменты, но я буду пользоваться V8, потому что он у многих установлен.

Деоптимизацию запускает последняя строка (foo(1, 2)), что вызывает недоумение, ведь именно этот вызов сделан в цикле! Мы получим сообщение Insufficient type feedback for call (полный список причин деоптимизации лежит здесь, и в нём есть забавная причина no reason). В результате создаётся фрейм ввода, отображающий литералы 1 и 2.

Так зачем деоптимизировать? V8 достаточно умён, чтобы выполнить приведение типов: когда i относится к типу integer, литералы передаются тоже как integer.

Чтобы разобраться в этом, давайте заменим последнюю строку на foo(i, i +1). Но деоптимизация всё равно применяется, только в этот раз сообщение другое: Insufficient type feedback for binary operation. ПОЧЕМУ?! Ведь это точно такая же операция, которая исполняется в цикле, с теми же переменными!

Ответ, друг мой, кроется в замещении в стеке (on-stack replacement, OSR). Инлайнинг это мощная оптимизация компилятора (не только JIT), при которой функции перестают быть функциями, а содержимое передаётся в места вызова. JIT-компиляторы для увеличения скорости могут инлайнить с помощью изменения кода в ходе исполнения (компилируемые языки могут инлайнить только статически).

// partial output from printing inlining details[compiling method 0x04a0439f3751 <JSFunction (sfi = 0x4a06ab56121)> using TurboFan OSR]0x04a06ab561e9 <SharedFunctionInfo foo>: IsInlineable? trueInlining small function(s) at call site #49:JSCall

Так что V8 скомпилирует foo, определит, что её можно инлайнить, и инлайнит с помощью OSR. Однако движок делает это только для кода внутри цикла, потому что это горячий путь, а последняя строка ещё отсутствует в интерпретаторе на момент инлайнинга. Поэтому у V8 пока нет достаточно обратной связи о типе функции foo, ведь не она используется в цикле, а её инлайненная версия. Если применить --no-use-osr, тогда деоптимизации не будет, вне зависимости от того, что мы передадим, литерал или i. Однако без инлайнинга даже ничтожные миллион итераций будут работать заметно медленнее. JIT-компиляторы действительно воплощают принцип решений нет, только компромиссы. Деоптимизации дороги, но они не идут в сравнение со стоимостью поиска методов и инлайнинга, предпочтительного в этом случае.

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


Хотя эта статья посвящена JIT, инлайнинг эффективен и в компилируемых языках. Все LLVM-языки активно применяют инлайнинг, потому что и LLVM тоже будет это делать, хотя Julia инлайнит и без LLVM, это в её природе. JIT могут инлайнить с помощью эвристик, получаемых в ходе исполнения, и способны переключаться между режимами без инлайнинга и с инлайнингом посредством OSR.

Заметка о JIT и LLVM


LLVM предоставляет кучу инструментов, связанных с компилированием. Julia работает с LLVM (обратите внимание, что это большой инструментарий, и каждый язык использует его по-разному), также, как и Rust, Swift и Crystal. Достаточно сказать, что это большой и замечательный проект, который также поддерживает JIT, хотя в LLVM нет значимых встроенных динамических JIT. На четвёртом уровне компилирования JavaScriptCore какое-то время использовался LLVM-бэкенд, но его заменили меньше двух лет назад. С тех пор этот инструментарий не слишком хорошо подходит для динамических JIT, в основном потому, что он не предназначен для работы в условиях динамичности. В Pypy его пытались применить 5-6 раз, но остановились на JSC. При использовании LLVM возможности allocation sinking и перемещения кода (code motion) были ограничены. Также невозможно было использовать мощные JIT-возможности вроде range-inferencing (это как приведение типов, но с известным диапазоном значения). Но куда важнее, что с LLVM на компилирование тратится очень много ресурсов.

А если вместо промежуточного представления на основе инструкций у нас будет большой граф, который модифицирует себя?


Мы поговорили об LLVM-байткоде и о Python/Ruby/Java-байткоде в качестве промежуточного представления. Все они выглядят как некий язык в виде инструкций. В Hotspot, Graal и V8 применяется промежуточное представление Sea of Nodes (появилось в Hotspot), которое является более низкоуровневым AST. Это эффективное представление, потому что значительная часть профилирования основана на представлении об определённом пути, который редко используется (или пересекается в случае какого-то шаблона). Обратите внимание, что эти AST компиляторов отличаются от AST парсеров.

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


В случае с V8 мы применим инструмент D8 с флагом --print-ast. Для Graal это будет --vm.Dgraal.Dump=Truffle:2. На экран будет выводиться текст (отформатированный так, чтобы получался граф). Не знаю, как разработчики V8 генерируют визуальные графы, но в Oracle есть Ideal Graph Visualizer, который применён на предыдущей иллюстрации. У меня не было сил переустанавливать IGV, поэтому я взял графы у Криса Ситона (Chris Seaton), сгенерированные с помощью Seafoam, чьи исходники сейчас закрыты.

Ладно, давайте взглянем на JavaScript AST!

function accumulate(n, a) {  var x = 0;  for (var i = 0; i < n; i++) {    x += a;  }  return x;}accumulate(1, 1)

Этот код я прогнал через d8 --print-ast test.js, хотя нас интересует только функция accumulate. Посмотрите, что я вызвал её только один раз, то есть мне не нужно ждать выполнения компиляции, чтобы получить AST.

Так выглядит AST (я убрал некоторые маловажные строки):

FUNC at 19. NAME "accumulate". PARAMS. . VAR (0x7ff5358156f0) (mode = VAR, assigned = false) "n". . VAR (0x7ff535815798) (mode = VAR, assigned = false) "a". DECLS. . VARIABLE (0x7ff5358156f0) (mode = VAR, assigned = false) "n". . VARIABLE (0x7ff535815798) (mode = VAR, assigned = false) "a". . VARIABLE (0x7ff535815840) (mode = VAR, assigned = true) "x". . VARIABLE (0x7ff535815930) (mode = VAR, assigned = true) "i". BLOCK NOCOMPLETIONS at -1. . EXPRESSION STATEMENT at 38. . . INIT at 38. . . . VAR PROXY local[0] (0x7ff535815840) (mode = VAR, assigned = true) "x". . . . LITERAL 0. FOR at 43. . INIT at -1. . . BLOCK NOCOMPLETIONS at -1. . . . EXPRESSION STATEMENT at 56. . . . . INIT at 56. . . . . . VAR PROXY local[1] (0x7ff535815930) (mode = VAR, assigned = true) "i". . . . . . LITERAL 0. . COND at 61. . . LT at 61. . . . VAR PROXY local[1] (0x7ff535815930) (mode = VAR, assigned = true) "i". . . . VAR PROXY parameter[0] (0x7ff5358156f0) (mode = VAR, assigned = false) "n". . BODY at -1. . . BLOCK at -1. . . . EXPRESSION STATEMENT at 77. . . . . ASSIGN_ADD at 79. . . . . . VAR PROXY local[0] (0x7ff535815840) (mode = VAR, assigned = true) "x". . . . . . VAR PROXY parameter[1] (0x7ff535815798) (mode = VAR, assigned = false) "a". . NEXT at 67. . . EXPRESSION STATEMENT at 67. . . . POST INC at 67. . . . . VAR PROXY local[1] (0x7ff535815930) (mode = VAR, assigned = true) "i". RETURN at 91. . VAR PROXY local[0] (0x7ff535815840) (mode = VAR, assigned = true) "x"

Парсить такое сложно, но это похоже на AST парсера (верно не для всех программ). А следующее AST сгенерировано с помощью Acorn.js

Заметное отличие определения переменных. В AST парсера нет явного определения параметров, а объявление цикла спрятано в ноду ForStatement. В AST уровня компилятора все объявления сгруппированы с адресами и метаданными.

AST компилятора также использует это дурацкое выражение VAR PROXY. AST парсера не может определить связи между именами и переменными (по адресам) из-за поднятия переменных (hoisting), оценки (eval) и прочего. Так что AST компилятора использует переменные PROXY, которые позднее связываются с фактической переменной.

// This chunk is the declarations and the assignment of `x = 0` . DECLS. . VARIABLE (0x7ff5358156f0) (mode = VAR, assigned = false) "n". . VARIABLE (0x7ff535815798) (mode = VAR, assigned = false) "a". . VARIABLE (0x7ff535815840) (mode = VAR, assigned = true) "x". . VARIABLE (0x7ff535815930) (mode = VAR, assigned = true) "i". BLOCK NOCOMPLETIONS at -1. . EXPRESSION STATEMENT at 38. . . INIT at 38. . . . VAR PROXY local[0] (0x7ff535815840) (mode = VAR, assigned = true) "x". . . . LITERAL 0

А так выглядит AST той же программы, полученное с помощью Graal!


Выглядит куда проще. Красным обозначен поток управления, синим поток данных, стрелками направления. Обратите внимание, что хотя этот граф проще, чем AST из V8, это не означает, что Graal лучше упростил программу. Просто он сгенерирован на основе Java, который куда менее динамичен. Тот же граф Graal, сгенерированный из Ruby, будет ближе к первой версии.

Забавно, что AST в Graal будут меняться в зависимости от исполнения кода. Этот граф сгенерирован с отключённым OSR и инлайнигом, при многократном вызове функции со случайными параметрами, чтобы она не была оптимизирована. И дамп снабдит вас целой пачкой графов! В Graal для оптимизации программ применяется специализированное AST (V8 делает аналогичные оптимизации, но не на уровне AST). Когда сохраняешь в Graal графы, то получаешь больше десяти схем с разными уровнями оптимизации. При перезаписи нод они заменяют себя (специализируют) другими нодами.

Вышеприведённый граф прекрасный пример специализации при динамически типизируемом языке (картинка взята из One VM to Rule Them All, 2013). Причина существования этого процесса тесно связана с тем, как работает частичная оценка всё дело в специализации.

Ура JIT скомпилировал код! Давай скомпилируем снова! И снова!


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

Hotspot это многоуровневый JIT с двумя компиляторами: C1 и C2. C1 выполняет быструю компиляцию и запускает код, затем проводит полное профилирование, чтобы получить скомпилированный с помощью С2 код. Это может помочь решить многие проблемы с прогревом. Не оптимизированный скомпилированный код всё-равно быстрее интерпретации. Кроме того, С1 и С2 компилируют не весь код. Если функция выглядит достаточно просто, с большой вероятностью С2 нам не поможет и даже не будет запускаться (ещё и сэкономим время на профилировании!). Если С1 занят компилированием, тогда профилирование может продолжиться, работа С1 будет прервана и запущено компилирование с помощью С2.


В JavaScript Core уровней ещё больше! П сути, там три JIT. Интерпретатор JSC выполняет лёгкое профилирование, затем переходит к Baseline JIT, затем к DFG (Data Flow Graph) JIT, и наконец к FTL (Faster than Light) JIT. С таким количеством уровней смысл деоптимизации больше не ограничивается переходом от компилятора к интерпретатору, деоптимизация может выполняться начиная с DFG и заканчивая Baseline JIT (это не так в случае Hotspot C2->C1). Все деоптимизации и переходы на следующий уровень выполняются с помощью OSR (замещения в стеке).

Baseline JIT подключается примерно после 100 исполнений, а DFG JIT примерно после 1000 (с некоторыми исключениями). Это означает, что JIT получает скомпилированный код гораздо быстрее, чем тот же Pypy (у которого это занимает около 3000 исполнений). Многоуровневость позволяет JIT пытаться соотнести длительность исполнения кода с длительностью его оптимизации. Есть куча уловок, какой вид оптимизации (инлайнинг, приведение типов и т.д.) исполнять на каждом из уровней, и поэтому такая стратегия является оптимальной.

Полезные источники


Подробнее..

Перевод Приёмы и хитрости начинающего Java-программиста

05.10.2020 10:14:40 | Автор: admin

Небольшая коллекция практик, трюков и подсказок, с помощью которых вы сэкономите своё время при изучении Java и написании кода на этом языке программирования. Перевод статьиTop 25 Java Tricks, Tips, and Best Practices от специалистов Рексофт.

Организация работы

Чистый код

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

  • Правило 10-50-500. В одном пакете не может быть более 10 классов. Каждый метод должен быть короче 50 строк кода, а каждый класс короче 500 строк.

  • SOLID принципы.

  • Использование паттернов проектирования.

Работа с ошибками

Stack Trace (Трассировка стека)

Выявление ошибок это, пожалуй, самая трудоемкая часть процесса разработки на Java. Трассировка стека позволяет вам точно отслеживать, где именно в проекте возникла ошибка или исключение (exception).

import java.io.*;Exception e = ;java.io.StringWriter sw = new java.io.StringWriter();e.printStackTrace(new java.io.PrintWriter(sw));String trace = sw.getBuffer().toString();

NullPointerException

Исключения, возникающие из-заnullзначений (NullPointerException), довольно часто появляются при попытке вызвать метод у несуществующего объекта.

Возьмем для примера следующий код:

int noOfStudents = school.listStudents().count;private int getListOfStudents(File[] files) {if (files == null)  throw new NullPointerException("File list cannot be null");}

Примечание переводчика: а вот пример от меня как переводчика материала:

String anyString = null;if (anyString.equals("some string")) { // здесь будет null pointer exception, т.к. anyString == null// ...}

Дата и Время

System.currentTimeMillis или System.nanoTime?

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

МетодSystem.currentTimeMillis()возвращает текущее количество миллисекунд с начала эры Unix в формате Long. Его точность составляет от 1 до 15 тысячных долей секунды в зависимости от системы.

long startTime = System.currentTimeMillis();long estimatedTime = System.currentTimeMillis() - startTime;

МетодSystem.nanoTime()имеет точность до одной миллионной секунды (наносекунды) и возвращает текущее значение наиболее точного доступного системного таймера.

long startTime = System.nanoTime();long estimatedTime = System.nanoTime() - startTime;

Таким образом, методSystem.currentTimeMillis()лучше применять для отображения и синхронизации абсолютного времени, аSystem.nanoTime()для измерения относительных интервалов времени.

Валидация Даты из строки

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

package net.viralpatel.java;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.ArrayList;import java.util.Date;import java.util.List;public class DateUtil {// List of all date formats that we want to parse.// Add your own format here.private static List; dateFormats = new ArrayList() {{add(new SimpleDateFormat("M/dd/yyyy"));add(new SimpleDateFormat("dd.M.yyyy"));add(new SimpleDateFormat("M/dd/yyyy hh:mm:ss a"));add(new SimpleDateFormat("dd.M.yyyy hh:mm:ss a"));add(new SimpleDateFormat("dd.MMM.yyyy"));add(new SimpleDateFormat("dd-MMM-yyyy"));}};/** * Convert String with various formats into java.util.Date *  * @param input *            Date as a string * @return java.util.Date object if input string is parsed  * successfully else returns null */public static Date convertToDate(String input) {Date date = null;if(null == input) {return null;}for (SimpleDateFormat format : dateFormats) {try {format.setLenient(false);date = format.parse(input);} catch (ParseException e) {//Shhh.. try other formats}if (date != null) {break;}}return date;}}

Пример его использования:

package net.viralpatel.java;public class TestDateUtil {public static void main(String[] args) {System.out.println("10/14/2012" + " = " + DateUtil.convertToDate("10/14/2012"));System.out.println("10-Jan-2012" + " = " + DateUtil.convertToDate("10-Jan-2012"));System.out.println("01.03.2002" + " = " + DateUtil.convertToDate("01.03.2002"));System.out.println("12/03/2010" + " = " + DateUtil.convertToDate("12/03/2010"));System.out.println("19.Feb.2011" + " = " + DateUtil.convertToDate("19.Feb.2011" ));System.out.println("4/20/2012" + " = " + DateUtil.convertToDate("4/20/2012"));System.out.println("some string" + " = " + DateUtil.convertToDate("some string"));System.out.println("123456" + " = " + DateUtil.convertToDate("123456"));System.out.println("null" + " = " + DateUtil.convertToDate(null));}}

Результат:

10/14/2012 = Sun Oct 14 00:00:00 CEST 201210-Jan-2012 = Tue Jan 10 00:00:00 CET 201201.03.2002 = Fri Mar 01 00:00:00 CET 200212/03/2010 = Fri Dec 03 00:00:00 CET 201019.Feb.2011 = Sat Feb 19 00:00:00 CET 20114/20/2012 = Fri Apr 20 00:00:00 CEST 2012some string = null123456 = nullnull = null

Строки

Оптимизация строки

Для конкатенации (сложения) строк в Java используется оператор +, для примера, в циклеforновый объект может создаваться для каждой новой строки, что приводит к потере памяти и увеличению времени работы программы.

Необходимо избегать создания Java строк через конструктор, пример:

// медленное создание строкиString bad = new String ("Yet another string object");// быстрое создание строкиString good = "Yet another string object"

Одинарные и двойные кавычки

Что ты ожидаешь в результате выполнения этого кода?

public class Haha {  public static void main(String args[]) {    System.out.print("H" + "a");    System.out.print('H' + 'a');  }}

Казалось бы, строка должна возвращать HaHa, но на самом деле это будет Ha169.

Двойные кавычки обрабатывают символы как строки, но одинарные кавычки ведут себя иначе. Они преобразуют символьные операнды ('H'и'a') в целые значения посредством расширения примитивных типов получается 169.

Математика

Float или Double?

Программисты часто не могут выбрать необходимую точность для чисел с плавающей запятой. Float требует всего 4 байта, но имеет только 7 значащих цифр, а Double в два раза точнее (15 цифр), но в два раза прожорливее.

Фактически, большинство процессоров могут одинаково эффективно работать как с Float, так и с Double, поэтому воспользуйтесь рекомендацией Бьорна Страуструпа (автор языка С++):

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

Проверка на нечетность

Можно ли использовать этот код для точного определения нечетного числа?

public boolean oddOrNot(int num) {  return num % 2 == 1;}

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

public boolean oddOrNot(int num) {  return (num & 1) != 0;}

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

Возведение в степень

Возвести число в степень можно двумя способами:

  1. простое умножение;

  2. используя методMath.pow()(двойное основание, двойной показатель степени).

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

double result = Math.pow(625, 0.5); // 25.0

Простое умножение в Java работает в 300-600 раз эффективнее, кроме того, его можно дополнительно оптимизировать:

double square = double a * double a;  double cube = double a * double a * double a; // не оптимизированоdouble cube = double a * double square;  // оптимизировано double quad = double a * double a * double a * double a; // не оптимизированоdouble quad = double square * double square; // оптимизировано

JIT оптимизация

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

В качестве примера рассмотрим две простые операции:

// 1n += 2 * i * i; // 2n += 2 * (i * i);

Давайте измерим время выполнения каждого из них:

// 1long startTime1 = System.nanoTime(); int n1 = 0; for (int i = 0; i < 1000000000; i++) {   n1 += 2 * i * i; } double resultTime1 = (double)(System.nanoTime() - startTime1) / 1000000000;System.out.println(resultTime1 + " s");  // 2long startTime2 = System.nanoTime(); int n2 = 0; for (int i = 0; i < 1000000000; i++) {   n2 += 2 * (i * i); } double resultTime2 = (double)(System.nanoTime() - startTime2) / 1000000000;System.out.println(resultTime2 + " s");

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

|`2*(i*i)`| `2*i*i`||---------|--------||0.5183738 | 0.6246434||0.5298337 | 0.6049722||0.5308647 | 0.6603363||0.5133458 | 0.6243328||0.5003011 | 0.6541802||0.5366181 | 0.6312638||0.515149  | 0.6241105||0.5237389 | 0.627815 ||0.5249942 | 0.6114252||0.5641624 | 0.6781033||0.538412  | 0.6393969||0.5466744 | 0.6608845||0.531159  | 0.6201077||0.5048032 | 0.6511559||0.5232789 | 0.6544526|

Схема очевидна: группировка переменных в круглые скобки ускоряет работу программы. Это связано с генерацией более эффективного байт-кода при умножении одинаковых значений.

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

Структуры данных

Комбинирование хеш-таблиц

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

import java.util.*;Map m1 = ;Map m2 = ;m2.putAll(m1); // добавить к m2 все элементы из m1

Array или ArrayList?

Выбор междуArrayиArrayListзависит от специфики задачи Java, которую вы хотите решить. Запомните следующие особенности этих типов:

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

  • Массивы Java работают намного быстрее, а вArrayListнамного проще добавлять и удалять элементы.

  • При работе сArrayскорее всего возникнет ошибкаArrayIndexOutOfBoundsException.

  • ArrayList может быть только одномерным, когда массивы Java могут быть многомерными.

import java.util.*; public class ArrayVsArrayList {  public static void main(String[] args) {    // создаем массив    int[] myArray = new int[6];     // ссылаемся на несуществующий индекс    myArray[7]= 10; // ArrayIndexOutOfBoundsException     // создаем ArrayList    List myArrayList = new ArrayList<>();     // простое добавление и удаление элементов    myArrayList.add(1);    myArrayList.add(2);    myArrayList.add(3);    myArrayList.add(4);    myArrayList.add(5);    myArrayList.remove(0);     // перебор элементов ArrayList     for(int i = 0; i < myArrayList.size(); i++) {      System.out.println("Element: " + myArrayList.get(i));    }     //  многомерный массив    int[][][] multiArray = new int[3][3][3];   }}

JSON

Сериализация и Десериализация

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

Примечание переводчика: для использования JSON из примера необходимо подключить библиотекуJSON Simple.

Вы можете сериализовать данные следующим образом:

import org.json.simple.JSONObject;import org.json.simple.JSONArray; public class JsonEncodeDemo {  public static void main(String[] args) {    JSONObject obj = new JSONObject();    obj.put("Novel Name", "Godaan");    obj.put("Author", "Munshi Premchand");     JSONArray novelDetails = new JSONArray();    novelDetails.add("Language: Hindi");    novelDetails.add("Year of Publication: 1936");    novelDetails.add("Publisher: Lokmanya Press");            obj.put("Novel Details", novelDetails);      System.out.print(obj);    }}

Получается следующая строка JSON:

{"Novel Name":"Godaan","Novel Details":["Language: Hindi","Year of Publication: 1936","Publisher: Lokmanya Press"],"Author":"Munshi Premchand"}

Десериализация в Java выглядит так:

import java.io.FileNotFoundException;import java.io.FileReader;import java.io.IOException;import java.util.Iterator; import org.json.simple.JSONArray;import org.json.simple.JSONObject;import org.json.simple.parser.JSONParser;import org.json.simple.parser.ParseException; public class JsonParseTest {  private static final String filePath = "//home//user//Documents//jsonDemoFile.json";      public static void main(String[] args) {    try {      // читаем json файл      FileReader reader = new FileReader(filePath);      JSONParser jsonParser = new JSONParser();      JSONObject jsonObject = (JSONObject)jsonParser.parse(reader);                  // берем данные из json объекта      Long id =  (Long) jsonObject.get("id");      System.out.println("The id is: " + id);                  String type = (String) jsonObject.get("type");      System.out.println("The type is: " + type);       String name = (String) jsonObject.get("name");      System.out.println("The name is: " + name);       Double ppu =  (Double) jsonObject.get("ppu");      System.out.println("The PPU is: " + ppu);       // извлекаем массив              System.out.println("Batters:");      JSONArray batterArray= (JSONArray) jsonObject.get("batters");      Iterator i = batterArray.iterator();            // проходимся по всем элементам массива      while (i.hasNext()) {        JSONObject innerObj = (JSONObject) i.next();        System.out.println("ID "+ innerObj.get("id") +             " type " + innerObj.get("type"));      }       System.out.println("Topping:");      JSONArray toppingArray= (JSONArray) jsonObject.get("topping");      Iterator j = toppingArray.iterator();      while (j.hasNext()) {        JSONObject innerObj = (JSONObject) j.next();        System.out.println("ID "+ innerObj.get("id") +             " type " + innerObj.get("type"));      }    } catch (FileNotFoundException|IOException|ParseException|NullPointerException ex) {      ex.printStackTrace();    }  }}

Используемый в примере файл JSON (jsonDemoFile.json):

{  "id": 0001,  "type": "donut",  "name": "Cake",  "ppu": 0.55,  "batters":    [      { "id": 1001, "type": "Regular" },      { "id": 1002, "type": "Chocolate" },      { "id": 1003, "type": "Blueberry" },      { "id": 1004, "type": "Devil's Food" }    ],  "topping":    [      { "id": 5001, "type": "None" },      { "id": 5002, "type": "Glazed" },      { "id": 5005, "type": "Sugar" },      { "id": 5007, "type": "Powdered Sugar" },      { "id": 5006, "type": "Chocolate with Sprinkles" },      { "id": 5003, "type": "Chocolate" },      { "id": 5004, "type": "Maple" }    ]}

Примечание переводчика: в Java проектах очень часто для работы с JSON используют библиотеки Gson от Google или Jackson. Обе библиотеки очень популярны и хорошо поддерживаются. Попробуйте и их.

Ввод и Вывод

FileOutputStream или FileWriter?

Запись файлов в Java осуществляется двумя способами:FileOutputStreamиFileWriter. Какой метод выбрать, зависит от конкретной задачи.

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

File foutput = new File(file_location_string);FileOutputStream fos = new FileOutputStream(foutput);BufferedWriter output = new BufferedWriter(new OutputStreamWriter(fos));output.write("Buffered Content");

УFileWriterдругое призвание: работа с потоками символов. Поэтому, если вы пишете текстовые файлы, выберите этот метод.

FileWriter fstream = new FileWriter(file_location_string);BufferedWriter output = new BufferedWriter(fstream);output.write("Buffered Content");

Производительность и лучшие практики

Пустая коллекция вместо Null

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

import java.util.*public List getLocations() {  List result = someService.getLocationsFromDB();  return result == null ? Collections.emptyList() : result;}

Создание объектов только когда необходимо

Создание объектов одна из самых затратных операций в Java. Лучше всего создавать их только тогда, когда они действительно нужны.

import java.util.ArrayList;import java.util.List; public class Employees {  private List Employees;   public List getEmployees() {    // initialization only if necessary    if(null == Employees) {      Employees = new ArrayList();    }        return Employees;  }}

Deadlocks (Дедлоки)

Взаимная блокировка (Deadlock) потоков может происходить по многим причинам, и полностью защититься от них в Java 8 очень сложно. Чаще всего, это происходит, когда один синхронизируемый объект ожидает ресурсов, которые заблокированы другим синхронизированным объектом.

Вот пример тупика этого потока:

public class DeadlockDemo {  public static Object addLock = new Object();  public static Object subLock = new Object();   public static void main(String args[]) {    MyAdditionThread add = new MyAdditionThread();    MySubtractionThread sub = new MySubtractionThread();    add.start();    sub.start();  }   private static class MyAdditionThread extends Thread {    public void run() {      synchronized (addLock) {        int a = 10, b = 3;        int c = a + b;        System.out.println("Addition Thread: " + c);        System.out.println("Holding First Lock...");        try { Thread.sleep(10); }        catch (InterruptedException e) {}        System.out.println("Addition Thread: Waiting for AddLock...");        synchronized (subLock) {           System.out.println("Threads: Holding Add and Sub Locks...");        }      }    }  }   private static class MySubtractionThread extends Thread {    public void run() {      synchronized (subLock) {        int a = 10, b = 3;        int c = a - b;        System.out.println("Subtraction Thread: " + c);        System.out.println("Holding Second Lock...");        try { Thread.sleep(10); }        catch (InterruptedException e) {}        System.out.println("Subtraction  Thread: Waiting for SubLock...");        synchronized (addLock) {           System.out.println("Threads: Holding Add and Sub Locks...");        }      }    }  }}

Результат этой программы:

=====Addition Thread: 13Subtraction Thread: 7Holding First Lock...Holding Second Lock...Addition Thread: Waiting for AddLock...Subtraction  Thread: Waiting for SubLock...

Взаимоблокировок можно избежать, изменив порядок вызова потоков:

private static class MySubtractionThread extends Thread {  public void run() {    synchronized (addLock) {      int a = 10, b = 3;      int c = a - b;      System.out.println("Subtraction Thread: " + c);      System.out.println("Holding Second Lock...");      try { Thread.sleep(10); }      catch (InterruptedException e) {}      System.out.println("Subtraction  Thread: Waiting for SubLock...");      synchronized (subLock) {        System.out.println("Threads: Holding Add and Sub Locks...");      }    }  }}

Вывод:

=====Addition Thread: 13Holding First Lock...Addition Thread: Waiting for AddLock...Threads: Holding Add and Sub Locks...Subtraction Thread: 7Holding Second Lock...Subtraction  Thread: Waiting for SubLock...Threads: Holding Add and Sub Locks...

Резервирование памяти

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

export JAVA_OPTS="$JAVA_OPTS -Xms5000m -Xmx6000m -XX:PermSize=1024m -XX:MaxPermSize=2048m"
  • Xms минимальный пул выделения памяти;

  • Xmx максимальный пул выделения памяти;

  • XX: PermSize начальный размер, который будет выделен при запуске JVM;

  • XX: MaxPermSize максимальный размер, который можно выделить при запуске JVM.

Примечание переводчика: PermSize и MaxPermSize, начиная с Java 8 уже больше не используются.

Решение распространенных проблем

Содержимое директории

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

import java.io.*; public class ListContents {  public static void main(String[] args) {    File file = new File("//home//user//Documents/");    String[] files = file.list();     System.out.println("Listing contents of " + file.getPath());    for(int i=0 ; i < files.length ; i++) {      System.out.println(files[i]);    }  }}

Выполнение консольных команд

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

Например, давайте попробуем открыть файл PDF через терминал Java (на Linuxe):

public class ShellCommandExec {  public static void main(String[] args) {    String gnomeOpenCommand = "gnome-open //home//user//Documents//MyDoc.pdf";     try {      Runtime rt = Runtime.getRuntime();      Process processObj = rt.exec(gnomeOpenCommand);       InputStream stdin = processObj.getErrorStream();      InputStreamReader isr = new InputStreamReader(stdin);      BufferedReader br = new BufferedReader(isr);       String myoutput = "";       while ((myoutput=br.readLine()) != null) {        myoutput = myoutput+"\n";      }      System.out.println(myoutput);    } catch (Exception e) {      e.printStackTrace();    }  }

Воспроизведение звуков

Звук важный компонент многих десктопных приложений и игр. Язык программирования Java предоставляет средства для работы с ним.

import java.io.*;import java.net.URL;import javax.sound.sampled.*;import javax.swing.*; public class PlaySoundDemo extends JFrame {    // constructor   public playSoundDemo() {      this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);      this.setTitle("Play Sound Demo");      this.setSize(300, 200);      this.setVisible(true);       try {         URL url = this.getClass().getResource("MyAudio.wav");         AudioInputStream audioIn = AudioSystem.getAudioInputStream(url);         Clip clip = AudioSystem.getClip();         clip.open(audioIn);         clip.start();      } catch (UnsupportedAudioFileException|IOException|LineUnavailableException e) {         e.printStackTrace();      }   }    public static void main(String[] args) {      new PlaySoundDemo();   }}

Отправка email

Отправить электронную почту на Java очень просто. Вам просто нужно установитьJava Mailи указать путь к нему в пути к классам проекта.

import java.util.*;import javax.mail.*;import javax.mail.internet.*; public class SendEmail {  public static void main(String [] args) {        String to = "recipient@gmail.com";    String from = "sender@gmail.com";    String host = "localhost";     Properties properties = System.getProperties();    properties.setProperty("mail.smtp.host", host);    Session session = Session.getDefaultInstance(properties);     try{      MimeMessage message = new MimeMessage(session);      message.setFrom(new InternetAddress(from));       message.addRecipient(Message.RecipientType.TO,new InternetAddress(to));       message.setSubject("My Email Subject");      message.setText("My Message Body");      Transport.send(message);      System.out.println("Sent successfully!");    } catch (MessagingException ex) {      ex.printStackTrace();    }  }}

Получение координат курсора

Чтобы фиксировать события мыши, вам необходимо реализовать интерфейсMouseMotionListener. Когда курсор попадает в определенную область, срабатывает обработчик событияmouseMoved, из которого вы можете получить точные координаты (используя Swing для UI)

import java.awt.event.*;import javax.swing.*; public class MouseCaptureDemo extends JFrame implements MouseMotionListener {  public JLabel mouseHoverStatus;   public static void main(String[] args) {    new MouseCaptureDemo();  }   MouseCaptureDemo() {    setSize(500, 500);    setTitle("Frame displaying Coordinates of Mouse Motion");     mouseHoverStatus = new JLabel("No Mouse Hover Detected.", JLabel.CENTER);    add(mouseHoverStatus);    addMouseMotionListener(this);    setVisible(true);  }   public void mouseMoved(MouseEvent e) {    mouseHoverStatus.setText("Mouse Cursor Coordinates => X:"+e.getX()+" | Y:"+e.getY());  }   public void mouseDragged(MouseEvent e) {  }}
Подробнее..

29 ноября в Москве конференция по PHP в России будет офлайн

16.10.2020 14:10:19 | Автор: admin
Пандемия повлияла на все бизнес-процессы, мы долго были в онлайне. Но 29 ноября PHP-разработчики смогут наконец встретиться офлайн в тёплой атмосфере, увидеть лучших спикеров PHP-вселенной, и задав им вопросы, разобрать актуальные кейсы и обсудить проблемы. PHP Russia 2020 пройдёт в Москве в гостинице Radisson Slavyanskaya. Приходите, если хотите получить ускорение и направление в развитии плюс набраться новых идей для своих проектов!

Александр Макаров расскажет о предстоящих активностях на конференции, о некоторых интерактивах и других нюансах. Александр эксперт в PHP, лидер фреймворка Yii, соавтор Yii 2 и представитель Yii в PHP-FIG. Кроме разработки фреймворка успел поработать в разных компаниях, таких как Skyeng, Wrike и Stay.com и перепробовать в бою целые поколения разных технологий.

Мы расспросили Александра как главу программного комитета по PHP Russia 2020 обо всех активностях и интересностях встречи.



Саша, что нас ждет на первой в этом году оффлайновой конференции?
Будет много интересных докладов :)

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

Мы отобрали доклады, в которых раскроем разные способы писать код: микросервисы, параллельная обработка, долгоживущие процессы. Покажем, как ещё вы можете использовать PHP, а с чем не стоит связываться, и разберем, как лучше оформить решения распространенных практических проблем. Поговорим, как организовать системную работу с legacy и справиться с техдолгом. Покажем, как legacy-проекты продолжают развиваться без радикального переписывания кода.

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

Будет, естественно, информация про PHP 8, и затронем интересную тему: она не совсем по PHP, а про написание плагинов для нашей любимой IDE PhpStorm.

Как и в прошлые разы, неплохо представлены такие лидеры PHP-разработки, как BADOO, Skyeng, ManyChat, Onliner, Lamoda, Авито и SuperJob. Они работают не только на PHP, но PHP это самое главное в их стеке. Необязательно все выступят с докладами, но представители этих компаний будут все.

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

Что необычного будет в этот раз?
Будет необычный формат с Дмитрием Стоговым из команды самого PHP. Он сделал JIT PHP.
Дмитрий приезжает не с докладом, а пообщаться в свободной форме с сообществом. Будет шанс задать ему любые вопросы не только про PHP 8 (и вообще про PHP), но и про остальную разработку, и даже чем он занимается в его свободное время. У Дмитрия неисчерпаемое множество тем для разговора, с ним очень интересно, а мы будем модерировать эту сессию вопросов и ответов.

Ещё в прошлый 2019-й офлайн мы попробовали такой замечательный формат как Unconference куда все желающие могли прийти и в блиц-формате рассказать про то, что они делают. Это тогда классно зашло, потому что были такие совершенно неожиданные рассказы, из которых потом получились и opensourse-бибилиотеки, и всякие крутые штуки. На онлайн-митапах, на которых мы сейчас иногда пересекаемся, до сих пор все с теплотой вспоминают эту встречу. Мы решили, что раз так, то этот формат забрасывать не надо, он классный. И мы повторим его в этом году.

Получается, в этом году будет сделан упор на общение, по которому все так соскучились?
Да. Мы решили доклады сделать немного разряженнее, а перерывы побольше. Это к тому же будет безопаснее в плане вирусов. Разумеется, по всем сегодняшним стандартам площадку мы обезопасим по максимуму, и это будет намного безопаснее, чем ездить в метро за счет дезинфекций, за счет проветривания, измерения температуры на входе, санитайзеров. Докладчик в маске будет вряд ли он стоит больше чем за полтора метра от первого ряда, но для остальных маски будут обязательными.

Что будет с конференцией и билетами, если введут карантин и мы все вынужденно останемся дома?
Сейчас мы исходим из того, что короновирус с нами надолго, мы делаем максимальные предосторожности и все-таки встречаемся, потому что жить как-то надо, новую информацию получать надо, и мы готовы её давать, даже несмотря на такие обстоятельства.
Но если вторая волна накроет нас совсем по полной, и запретят все-все-все мероприятия, билеты покупать на конференцию всё равно стоит.

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

То есть, если ничего не будет, то PHP-конференции не уйдет в онлайн?
Да. В прошлый раз она была онлайн, это было сильно, классно, но это показало, что онлайн это другая среда, совершенно другая штука. Классно то, что можно не привозя спикеров из очень дальних уголков, их всё-таки послушать. И просто и замечательно было в том, что в плане накладок ни мы, ни они ничего не теряли.

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

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

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

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

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

То есть мы обсуждаем всем программным комитетом и вместе принимаем решение брать это направление или нет, зайдет или нет, на чем лучше акцентировать внимание, а на чем не стоит, и т.д. По большому счету, это зависит от того, знакома тема всем или эта тема еще неизвестна многим. Так мы задаем спикеру путь, куда копать.

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

За счет прогонов и крутой экспертизы у ПК (они все очень опытные докладчики, которые выступали в том числе и в Европе), получаются очень качественные доклады на выходе, и мы стараемся не опускать планку.

Есть ли шанс, что не очень известный и не очень опытный разработчик найдёт тему, которая зайдет так же, как от более опытных коллег?
Да. Приведу пример.

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

И результаты есть. Мы созванивались с ними 2-3 недели, всех послушали, и действительно есть классные и сильные ребята. Многие темы в этом году не подойдут, потому что они уже закрыты или авторами самих инструментов или кем-то супер-опытным. Но в общем очень достойно всё кое-кого мы включим в основную программу.

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

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

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

Must learn PHP Tools:


PHP Engineer Things to Learn:


На конференции PHP Russia 2020 Александр Макаров выступит с докладом Поговорим про код в рамках лучших практик PHP. Вы узнаете принципы, позволяющие писать код, который ломается меньше. Например, о композиции и как её форсировать. О private по умолчанию и именованных конструкторах. О состоянии и иммутабельности, а также о цепочке вызовов и многом другом.

29 ноября мы встретимся в ламповом инфопространстве, чтобы наконец увидеть друг друга вживую. Здесь можно забронировать билет на PHP Russia 2020. Подключайтесь к telegram-сообществу, чтобы обсудить архитектурные вызовы и любые другие вопросы по PHP.

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

PHP 8 Что нового?

25.11.2020 16:04:07 | Автор: admin

PHP, начиная с 7 версии, кардинально изменился. Код стал куда быстрее и надёжнее, и писать его стало намного приятнее. Но вот, уже релиз 8 версии! Ноябрь 26, 2020 примерно на год раньше, чем обещали сами разработчики. И всё же, не смотря на это, мажорная версия получилась особенно удачной. В этой статье я попытаюсь выложить основные приятные изменения, которые мы должны знать.


1. JIT


Как говорят сами разработчики, они выжали максимум производительности в 7 версии (тем самым сделав PHP наиболее шустрым среди динамических ЯПов). Для подальшего ускорения, без JIT-компилятора не обойтись. Справедливости ради, стоит сказать, что для веб-приложений использование JIT не сильно улучшает скорость обработки запросов (в некоторых случаях скорость будет даже меньше, чем без него). А вот, где нужно выполнять много математических операций там прирост скорости очень даже значительный. Например, теперь можно делать такие безумные вещи, как ИИ на PHP.
Включить JIT можно в настройках opcache в файле php.ini.
Подробнее 1 | Подробнее 2 | Подробнее 3


2. Аннотации/Атрибуты (Attributes)


Все мы помним, как раньше на Symfony код писался на языке комментариев. Очень радует, что такое теперь прекратится, и можно будет использовать подсказки любимой IDE, функция "Find usages", и даже рефакторинг!


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

Было очень много споров о синтаксисе для атрибутов, но приняли Rust-like синтаксис:


#[ORM\Entity]#[ORM\Table("user")]class User{    #[ORM\Id, ORM\Column("integer"), ORM\GeneratedValue]    private $id;    #[ORM\Column("string", ORM\Column::UNIQUE)]    #[Assert\Email(["message" => "The email '{{ value }}' is not a valid email."])]    private $email;}

Подробнее 1 | Атрибуты в Symfony


3. Именованые параметры (Named Arguments)


Если вы когда-либо видели код, где есть булевы параметры, то понимаете насколько он ужасен. Ещё хуже, когда этих параметров 8 штук, 6 из которых имеют значение по-умолчанию, а вам нужно изменить значение последнего параметра.


К примеру, код для использования библиотеки phpamqplib:


$channel->queue_declare($queue, false, true, false, false);// ...$channel->basic_consume($queue, '', false, false, false, false, [$this, 'consume']);

С использованием именованых параметров, код становится намного легче читать:


$channel->queue_declare($queue, durable: true, auto_delete: false);// ...$channel->basic_consume($queue, callback: [$this, 'consume']);

Ещё несколько примеров:


htmlspecialchars($string, default, default, false);// vshtmlspecialchars($string, double_encode: false);

Внимание! Можно также использовать ассоциативные массивы для именованых параметров (и наоборот).


$params = ['start_index' => 0, 'num' => 100, 'value' => 50];$arr = array_fill(...$params);

function test(...$args) { var_dump($args); }test(1, 2, 3, a: 'a', b: 'b');// [1, 2, 3, "a" => "a", "b" => "b"]

Подробнее


4. Оператор безопасного null (Nullsafe operator)


Null сам по себе не очень хорошая штука (даже очень плохая). Когда функция возвращает null, то в каждом месте, где идёт её вызов, программист обязан проверить на null. И это приводит к ужасным последствиям.


$session = Session::find(123);if ($session !== null) {    $user = $session->user;    if ($user !== null) {        $address = $user->getAddress();        if ($address !== null) {            $country = $address->country;        }    }}

По хорошему, должен быть метод Session::findOrFail, который будет кидать исключение в случае отсутствия результата. Но когда эти методы диктует фреймворк, то мы не можем ничего сделать. Единственное, это проверять каждый раз на null либо, где это уместно, использовать ?->.


Да, с оператором nullsafe код станет немного лучше, но всё же это не повод возвращать null.

$country = $session?->user?->getAddress()?->country;

Этот код нельзя назвать чистым, только лишь от части. Для чистого кода, нужно использовать шаблон Null Object, либо выбрасывать exception. Идеальным вариантом было б:


$country = $session->user->getAddress()->country;

Поэтому, если возможно с вашей стороны, никогда не возвращайте null (к Римлянам 12:18).

Также интересным моментом в использовании nullsafe есть то, что при вызове метода с помощью ?->, параметры будут обработаны только если объект не null:


function expensive_function() {    var_dump('will not be executed');}$foo = null;$foo?->bar(expensive_function());

5. Оператор выбора match (Match expression v2)


Для начала покажу код до и после:


$v = 1;switch ($v) {    case 0:        $result = 'Foo';        break;    case 1:        $result = 'Bar';        break;    case 2:        $result = 'Baz';        break;}echo $result; // Bar

VS


$v = 1;echo match ($v) {    0 => 'Foo',    1 => 'Bar',    2 => 'Baz',};  // Bar

Как видим, это очень приятный оператор для выбора значений, который удобно заменяет switch.
Но есть очень важное отчилие switch от match: первый сравнивает нестрого ==, а во втором производится строгое === сравнение.


Наглядный пример различия:


switch ('foo') {    case 0:      $result = "Oh no!\n";      break;    case 'foo':      $result = "This is what I expected\n";      break;}echo $result; // Oh no!

VS


echo match ('foo') {    0 => "Oh no!\n",    'foo' => "This is what I expected\n",}; // This is what I expected

В PHP8 этот пример со switch работает по другому, далее рассмотрим это.

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


$result = match ($x) {    foo() => ...,    $this->bar() => ..., // bar() isn't called if foo() matched with $x    $this->baz => ...,    // etc.};

6. Адекватное приведение строки в число (Saner string to number comparisons)


Проблема


$validValues = ["foo", "bar", "baz"];$value = 0;var_dump(in_array($value, $validValues));// bool(true) ???

Это происходит потому, что при нестрогом == сравнении строки с числом, строка приводится к числу, то-есть, например (int)"foobar" даёт 0.


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


Comparison Before After
0 == "0" true true
0 == "0.0" true true
0 == "foo" true false
0 == "" true false
42 == " 42" true true
42 == "42foo" true false

Стоит отметить, что теперь выражение 0 == "" даёт false. Если у вас из базы пришло значение пустой строки и обрабатывалось как число 0, то теперь это не будет работать. Нужно вручную приводить типы.

Эти изменения относятся ко всем операциям, которые производят нестрогое сравнение:


  • Операторы <=>, ==, !=, >, >=, <, <=.
  • Функции in_array(), array_search(), array_keys() с параметром strict: false (то-есть по-умолчанию).
  • Сотрировочные функции sort(), rsort(), asort(), arsort(), array_multisort() с флагом sort_flags: SORT_REGULAR (то-есть по-умолчанию).

Также, есть специальные значения которые при нестрогом сравнении дают true:


Expression Before After
INF == "INF" false true
-INF == "-INF" false true
NAN == "NAN" false false
INF == "1e1000" true true
-INF == "-1e1000" true true

7. Constructor Property Promotion


Изначально идея позаимствована в языка-брата Hack. Она состоит в том, чтобы упростить инициализацию полей класса в конструкторе.


Вместо прописания полей класа, параметров конструктора, инициализации полей с помощью параметров, можно просто прописать поля параметрами конструктора:


class Point {    public function __construct(        public float $x = 0.0,        public float $y = 0.0,        public float $z = 0.0,    ) {}}

Это эквивалентно:


class Point {    public float $x;    public float $y;    public float $z;    public function __construct(        float $x = 0.0,        float $y = 0.0,        float $z = 0.0,    ) {        $this->x = $x;        $this->y = $y;        $this->z = $z;    }}

С этим всё просто, так как это синтаксический сахар. Но интересный момент возникает при использовании вариативные параметры (их нельзя объявлять таким образом). Для них нужно по-старинке вручную прописать поля и установить их в конструкторе:


class Test extends FooBar {    private array $integers;    public function __construct(        private int $promotedProp,         Bar $bar,        int ...$integers,    ) {        parent::__construct($bar);        $this->integers = $integers;    }}

8. Новые функции для работы со строками (str_contains, str_starts_with, str_ends_with)


Функция str_contains проверяет, содержит ли строка $haystack строку $needle:


str_contains("abc", "a"); // truestr_contains("abc", "d"); // falsestr_contains("abc", "B"); // false // $needle is an empty stringstr_contains("abc", "");  // truestr_contains("", "");     // true

Функция str_starts_with проверяет, начинается ли строка $haystack строкой $needle:


$str = "beginningMiddleEnd";var_dump(str_starts_with($str, "beg")); // truevar_dump(str_starts_with($str, "Beg")); // false

Функция str_ends_with проверяет, кончается ли строка $haystack строкой $needle:


$str = "beginningMiddleEnd";var_dump(str_ends_with($str, "End")); // truevar_dump(str_ends_with($str, "end")); // false

Вариантов mb_str_ends_with, mb_str_starts_with, mb_str_contains нету, так как эти функции уже хорошо работают с мутльтибайтовыми символами.


На самом деле, очень приятно, что наконец-то добавили эти функции. Теперь не нужно писать каждый раз своих костылей с помощью substr, strncmp, strpos.

9. Использование ::class на объектах (Allow ::class on objects)


Раньше, чтобы получить название класса, к которому принадлежит объект, нужно было использовать get_class:


$object = new stdClass;$className = get_class($object); // "stdClass"

Теперь же, можно использовать такую же нотацию, как и ClassName::class:


$object = new stdClass;var_dump($object::class); // "stdClass"

10. Возвращаемый тип static (Static return type)


Тип static был добавлен для более явного указания, что используется позднее статическое связывание (Late Static Binding) при возвращении результата:


class Foo {    public static function createFromWhatever(...$whatever): static {        return new static(...$whatever);    }}

Также, для возвращения $this, стоит указывать static вместо self:


abstract class Bar {    public function doWhatever(): static {        // Do whatever.        return $this;    }}

11. Weak Map


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


Интерфейс класса выглядит следующим образом:


WeakMap implements Countable , ArrayAccess , Iterator {    public __construct ( )    public count ( ) : int    public current ( ) : mixed    public key ( ) : object    public next ( ) : void    public offsetExists ( object $object ) : bool    public offsetGet ( object $object ) : mixed    public offsetSet ( object $object , mixed $value ) : void    public offsetUnset ( object $object ) : void    public rewind ( ) : void    public valid ( ) : bool}

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


class FooBar {    private WeakMap $cache;    public function getSomethingWithCaching(object $obj) {        return $this->cache[$obj] ??= $this->decorated->getSomething($obj);    }    // ...}

Подробнее можно почитать в документации.


12. Убрано возможность использовать левоассоциативный оператор (Deprecate left-associative ternary operator)


Рассмотрим код:


return $a == 1 ? 'one'     : $a == 2 ? 'two'     : $a == 3 ? 'three'     : $a == 4 ? 'four'              : 'other';

Вот как он всегда работал:


$a Result
1 'four'
2 'four'
3 'four'
4 'four'

В 7.4 код как прежде, отрабатывал, но выдавался Deprecated Warning.
Теперь же, в 8 версии, код упадёт с Fatal error.


13. Изменение приоритета оператора конкатенации (Change the precedence of the concatenation operator)


Раньше, приоритет оператора конкатенации . был на равне с + и -, поэтому они исполнялись поочерёдно слева направо, что приводило к ошибкам. Теперь же, его приоритет ниже:


Expression Before Currently
echo "sum: " . $a + $b; echo ("sum: " . $a) + $b; echo "sum :" . ($a + $b);

14. Удалены краткие открывающие php теги


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


Это не касается тега <?=, так как он, начиная с 5.4 всегда доступен.


15. Новый интерфейс Stringable


Объекты, которые реализуют метод __toString, неявно реализуют этот интерфейс. Сделано это в большей мере для гарантии типобезопасности. С приходом union-типов, можно писать string|Stringable, что буквально означает "строка" или "объект, который можно преобразовать в строку". В таком случае, объект будет преобразован в строку только когда уже не будет куда оттягивать.


interface Stringable{    public function __toString(): string;}

Рассмотрим такой код:


class A{    public function __toString(): string     {        return 'hello';    }}function acceptString(string $whatever) {    var_dump($whatever);}acceptString(123.45); // string(6) "123.45"acceptString(new A()); // string(5) "hello"

Здесь функция acceptString принимает строку, но что если нам нужно конкретно объект, что может быть преобразован в строку, а не что-либо иное. Вот тут нам поможет интерфейс Stringable:


function acceptString(Stringable $whatever) {    var_dump($whatever);    var_dump((string)$whatever);}// acceptString(123.45); /*TypeError*/acceptString(new A()); /*object(A)#1 (0) {}string(5) "hello"*/

16. Теперь throw это выражение


Примеры использования:


// This was previously not possible since arrow functions only accept a single expression while throw was a statement.$callable = fn() => throw new Exception();// $value is non-nullable.$value = $nullableValue ?? throw new InvalidArgumentException();// $value is truthy.$value = $falsableValue ?: throw new InvalidArgumentException();// $value is only set if the array is not empty.$value = !empty($array)    ? reset($array)    : throw new InvalidArgumentException();

Подробнее можно почитать здесь.


17. Стабильная сортировка


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


Сюда входят sort, rsort, usort, asort, arsort, uasort, ksort, krsort, uksort, array_multisort, а также соответствующие методы в ArrayObject.


18. Возможньсть опустить переменную исключения (non-capturing catches)


Раньше, даже если переменная исключения не использовалась в блоке catch, её всё равно нужно быто объявлять (и IDE подсвечивала ошибку, что переменная нигде не используется):


try {    changeImportantData();} catch (PermissionException $ex) {    echo "You don't have permission to do this";}

Теперь же, можно опустить переменную, если никакая дополнительная информация не нужна:


try {    changeImportantData();} catch (PermissionException) { // The intention is clear: exception details are irrelevant    echo "You don't have permission to do this";}

19. Обеспечение правильной сигнатуры магических методов (Ensure correct signatures of magic methods):


Когда были добавлены type-hints в php, оставалась возможность непавильно написать сигнатуру для магических методов.
К примеру:


class Test {    public function __isset(string $propertyName): float {        return 123.45;    }}$t = new Test();var_dump(isset($t)); // true

Теперь же, всё жёстко контролируется, и допустить ошибку сложнее.


Foo::__call(string $name, array $arguments): mixed;Foo::__callStatic(string $name, array $arguments): mixed;Foo::__clone(): void;Foo::__debugInfo(): ?array;Foo::__get(string $name): mixed;Foo::__invoke(mixed $arguments): mixed;Foo::__isset(string $name): bool;Foo::__serialize(): array;Foo::__set(string $name, mixed $value): void;Foo::__set_state(array $properties): object;Foo::__sleep(): array;Foo::__unserialize(array $data): void;Foo::__unset(string $name): void;Foo::__wakeup(): void;

20. Включить расширение json по-умолчанию (Always available JSON extension)


Так как функции для работы с json постоянно используются, и нужны чуть ли не в каждом приложении, то было принято решение включить ext-json в PHP по-умолчанию.


21. Более строгие проверки типов при для арифметических и побитовых операторов (Stricter type checks for arithmetic/bitwise operators)


Проблема, которую разработчики здесь решили предоставлена кодом ниже:


var_dump([] % [42]);

Что должен вывести этот код? Здесь непредсказуемое поведение (будет 0). Всё потому, что большинство арифметических операторов не должны применятся на массивах.


Теперь, при использовании операторов +, -, *, /, **, %, <<, >>, &, |, ^, ~, ++, -- будет вызывать исключение TypeError для операндов array, resource и object.


22. Валидация абстрактных методов в трейтах (Validation for abstract trait methods)


До восьмой версии, можно было писать что-то вроде:


trait T {    abstract public function test(int $x);}class C {    use T;    // Allowed, but shouldn't be due to invalid type.    public function test(string $x) {}}

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


trait MyTrait {    abstract private function neededByTheTrait(): string;    public function doSomething() {        return strlen($this->neededByTheTrait());    }}class TraitUser {    use MyTrait;    // This is allowed:    private function neededByTheTrait(): string { }    // This is forbidden (incorrect return type)    private function neededByTheTrait(): stdClass { }    // This is forbidden (non-static changed to static)    private static function neededByTheTrait(): string { }}

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


23. Объединения типов (Union Types 2.0)


Рассмотрим код:


class Number {    /**     * @var int|float $number     */    private $number;    /**     * @param int|float $number     */    public function setNumber($number) {        $this->number = $number;    }    /**     * @return int|float     */    public function getNumber() {        return $this->number;    }}

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


Теперь же, можно прописать тип int|float (или любой другой) явно, чтобы обеспечить корректность работы модуля:


class Number {    private int|float $number;    public function setNumber(int|float $number): void {        $this->number = $number;    }    public function getNumber(): int|float {        return $this->number;    }}

А также, код становится намного чище.
Как вы уже могли заметить, типы-объединения имеют синтаксис T1|T2|... и могут быть использованы во всех местах, где можно прописать type-hints.


Некоторые оговорки:


  • Тип void не может быть частью объединения.
  • Чтобы обозначить отсутствие результата, можно объявить "Nullable union type", который имеет следующий синтаксис: T1|T2|null.
  • Тип null не может быть использован вне объединения. Вместо него стоит использовать void.
  • Существует также псевдотип false, который по историческим причинам уже используется некоторыми функциями в php. С другой стороны, не существует тип true, так как он нигде не использовался ранее.

Типы полей класса инвариантны, и не могут быть изменены при наследовании.
А вот с методами всё немного интересней:


  1. Параметры методов можно расширить, но нельзя сузить.
  2. Возвращаемые типы можно сузить, но нельзя расширить.

Вот как это выглядит в коде:


class Test {    public function param1(int $param) {}    public function param2(int|float $param) {}    public function return1(): int|float {}    public function return2(): int {}}class Test2 extends Test {    public function param1(int|float $param) {} // Allowed: Adding extra param type    public function param2(int $param) {}       // FORBIDDEN: Removing param type    public function return1(): int {}           // Allowed: Removing return type    public function return2(): int|float {}     // FORBIDDEN: Adding extra return type}

То же самое происходит при типах, которые получились как результат наследования:


class A {}class B extends A {}class Test {    public function param1(B|string $param) {}    public function param2(A|string $param) {}    public function return1(): A|string {}    public function return2(): B|string {}}class Test2 extends Test {    public function param1(A|string $param) {} // Allowed: Widening union member B -> A    public function param2(B|string $param) {} // FORBIDDEN: Restricting union member A -> B    public function return1(): B|string {}     // Allowed: Restricting union member A -> B    public function return2(): A|string {}     // FORBIDDEN: Widening union member B -> A}

Интереснее становится когда strict_types установлен в 0, то-есть по-умолчанию. Например, функция принимает int|string, а мы передали ей bool. Что в результате должно быть в переменной? Пустая строка, или ноль? Есть набор правил, по которым будет производиться приведение типов.


Так, если переданный тип не является частью объединения, то действуют следующие приоритеты:


  1. int;
  2. float;
  3. string;
  4. bool;

Так вот, будет перебираться этот список с типами, и для каждого проверяться: Если тип существует в объединении, и значение может быть приведёно к нему в соответствии с семантикой PHP, то так и будет сделано. Иначе пробуем следующий тип.


Как исключение, если string должен быть приведён к int|float, то сравнение идёт в первую очередь в соответствии с семантикой "числовых строк". К примеру, "123" станет int(123), в то время как "123.0" станет float(123.0).


К типам null и false не происходит неявного преобразования.

Таблица неявного приведения типов:


Original type 1st try 2nd try 3rd try
bool int float string
int float string bool
float int string bool
string int/float bool
object string

Типы полей и ссылки


class Test {    public int|string $x;    public float|string $y;}$test = new Test;$r = "foobar";$test->x =& $r;$test->y =& $r;// Reference set: { $r, $test->x, $test->y }// Types: { mixed, int|string, float|string }$r = 42; // TypeError

Здесь проблема в том, что тип устанавливаемого значения не совместим с объявленными в полях класса. Для Test::$x это могло быть int(42), а для Test::$y float(42.0). Так как эти значения не эквивалентны, то невозможно обеспечить единую ссылку, и TypeError будет сгенерирован.


24. Тип mixed (Mixed Type v2)


Был добавлен новый тип mixed.
Он эквивалентен типу array|bool|callable|int|float|object|resource|string|null.
Когда параметр объявлен без типа, то его тип это mixed.
Но стоит отметить, что если функция не объявляет возвращаемого значения, то это не mixed, а mixed|void. Таким образом, если функция гарантировано должна что-то возвращать, но тип результата не известен заранее, то стоит написать его mixed.


При наследовании действуют следующие правила:


class A{    public function bar(): mixed {}}class B extends A{    // return type was narrowed from mixed to int, this is allowed    public function bar(): int {}}

class C{    public function bar(): int {}}class D extends C{    // return type cannot be widened from int to mixed    // Fatal error thrown    public function bar(): mixed {}}

Подробнее можно почитать здесь


Где смотреть новые фичи


Более информации про новые функции в PHP можно посмотреть на rfc watch.


IMHO хорошие идеи для PHP



С текущей реализацией PHP можно писать программы практически не используя переназначение переменной. Мы могли бы для каждого нового значения создавать свою собственную переменную. Тем самым код стал бы куда лучше понятным, так как для каждой переменной нужно дать имя в соответствии с тем, что там храниться. Но сейчас так никто не делает. Отчасти это потому что язык не позволяет ограничить переприсваивание значения.


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


$invoice = getInvoice();$invoice = loadDependencies($invoice);$invoice = formatInvoice($invoice);// hm... how do I access initial $invoice now?return $invoice;

Я вижу как минимум 4 недостатка в этом коде:


  1. Никогда точно не знаешь что в переменной;
  2. Невозможность использовать уже перезаписанное значение где-то дальше в коде;
  3. Неустойчивость к изменениям если производиться копипаст большой части кода с такими-же переменными где-то во вложенном if, тогда ночь отладки обеспеченна.
  4. Каждый раз нужно писать знак $ перед $переменной. Да, это спорно, но ведь без долларов проще читать код. Возьмите какого-либо джависта, что он скажет про ваш код? Уххх как много долларов!

Вот каким мог быть этот код:


invoice = getInvoice();invoiceWithDependencies = loadDependencies(invoice);invoiceFormatted = formatInvoice(invoiceWithDependencies);// someAnotherAction(invoice);return invoiceFormatted;

Значения, что содержатся в invoice, invoiceWithDependencies, invoiceFormatted не могут быть перезаписаны. Да, и теперь мы точно знаем что и где хранится.


function printArr(array arr) {    foreach (arr as firmValue) {        print strtr(            "Current value is {current}. +1: {next}",             [                '{current}' => firmValue,                 '{next}'    => firmValue + 1            ]        );    }}


use Spatie\ModelStates\State;abstract class OrderStatus extends State{    public static string $name = static::getName();    abstract protected function getName(): string;}

Как видим, при первом обращении к $name, будет вызван метод getName финального класса. Это дает нам возможность настраивать какие значения будут попадать в поля в зависимости от каких-либо условий. А в данном примере это использовано с шаблоном "Template Method", и финальные классы обязаны предоставить нам значение для поля.


Сейчас многие фреймворки имеют значени по-умолчанию для большинства конфигураций в своих классах. Проблема с таким подходом заключается в том, что мы можем предоставить только примитивное значение. Никаких вызовов функций не разрешено. А что если мы хотим вызвать хелпер config для предоставления конфигурации, которая задаётся в поле класса? Тогда у нас проблемы, и нужно переопределять конструктор, где уже задавать значение поля. Хорошо, когда конструктор не имеет много параметров. А что, если там много параметров (к примеру, 7)? Тогда для простого создания поля, нужно 20 дополнительных бесполезных строк кода. И выглядит это ещё как уродливо!


Просто сравните это:


    protected string $whatever = $this->doCalculate();

И это:


    public function __construct(        array $query = [],        array $request = [],        array $attributes = [],        array $cookies = [],        array $files = [],        array $server = [],        $content = null    ) {        parent::__construct(            $query,            $request,            $attributes,            $cookies,            $files,            $server,            $content        );        $this->whatever = $this->doCalculate();    }

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

Подробнее..

Анбоксинг в современной Java

20.01.2021 22:15:38 | Автор: admin

Сейчас новые версии Java выходят раз в полгода. В них время от времени появляются новые возможности: var в Java 10, switch-выражения в Java 14, рекорды и паттерны в Java 16. Про это всё, конечно, написано множество статей, блог-постов, сделано множество докладов на конференциях. Оказалось, однако, что мы все пропустили один очень крутой апгрейд языка, который произошёл в Java 14 - апгрейд обычного цикла for по набору целых чисел. Дело в том, что этот апгрейд случился не в языке, а в виртуальной машине, но заметно поменял на то как мы можем программировать на Java.

Вспомним старый добрый цикл for:

for (int i = 0; i < 10; i++) {  System.out.println(i);}

У такого синтаксиса уйма недостатков. Во-первых, переменная цикла упоминается три раза. Очень легко перепутать и упомянуть не ту переменную в одном или двух местах. Во-вторых, такая переменная не является effectively final. Её не передашь в явном виде в лямбды или анонимные классы. Но ещё важнее: нельзя застраховаться от случайного изменения переменной внутри цикла. Читать код тоже трудно. Если тело цикла большое, не так-то легко сказать, изменяется ли она ещё и внутри цикла, а значит непонятно, просто мы обходим числа по порядку или делаем что-то более сложное. Есть ещё потенциальные ошибки, если необходимо сменить направление цикла или включить границу. Да и выглядит это старомодно.

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

for (x in 0 until 10) {  println(x)}

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

Можно ли приблизиться к этому варианту в Java? Да, с помощью цикла for-each, который появился в Java 5. Достаточно написать такой библиотечный метод в утилитном классе:

/** * Возвращает диапазон целых чисел * @param fromInclusive начальное значение (включительно) * @param toExclusive конечное значение (не включается) * @return Iterable, содержащий числа от fromInclusive до toExclusive. */public static Iterable<Integer> range(int fromInclusive,                                       int toExclusive) {  return () -> new Iterator<Integer>() {    int cursor = fromInclusive;    public boolean hasNext() { return cursor < toExclusive; }    public Integer next() { return cursor++; }  };}

Никакого rocket science, исключительно тривиальный код, даже комментировать нечего. После этого вы легко можете писать красивые циклы и в Java:

for (int i : range(0, 10)) { // импортируем наш метод статически  System.out.println(i);}

Красиво. Можно явно объявить переменную final, чтобы запретить случайные изменения. Несложно сделать такие же методы с шагом или с включённой верхней границей и пользоваться ими. Почему же никто так до сих пор не делает? Потому что в данном случае из-за боксинга фатально страдает производительность. Давайте для примера посчитаем сумму кубов чисел в простеньком JMH-бенчмарке:

@Param({"1000"})private int size;@Benchmarkpublic int plainFor() {  int result = 0;  for (int i = 0; i < size; i++) {    result += i * i * i;  }  return result;}@Benchmarkpublic int rangeFor() {  int result = 0;  for (int i : range(0, size)) {    result += i * i * i;  }  return result;}

Тело цикла весьма быстрое и не выделяет никакой памяти, но при этом делает какую-то полезную работу, что не позволит JIT-компилятору выкинуть цикл совсем. Также я на всякий случай параметризовал верхнюю границу, чтобы JIT не заложился на конкретное значение количества итераций. Запустим на Java 8 и увидим безрадостную картину:

Benchmark            (size)  Mode  Cnt     Score     Error  Units BoxedRange.plainFor    1000  avgt   30   622.679    7.286  ns/op BoxedRange.rangeFor    1000  avgt   30  3591.052  792.159  ns/op

Использование метода range снизило производительность практически в шесть раз: тест выполняется 3,5 мкс вместо 0,6 мкс. Если посчитать аллокации с помощью -prof gc, мы обнаружим, что версия rangeFor выделяет 13952 байта, тогда как версия plainFor ожидаемо не выделяет памяти вообще. Легко понять, откуда взялось это число, если вспомнить, что целые числа до 127 кэшируются. Новые объекты Integer выделяются на итерациях 128-999, то есть создаётся 872 объекта по 16 байт. Заметьте, кстати, что ни объект Iterable, ни объект Iterator не создаются: здесь наш код прекрасно обрабатывается оптимизацией скаляризации (scalar replacement). Однако боксинг всё портит.

Понятно, что такие накладные расходы на обычный цикл for часто неприемлемы, поэтому программировать в таком стиле на Java никто всерьёз рекомендовать не будет. Однако давайте попробуем более новые версии Java:

Вот тут нас ждёт приятный сюрприз: начиная с Java 14 производительность варианта с range сравнялась с простой версией! JIT-компилятор стал достаточно умным, чтобы сгенерировать настолько же хороший ассемблер, как и в простой версии.

На самом деле работа над оптимизацией по уничтожению ненужного боксинга велась много лет. Её плоды можно было пощупать ещё с версии Java 8 с помощью опций JVM -XX:+UnlockExperimentalVMOptions -XX:+AggressiveUnboxing. Мы можем попробовать запустить наш тест с этой опцией, и окажется, что с ней уже в восьмёрке производительность была существенно лучше:

В Java 8-11 мы имели производительность на уровне 0,9 мкс, в 12 стало в районе 0,8, а начиная с 13 сравнялось с обычным циклом. И вот к Java 14 эта оптимизация стала достаточно стабильной, чтобы её включить по умолчанию. Вы можете пытаться сделать это и в более ранних версиях, но я бы не рекомендовал этого на серьёзном продакшне. Смотрите, например, какие страшные баги приходилось исправлять в связи с этой опцией.

В чём была сложность реализации автоматического удаления боксинга? Одна из основных проблем - как раз тот самый кэш объектов Integer до 127. При боксинге целых чисел выполняется нетривиальный метод valueOf (цитата по Java 16):

public static Integer valueOf(int i) {  if (i >= IntegerCache.low && i <= IntegerCache.high)    return IntegerCache.cache[i + (-IntegerCache.low)];  return new Integer(i);}

Как видно, этот метод берёт значения в диапазоне от IntegerCache.low до IntegerCache.high из кэша, который заполняется на ранней стадии инициализации виртуальной машины. Поэтому, если у нас происходит боксинг с последующим анбоксингом, нельзя просто положиться на механизм скаляризации: иногда мы должны возвращать закэшированные объекты. В режиме AggressiveUnboxing JIT-компилятор принудительно начинает игнорировать этот кэш, если может доказать, что ссылка на объект никуда не уплывает. В этом можно убедиться, написав какой-нибудь такой код:

Field field = Class.forName("java.lang.Integer$IntegerCache").getDeclaredField("cache");field.setAccessible(true);Integer[] arr = (Integer[]) field.get(null);arr[130] = new Integer(1_000_000);for (int i = 0; i < 10000; i++) {  int res = rangeFor();  if (res != -1094471800) {    System.out.println("oops! " + res + "; i = " + i);    break;  }}

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

oops! 392146832; i = 333

То есть когда JIT-компилятор C2 добирается до метода rangeFor, он выкидывает обращение к испорченному кэшу, и начинает выдавать правильный ответ, который должен получаться, если бы мы не залезли в кэш грязными руками.

Однако хоть кэш и игнорируется, до Java 12 включительно я вижу в ассемблерных листингах инструкции вида cmp r10d,7fh, то есть счётчик сравнивается с числом 127 (=0x7f). Похоже, условие полностью выкинуть удалось только в Java 13. Я могу спекулировать, что эти лишние проверки не только отъедают такты процессора, но и занимают дополнительные регистры, из-за чего страдает уровень развёртки цикла. Во всяком случае, до Java 12 цикл с rangeFor разворачивается по 8 итераций, а начиная с Java 13 лишние проверки исчезают и развёртка уже охватывает 16 итераций, сравниваясь с plainFor.

Так или иначе, мы видим результат: агрессивное уничтожение боксинга стало поведением по умолчанию с Java 14, что позволяет гораздо чаще наплевать на боксинг и пользоваться удобными конструкциями. В связи с этим цикл вида for (int i : range(0, 10)) должен стать каноническим в новых версиях Java и должен заменить динозавра for (int i = 0; i < 10; i++), в том числе в учебниках по языку.

Окончательное решение проблемы с боксингом должно прийти к нам после специализации дженериков в проекте Valhalla. Тогда можно будет возвращать Iterable<int>, и боксинг в данном случае не потребуется вообще. Вероятно, мы не увидим в стандартной библиотеке методов вроде range пока этого не случится. Однако боксинг уже не так страшен и с Iterable<Integer> можно комфортно жить.

Подробнее..

Процессор, эмулирующий сам себя может быть быстрее самого себя

04.06.2021 04:18:40 | Автор: admin

Современный мир ПО содержит настолько много слоёв, что оптимизации могут быть в самых неожиданных местах. Знакомьтесь - год 2000, проект HP Dynamo. Это эмулятор процессора PA-8000, работающий на этом же процессоре PA-8000, но с технологией JIT. И реальные программы, запускающиеся в эмуляторе - в итоге работают быстрее, чем на голом процессоре.

td;dr - всё сказано в заголовке

Программистам из HP Labs стало интересно, а что будет, если написать оптимизирующий JIT компилятор под ту же платформу, на которой он работает. Работа заняла несколько лет. Под эмулятором можно было запускать немодифицированные родные бинари. И результаты оказались несколько неожиданными.

В эмуляторе они искали "hot paths" и оптимизировали ход исполнения кода. Таким образом уменьшались расходы на джампы, вызов функций, динамических библиотек, оптимизации работы с кешем процессора. Результаты повышения производительности доходили до +22%, в среднем по тестам получалось +9%.

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

Если кому интересны подробности:

1. http://cseweb.ucsd.edu/classes/sp00/cse231/dynamopldi.pdf
2. https://stackoverflow.com/questions/5641356/why-is-it-that-bytecode-might-run-faster-than-native-code/5641664#5641664
3. https://en.wikipedia.org/wiki/Just-in-time_compilation

Подробнее..

Recovery mode Запускаем php 8 с jit через docker за 5 минут

15.08.2020 02:16:25 | Автор: admin
Зима близко! А вместе с ней близится и релиз php 8. Если вам не терпится протестировать свой код в beta версии php 8, а заодно пощупать jit, то прошу под кат.

TL:DR;


Все примеры можно скачать с github и сразу запустить: github.com/xtrime-ru/php8-test

Подготовка


Для начала потребуется поставить docker и docker-compose.

Теперь создадим opcache.ini файл, который включит opcache и JIT в нашем контейнере.
; Extended PHP.ini file to enable JIT.; ====================================; Place this file under /usr/local/etc/php/conf.d/zend_extension=opcache.soopcache.enable=1opcache.enable_cli=1opcache.jit_buffer_size=32Mopcache.jit=1235


Теперь в рабочей папке нужно создать Dockerfile.
Список пакетов и расширений приведен для примера. Можно удалить эти строки или указать нужные именно вам расширения. Так же часть популярных расширений уже включена в базовый образ. Так что подключать json, xml и многие другие не требуется.

FROM php:8.0-rc-cliCOPY opcache.ini /usr/local/etc/php/conf.d/RUN apt-get update && apt-get upgrade -y \    && apt-get install apt-utils -y \##    устанавливаем необходимые пакеты    && apt-get install git zip vim libzip-dev libgmp-dev libffi-dev libssl-dev -y \##    Включаем необходимые расширения    && docker-php-ext-install -j$(nproc) sockets zip gmp pcntl bcmath ffi \##    Расшерения через pecl ставятся так, то в php 8 pecl сейчас отсутствует, так что строки закоментированы#    && PHP_OPENSSL=yes pecl install ev \#    && docker-php-ext-enable ev \##    Чистим временные файлы    && docker-php-source delete \    && apt-get autoremove --purge -y && apt-get autoclean -y && apt-get clean -y


Остался последний файл. Это docker-compose.yml, который позволяет легко управлять контейнерами при разработке.

version: '3.5'services:  php8-test:    build: ./    container_name: php8-test    restart: unless-stopped    volumes:      - ./:/app    working_dir: /app    entrypoint: "php -S 0.0.0.0:8000"    ports:      - "127.0.0.1:8000:8000"    logging:      driver: "json-file"      options:        max-size: "1024k"        max-file: "2"


Теперь можно запускать сборку контейнера и тесты.

  1. Собираем образ: docker-compose build
  2. Запускаем контейнер в фоне: docker-compose up -d
  3. Подключаемся к контейнеру: docker exec -it php8-test /bin/bash
  4. Текущая папка на контейнере синхронизирована с папкой проекта. Файлы можно редактировать на локальной машине.
  5. Скачиваем файл бенчмарка: github.com/php/php-src/blob/master/Zend/bench.php
  6. Запускаем бенч: php bench.php
  7. Можно отключить jit или opcache внутри контейнера тут: /usr/local/etc/php/conf.d/opcache.ini, что бы посмотреть, как изменится производительность
  8. В docker-compose.yml можно изменить директивы `volumes` и `workdir`, что бы залинковать другие директории в контейнер. Так же можно поменять entrypoint для запуска другой команды при старте контейнера. Например `php artisan serve` для laravel.
  9. Все файлы так же можно посмотреть в браузере по адресу http://127.0.0.1:8000/
    За это отвечают директивы entrypoint и ports.


Бенчмарк


Файл бенчмарка из оффициального репозитория php: github.com/php/php-src/blob/master/Zend/bench.php

########################## php 7.4.9# opcache.enable=1# opcache.enable_cli=0simple             0.053simplecall         0.007simpleucall        0.019simpleudcall       0.022mandel             0.182mandel2            0.220ackermann(7)       0.038ary(50000)         0.006ary2(50000)        0.005ary3(2000)         0.045fibo(30)           0.069hash1(50000)       0.014hash2(500)         0.008heapsort(20000)    0.036matrix(20)         0.034nestedloop(12)     0.089sieve(30)          0.014strcat(200000)     0.006------------------------Total              0.867########################## php 7.4.9# opcache.enable=1# opcache.enable_cli=1simple             0.007simplecall         0.003simpleucall        0.004simpleudcall       0.003mandel             0.088mandel2            0.113ackermann(7)       0.036ary(50000)         0.006ary2(50000)        0.007ary3(2000)         0.039fibo(30)           0.055hash1(50000)       0.012hash2(500)         0.008heapsort(20000)    0.030matrix(20)         0.029nestedloop(12)     0.041sieve(30)          0.011strcat(200000)     0.007------------------------Total              0.499########################## php 8.0-rc# opcache.enable=1# opcache.enable_cli=1# opcache.jit_buffer_size=128M# opcache.jit=1235simple             0.002simplecall         0.001simpleucall        0.001simpleudcall       0.001mandel             0.008mandel2            0.009ackermann(7)       0.016ary(50000)         0.006ary2(50000)        0.007ary3(2000)         0.015fibo(30)           0.030hash1(50000)       0.016hash2(500)         0.011heapsort(20000)    0.014matrix(20)         0.012nestedloop(12)     0.010sieve(30)          0.004strcat(200000)     0.006------------------------Total              0.168


JIT, конечно, сильно ускоряет операции связанные с использованием CPU. Но меня поразило другое. В php по умолчанию используется opcache.enable_cli=0. Если включить эту опцию, то можно получить двухкратный рост в бенчмарке. Лично я не знал о том, что opcache может так ускорять cli команды.

Я проверял несколько раз на чистых контейнерах, а так же с предварительной очисткой opcache. Результат всегда один и тот же: opcache.enable_cli=1 ускоряет бенчмарк начиная с первого запуска.
Подробнее..
Категории: Php , Docker , Php 8 , Jit , Opcache

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru