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:
committed by
Arnaud Le Blanc
parent
a7fde28d90
commit
26c96d38f4
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(©, ZEND_COPY_TMP, &rope_result, NULL);
|
||||
zend_emit_op(NULL, ZEND_ECHO, &rope_result, NULL);
|
||||
zend_emit_op_tmp(result, ZEND_STRLEN, ©, 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 {
|
||||
|
||||
Reference in New Issue
Block a user