1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

re-use sprintf() optimisation for printf()

Closes GH-19658
This commit is contained in:
Florian Engelhardt
2025-09-01 13:15:35 +02:00
committed by Arnaud Le Blanc
parent a7fde28d90
commit 26c96d38f4
4 changed files with 70 additions and 2 deletions

View File

@@ -99,5 +99,10 @@ PHP 8.6 UPGRADE NOTES
14. Performance Improvements
========================================
- Core:
. `printf()` using only `%s` and `%d` will be compiled into the equivalent
string interpolation, avoiding the overhead of a function call and repeatedly
parsing the format string.
- JSON:
. Improve performance of encoding arrays and objects.

View File

@@ -124,6 +124,7 @@ static inline bool may_have_side_effects(
case ZEND_FUNC_NUM_ARGS:
case ZEND_FUNC_GET_ARGS:
case ZEND_ARRAY_KEY_EXISTS:
case ZEND_COPY_TMP:
/* No side effects */
return false;
case ZEND_FREE:
@@ -425,10 +426,12 @@ static bool dce_instr(const context *ctx, zend_op *opline, zend_ssa_op *ssa_op)
return false;
}
if ((opline->op1_type & (IS_VAR|IS_TMP_VAR))&& !is_var_dead(ctx, ssa_op->op1_use)) {
if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) && !is_var_dead(ctx, ssa_op->op1_use)) {
if (!try_remove_var_def(ctx, ssa_op->op1_use, ssa_op->op1_use_chain, opline)) {
if (may_be_refcounted(ssa->var_info[ssa_op->op1_use].type)
&& opline->opcode != ZEND_CASE && opline->opcode != ZEND_CASE_STRICT) {
&& opline->opcode != ZEND_CASE
&& opline->opcode != ZEND_CASE_STRICT
&& opline->opcode != ZEND_COPY_TMP) {
free_var = ssa_op->op1_use;
free_var_type = opline->op1_type;
}

View File

@@ -264,6 +264,16 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx)
collect_constants = false;
break;
}
case ZEND_DO_UCALL:
case ZEND_DO_FCALL:
case ZEND_DO_FCALL_BY_NAME:
case ZEND_FRAMELESS_ICALL_0:
case ZEND_FRAMELESS_ICALL_1:
case ZEND_FRAMELESS_ICALL_2:
case ZEND_FRAMELESS_ICALL_3:
/* don't collect constants after any UCALL/FCALL/FRAMELESS ICALL */
collect_constants = 0;
break;
case ZEND_STRLEN:
if (opline->op1_type == IS_CONST &&
zend_optimizer_eval_strlen(&result, &ZEND_OP1_LITERAL(opline)) == SUCCESS) {

View File

@@ -28,6 +28,7 @@
#include "zend_API.h"
#include "zend_exceptions.h"
#include "zend_interfaces.h"
#include "zend_types.h"
#include "zend_virtual_cwd.h"
#include "zend_multibyte.h"
#include "zend_language_scanner.h"
@@ -4968,6 +4969,53 @@ static zend_result zend_compile_func_sprintf(znode *result, zend_ast_list *args)
return SUCCESS;
}
static zend_result zend_compile_func_printf(znode *result, zend_ast_list *args) /* {{{ */
{
/* Special case: printf with a single constant string argument and no format specifiers.
* In this case, just emit ECHO and return the string length if needed. */
if (args->children == 1) {
zend_eval_const_expr(&args->child[0]);
if (args->child[0]->kind != ZEND_AST_ZVAL) {
return FAILURE;
}
zval *format_string = zend_ast_get_zval(args->child[0]);
if (Z_TYPE_P(format_string) != IS_STRING) {
return FAILURE;
}
/* Check if there are any format specifiers */
if (!memchr(Z_STRVAL_P(format_string), '%', Z_STRLEN_P(format_string))) {
/* No format specifiers - just emit ECHO and return string length */
znode format_node;
zend_compile_expr(&format_node, args->child[0]);
zend_emit_op(NULL, ZEND_ECHO, &format_node, NULL);
/* Return the string length as a constant if the result is used */
result->op_type = IS_CONST;
ZVAL_LONG(&result->u.constant, Z_STRLEN_P(format_string));
return SUCCESS;
}
}
/* Fall back to sprintf optimization for format strings with specifiers */
znode rope_result;
if (zend_compile_func_sprintf(&rope_result, args) != SUCCESS) {
return FAILURE;
}
/* printf() returns the amount of bytes written, so just an ECHO of the
* resulting sprintf() optimisation might not be enough. At this early
* stage we can't detect if the result is actually used, so we just emit
* the opcodes and let them be cleaned up by the dead code elimination
* pass in the Zend Optimizer if the result of the printf() is in fact
* unused */
znode copy;
zend_emit_op_tmp(&copy, ZEND_COPY_TMP, &rope_result, NULL);
zend_emit_op(NULL, ZEND_ECHO, &rope_result, NULL);
zend_emit_op_tmp(result, ZEND_STRLEN, &copy, NULL);
return SUCCESS;
}
static zend_result zend_compile_func_clone(znode *result, zend_ast_list *args)
{
znode arg_node;
@@ -5050,6 +5098,8 @@ static zend_result zend_try_compile_special_func_ex(znode *result, zend_string *
return zend_compile_func_array_key_exists(result, args);
} else if (zend_string_equals_literal(lcname, "sprintf")) {
return zend_compile_func_sprintf(result, args);
} else if (zend_string_equals_literal(lcname, "printf")) {
return zend_compile_func_printf(result, args);
} else if (zend_string_equals(lcname, ZSTR_KNOWN(ZEND_STR_CLONE))) {
return zend_compile_func_clone(result, args);
} else {