PHP 3694 ~ 5 мин.

Новый JIT-движок для PHP 8.4 и PHP 9

Новый JIT-движок для PHP 8.4 и PHP 9

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

Как вы знаете JIT появился в PHP 8.0 и представлял из себя компиляцию "на лету" - Just In Time, так же вы вероятно знаете, что PHP является интерпретируемым языком, он не компилирует программы в прямом смысле этого значения, как, например это делают C или Rust.

Чтобы включить JIT нужно было указать несколько параметров конфига php.ini.

Новый JIT движок PHP

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

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

Старая реализация JIT будет сохранена на некоторое время. Все желающие могут просмотреть код https://github.com/php/php-src/pull/12079.

Новая реализация JIT основана на IR-Lightweight JIT Compilation Framework и в отличии от старого JIT-подхода в PHP 8.*, который генерирует собственный код непосредственно из байт-кода PHP, эта реализация генерирует промежуточное представление (IR) и делегирует все задачи более низкого уровня в IR Framework. IR для JIT похож на абстрактное синтаксическое дерево (AST) для компилятора.

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

IR-пример

Попробуем сгенерировать код для следующей функции:

int32_t mandelbrot(double x, double y)
{
	double cr = y - 0.5;
	double ci = x;
	double zi = 0.0;
	double zr = 0.0;
	int i = 0;

	while(1) {
		i++;
		double temp = zr * zi;
		double zr2 = zr * zr;
		double zi2 = zi * zi;
		zr = zr2 - zi2 + cr;
		zi = temp + temp + ci;
		if (zi2 + zr2 > 16)
			return i;
		if (i > 1000)
			return 0;
	}
	
}

Это можно сделать через API построения IR с помощью следующего кода:

void gen_mandelbrot(ir_ctx *ctx)
{
	ir_START();
	ir_ref x = ir_PARAM(IR_DOUBLE, "x", 1);
	ir_ref y = ir_PARAM(IR_DOUBLE, "y", 2);
	ir_ref cr = ir_SUB_D(y, ir_CONST_DOUBLE(0.5));
	ir_ref ci = ir_COPY_D(x);
	ir_ref zi = ir_COPY_D(ir_CONST_DOUBLE(0.0));
	ir_ref zr = ir_COPY_D(ir_CONST_DOUBLE(0.0));
	ir_ref i = ir_COPY_D(ir_CONST_I32(0));

	ir_ref loop = ir_LOOP_BEGIN(ir_END());
		ir_ref zi_1 = ir_PHI_2(zi, IR_UNUSED);
		ir_ref zr_1 = ir_PHI_2(zr, IR_UNUSED);
		ir_ref i_1 = ir_PHI_2(i, IR_UNUSED);

		ir_ref i_2 = ir_ADD_I32(i_1, ir_CONST_I32(1));
		ir_ref temp = ir_MUL_D(zr_1, zi_1);
		ir_ref zr2 = ir_MUL_D(zr_1, zr_1);
		ir_ref zi2 = ir_MUL_D(zi_1, zi_1);
		ir_ref zr_2 = ir_ADD_D(ir_SUB_D(zr2, zi2), cr);
		ir_ref zi_2 = ir_ADD_D(ir_ADD_D(temp, temp), ci);
		ir_ref if_1 = ir_IF(ir_GT(ir_ADD_D(zi2, zr2), ir_CONST_DOUBLE(16.0)));
			ir_IF_TRUE(if_1);
				ir_RETURN(i_2);
			ir_IF_FALSE(if_1);
				ir_ref if_2 = ir_IF(ir_GT(i_2, ir_CONST_I32(1000)));
				ir_IF_TRUE(if_2);
					ir_RETURN(ir_CONST_I32(0));
				ir_IF_FALSE(if_2);
					ir_ref loop_end = ir_LOOP_END();

	/* close loop */
	ir_MERGE_SET_OP(loop, 2, loop_end);
	ir_PHI_SET_OP(zi_1, 2, zi_2);
	ir_PHI_SET_OP(zr_1, 2, zr_2);
	ir_PHI_SET_OP(i_1, 2, i_2);
}

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

{
	uintptr_t c_1 = 0;
	bool c_2 = 0;
	bool c_3 = 1;
	double c_4 = 0.5;
	double c_5 = 0;
	int32_t c_6 = 0;
	int32_t c_7 = 1;
	double c_8 = 16;
	int32_t c_9 = 1000;
	l_1 = START(l_22);
	double d_2 = PARAM(l_1, "x", 1);
	double d_3 = PARAM(l_1, "y", 2);
	double d_4 = SUB(d_3, c_4);
	l_5 = END(l_1);
	l_6 = LOOP_BEGIN(l_5, l_29);
	double d_7 = PHI(l_6, c_5, d_28);
	double d_8 = PHI(l_6, c_5, d_26);
	int32_t d_9 = PHI(l_6, c_6, d_10);
	int32_t d_10 = ADD(d_9, c_7);
	double d_11 = MUL(d_8, d_8);
	double d_12 = MUL(d_7, d_7);
	double d_13 = ADD(d_12, d_11);
	bool d_14 = GT(d_13, c_8);
	l_15 = IF(l_6, d_14);
	l_16 = IF_TRUE(l_15);
	l_17 = RETURN(l_16, d_10);
	l_18 = IF_FALSE(l_15);
	bool d_19 = GT(d_10, c_9);
	l_20 = IF(l_18, d_19);
	l_21 = IF_TRUE(l_20);
	l_22 = RETURN(l_21, c_6, l_17);
	l_23 = IF_FALSE(l_20);
	double d_24 = MUL(d_7, d_8);
	double d_25 = SUB(d_11, d_12);
	double d_26 = ADD(d_25, d_4);
	double d_27 = ADD(d_24, d_24);
	double d_28 = ADD(d_27, d_2);
	l_29 = LOOP_END(l_23);
}

Окончательно сгенерированный код:

test:
	subsd .L4(%rip), %xmm1
	xorpd %xmm3, %xmm3
	xorpd %xmm2, %xmm2
	xorl %eax, %eax
.L1:
	leal 1(%rax), %eax
	movapd %xmm2, %xmm4
	mulsd %xmm2, %xmm4
	movapd %xmm3, %xmm5
	mulsd %xmm3, %xmm5
	movapd %xmm5, %xmm6
	addsd %xmm4, %xmm6
	ucomisd .L5(%rip), %xmm6
	ja .L2
	cmpl $0x3e8, %eax
	jg .L3
	mulsd %xmm2, %xmm3
	subsd %xmm5, %xmm4
	movapd %xmm4, %xmm2
	addsd %xmm1, %xmm2
	addsd %xmm3, %xmm3
	addsd %xmm0, %xmm3
	jmp .L1
.L2:
	retq
.L3:
	xorl %eax, %eax
	retq
.rodata
.L4:
	.db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x3f
.L5:
	.db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x40

Визуализированный график:


Ключевые преимущества новой реализации JIT

  • Использование IR открывает возможности для лучшей оптимизации и распределения регистров (полученный собственный код более эффективен). 
  • PHP не нужно заботиться о большинстве деталей низкого уровня (различные процессоры, соглашения о вызовах, детали TLS и т. д.).
  • Гораздо проще реализовать поддержку новых целей (например, RISCV)
  • IR-фреймворк будет разрабатываться отдельно от PHP и может принимать вклад от других проектов (новые оптимизации, улучшения, исправления ошибок).

Недостатки новой реализации JIT

К сожалению JIT-компиляция становится медленнее (это практически незаметно для tracing JIT, но function JIT-компиляция того же Wordpress становится в 4 раза медленнее)

В заключении

Новая реализация JIT успешно проходит все рабочие процессы CI, включая Nightly, но она еще не доработана и может вызывать сбои. Чтобы снизить риски, новый патч не удаляет старую реализацию JIT (то есть такую ​​же, как JIT PHP 8.3). Можно собрать PHP со старым JIT, настроив его с помощью --disable-opcache-jit-ir. В будущем старая реализация будет удалена.

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



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

 

Что думаешь?

Категории
  • PHP 67
  • Заметки 18
  • Безопасность 4
  • Флуд 2
  • Nginx 2
  • ИТ новости 2
  • Видео 1
  • Docker 1
  • Roadmap 1
  • Архитектура 0

Хочешь поддержать сайт?

Делаем из мухи слона

sergeymukhin.com

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

Релизы PHP 8.4

Дата Релиз
4 Июля 2024 Альфа 1
18 Июля 2024 Альфа 2
1 Августа 2024 Альфа 3
13 Августа 2024 Feature freeze
15 Августа 2024 Бета 1
29 Августа 2024 Бета 2
12 Сентября 2024 Бета 3
26 Сентября 2024 RC 1
10 Октября 2024 RC 2
24 Октября 2024 RC 3
7 Ноября 2024 RC 4
21 Ноября 2024 GA

Что нового?