Skip to content

Commit 23c33b1

Browse files
committedMar 24, 2015
Optimized strings concatenation.
ZEND_ADD_STRING/VAR/CHAR are replaced with ZEND_ROPE_INTI, ZEND_ROPE_ADD, ZEND_ROPE_END. Instead of reallocation and copying string on each ZEND_ADD_STRING/VAR/CAHR, collect all the strings and then allocate and construct the resulting string once.
1 parent bdf7fc6 commit 23c33b1

11 files changed

+827
-308
lines changed
 

‎Zend/zend_compile.c

+109-35
Original file line numberDiff line numberDiff line change
@@ -6231,55 +6231,129 @@ void zend_compile_resolve_class_name(znode *result, zend_ast *ast) /* {{{ */
62316231
}
62326232
/* }}} */
62336233

6234-
void zend_compile_encaps_list(znode *result, zend_ast *ast) /* {{{ */
6234+
static zend_op *zend_compile_rope_add(znode *result, uint32_t num, znode *elem_node) /* {{{ */
62356235
{
6236-
zend_ast_list *list = zend_ast_get_list(ast);
6237-
uint32_t i;
6236+
zend_op *opline = get_next_op(CG(active_op_array));
62386237

6239-
ZEND_ASSERT(list->children > 0);
6238+
if (num == 0) {
6239+
result->op_type = IS_TMP_VAR;
6240+
result->u.op.var = -1;
6241+
opline->opcode = ZEND_ROPE_INIT;
6242+
SET_UNUSED(opline->op1);
6243+
} else {
6244+
opline->opcode = ZEND_ROPE_ADD;
6245+
SET_NODE(opline->op1, result);
6246+
}
6247+
SET_NODE(opline->op2, elem_node);
6248+
SET_NODE(opline->result, result);
6249+
opline->extended_value = num;
6250+
return opline;
6251+
}
6252+
/* }}} */
62406253

6241-
result->op_type = IS_TMP_VAR;
6242-
result->u.op.var = get_temporary_variable(CG(active_op_array));
6254+
static void zend_compile_encaps_list(znode *result, zend_ast *ast) /* {{{ */
6255+
{
6256+
uint32_t i, j;
6257+
uint32_t rope_init_lineno = -1;
6258+
zend_op *opline = NULL, *init_opline;
6259+
znode elem_node, last_const_node;
6260+
zend_ast_list *list = zend_ast_get_list(ast);
62436261

6244-
for (i = 0; i < list->children; ++i) {
6245-
zend_ast *elem_ast = list->child[i];
6246-
znode elem_node;
6247-
zend_op *opline;
6262+
ZEND_ASSERT(list->children > 0);
62486263

6249-
zend_compile_expr(&elem_node, elem_ast);
6264+
j = 0;
6265+
last_const_node.op_type = IS_UNUSED;
6266+
for (i = 0; i < list->children; i++) {
6267+
zend_compile_expr(&elem_node, list->child[i]);
62506268

6251-
if (elem_ast->kind == ZEND_AST_ZVAL) {
6252-
zval *zv = &elem_node.u.constant;
6253-
ZEND_ASSERT(Z_TYPE_P(zv) == IS_STRING);
6269+
if (elem_node.op_type == IS_CONST) {
6270+
convert_to_string(&elem_node.u.constant);
62546271

6255-
if (Z_STRLEN_P(zv) > 1) {
6256-
opline = get_next_op(CG(active_op_array));
6257-
opline->opcode = ZEND_ADD_STRING;
6258-
} else if (Z_STRLEN_P(zv) == 1) {
6259-
char ch = *Z_STRVAL_P(zv);
6260-
zend_string_release(Z_STR_P(zv));
6261-
ZVAL_LONG(zv, ch);
6262-
6263-
opline = get_next_op(CG(active_op_array));
6264-
opline->opcode = ZEND_ADD_CHAR;
6272+
if (Z_STRLEN(elem_node.u.constant) == 0) {
6273+
zval_ptr_dtor(&elem_node.u.constant);
6274+
} else if (last_const_node.op_type == IS_CONST) {
6275+
concat_function(&last_const_node.u.constant, &last_const_node.u.constant, &elem_node.u.constant);
6276+
zval_ptr_dtor(&elem_node.u.constant);
62656277
} else {
6266-
/* String can be empty after a variable at the end of a heredoc */
6267-
zend_string_release(Z_STR_P(zv));
6268-
continue;
6278+
last_const_node.op_type = IS_CONST;
6279+
ZVAL_COPY_VALUE(&last_const_node.u.constant, &elem_node.u.constant);
62696280
}
6281+
continue;
62706282
} else {
6271-
opline = get_next_op(CG(active_op_array));
6272-
opline->opcode = ZEND_ADD_VAR;
6273-
ZEND_ASSERT(elem_node.op_type != IS_CONST);
6283+
if (j == 0) {
6284+
rope_init_lineno = get_next_op_number(CG(active_op_array));
6285+
}
6286+
if (last_const_node.op_type == IS_CONST) {
6287+
zend_compile_rope_add(result, j++, &last_const_node);
6288+
last_const_node.op_type = IS_UNUSED;
6289+
}
6290+
opline = zend_compile_rope_add(result, j++, &elem_node);
62746291
}
6292+
}
62756293

6276-
if (i == 0) {
6277-
SET_UNUSED(opline->op1);
6294+
if (j == 0) {
6295+
result->op_type = IS_CONST;
6296+
if (last_const_node.op_type == IS_CONST) {
6297+
ZVAL_COPY_VALUE(&result->u.constant, &last_const_node.u.constant);
6298+
} else {
6299+
ZVAL_EMPTY_STRING(&result->u.constant);
6300+
/* empty string */
6301+
}
6302+
return;
6303+
} else if (last_const_node.op_type == IS_CONST) {
6304+
opline = zend_compile_rope_add(result, j++, &last_const_node);
6305+
}
6306+
init_opline = CG(active_op_array)->opcodes + rope_init_lineno;
6307+
if (j == 1) {
6308+
if (opline->op2_type == IS_CONST) {
6309+
GET_NODE(result, opline->op2);
6310+
MAKE_NOP(opline);
62786311
} else {
6279-
SET_NODE(opline->op1, result);
6312+
opline->opcode = ZEND_CAST;
6313+
opline->extended_value = IS_STRING;
6314+
opline->op1_type = opline->op2_type;
6315+
opline->op1 = opline->op2;
6316+
opline->result_type = IS_TMP_VAR;
6317+
opline->result.var = get_temporary_variable(CG(active_op_array));
6318+
SET_UNUSED(opline->op2);
6319+
GET_NODE(result, opline->result);
6320+
}
6321+
} else if (j == 2) {
6322+
opline->opcode = ZEND_FAST_CONCAT;
6323+
opline->extended_value = 0;
6324+
opline->op1_type = init_opline->op2_type;
6325+
opline->op1 = init_opline->op2;
6326+
opline->result_type = IS_TMP_VAR;
6327+
opline->result.var = get_temporary_variable(CG(active_op_array));
6328+
MAKE_NOP(init_opline);
6329+
GET_NODE(result, opline->result);
6330+
} else {
6331+
uint32_t var;
6332+
6333+
init_opline->extended_value = j;
6334+
opline->opcode = ZEND_ROPE_END;
6335+
opline->result.var = get_temporary_variable(CG(active_op_array));
6336+
var = opline->op1.var = get_temporary_variable(CG(active_op_array));
6337+
GET_NODE(result, opline->result);
6338+
6339+
/* Allocates the necessary number of zval slots to keep the rope */
6340+
i = ((j * sizeof(zend_string*)) + (sizeof(zval) - 1)) / sizeof(zval);
6341+
while (i > 1) {
6342+
get_temporary_variable(CG(active_op_array));
6343+
i--;
6344+
}
6345+
/* Update all the previous opcodes to use the same variable */
6346+
while (opline != init_opline) {
6347+
opline--;
6348+
if (opline->opcode == ZEND_ROPE_ADD &&
6349+
opline->result.var == -1) {
6350+
opline->op1.var = var;
6351+
opline->result.var = var;
6352+
} else if (opline->opcode == ZEND_ROPE_INIT &&
6353+
opline->result.var == -1) {
6354+
opline->result.var = var;
6355+
}
62806356
}
6281-
SET_NODE(opline->op2, &elem_node);
6282-
SET_NODE(opline->result, result);
62836357
}
62846358
}
62856359
/* }}} */

‎Zend/zend_opcode.c

+1
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,7 @@ ZEND_API binary_op_type get_binary_op(int opcode)
861861
case ZEND_SR:
862862
case ZEND_ASSIGN_SR:
863863
return (binary_op_type) shift_right_function;
864+
case ZEND_FAST_CONCAT:
864865
case ZEND_CONCAT:
865866
case ZEND_ASSIGN_CONCAT:
866867
return (binary_op_type) concat_function;

‎Zend/zend_vm_def.h

+89-51
Original file line numberDiff line numberDiff line change
@@ -2571,82 +2571,120 @@ ZEND_VM_HANDLER(127, ZEND_FE_FREE, TMPVAR, ANY)
25712571
ZEND_VM_NEXT_OPCODE();
25722572
}
25732573

2574-
ZEND_VM_HANDLER(54, ZEND_ADD_CHAR, TMP|UNUSED, CONST)
2574+
ZEND_VM_HANDLER(53, ZEND_FAST_CONCAT, CONST|TMPVAR|CV, CONST|TMPVAR|CV)
25752575
{
25762576
USE_OPLINE
2577-
zval *str = EX_VAR(opline->result.var);
2577+
zend_free_op free_op1, free_op2;
2578+
zval *op1, *op2;
2579+
zend_string *op1_str, *op2_str, *str;
25782580

25792581
SAVE_OPLINE();
2580-
2581-
if (OP1_TYPE == IS_UNUSED) {
2582-
/* Initialize for erealloc in add_char_to_string */
2583-
ZVAL_EMPTY_STRING(str);
2582+
op1 = GET_OP1_ZVAL_PTR(BP_VAR_R);
2583+
op2 = GET_OP2_ZVAL_PTR(BP_VAR_R);
2584+
if (OP1_TYPE == IS_CONST) {
2585+
op1_str = Z_STR_P(op1);
2586+
} else {
2587+
op1_str = zval_get_string(op1);
25842588
}
2585-
2586-
add_char_to_string(str, str, EX_CONSTANT(opline->op2));
2587-
2588-
/* FREE_OP is missing intentionally here - we're always working on the same temporary variable */
2589-
/*CHECK_EXCEPTION();*/
2589+
if (OP2_TYPE == IS_CONST) {
2590+
op2_str = Z_STR_P(op2);
2591+
} else {
2592+
op2_str = zval_get_string(op2);
2593+
}
2594+
str = zend_string_alloc(op1_str->len + op2_str->len, 0);
2595+
memcpy(str->val, op1_str->val, op1_str->len);
2596+
memcpy(str->val + op1_str->len, op2_str->val, op2_str->len+1);
2597+
ZVAL_NEW_STR(EX_VAR(opline->result.var), str);
2598+
if (OP1_TYPE != IS_CONST) {
2599+
zend_string_release(op1_str);
2600+
}
2601+
if (OP2_TYPE != IS_CONST) {
2602+
zend_string_release(op2_str);
2603+
}
2604+
FREE_OP1();
2605+
FREE_OP2();
2606+
CHECK_EXCEPTION();
25902607
ZEND_VM_NEXT_OPCODE();
25912608
}
25922609

2593-
ZEND_VM_HANDLER(55, ZEND_ADD_STRING, TMP|UNUSED, CONST)
2610+
ZEND_VM_HANDLER(54, ZEND_ROPE_INIT, UNUSED, CONST|TMPVAR|CV)
25942611
{
25952612
USE_OPLINE
2596-
zval *str = EX_VAR(opline->result.var);
2597-
2598-
SAVE_OPLINE();
2613+
zend_free_op free_op2;
2614+
zend_string **rope;
2615+
zval *var;
25992616

2600-
if (OP1_TYPE == IS_UNUSED) {
2601-
/* Initialize for erealloc in add_string_to_string */
2602-
ZVAL_EMPTY_STRING(str);
2617+
/* Compiler allocates the necessary number of zval slots to keep the rope */
2618+
rope = (zend_string**)EX_VAR(opline->result.var);
2619+
if (OP2_TYPE == IS_CONST) {
2620+
var = GET_OP2_ZVAL_PTR(BP_VAR_R);
2621+
rope[0] = zend_string_copy(Z_STR_P(var));
2622+
} else {
2623+
SAVE_OPLINE();
2624+
var = GET_OP2_ZVAL_PTR(BP_VAR_R);
2625+
rope[0] = zval_get_string(var);
2626+
FREE_OP2();
2627+
CHECK_EXCEPTION();
26032628
}
2604-
2605-
add_string_to_string(str, str, EX_CONSTANT(opline->op2));
2606-
2607-
/* FREE_OP is missing intentionally here - we're always working on the same temporary variable */
2608-
/*CHECK_EXCEPTION();*/
26092629
ZEND_VM_NEXT_OPCODE();
26102630
}
26112631

2612-
ZEND_VM_HANDLER(56, ZEND_ADD_VAR, TMP|UNUSED, TMPVAR|CV)
2632+
ZEND_VM_HANDLER(55, ZEND_ROPE_ADD, TMP, CONST|TMPVAR|CV)
26132633
{
26142634
USE_OPLINE
26152635
zend_free_op free_op2;
2616-
zval *str = EX_VAR(opline->result.var);
2636+
zend_string **rope;
26172637
zval *var;
2618-
zval var_copy;
2619-
int use_copy = 0;
2620-
2621-
SAVE_OPLINE();
2622-
var = GET_OP2_ZVAL_PTR(BP_VAR_R);
26232638

2624-
if (OP1_TYPE == IS_UNUSED) {
2625-
/* Initialize for erealloc in add_string_to_string */
2626-
ZVAL_EMPTY_STRING(str);
2639+
/* op1 and result are the same */
2640+
rope = (zend_string**)EX_VAR(opline->op1.var);
2641+
if (OP2_TYPE == IS_CONST) {
2642+
var = GET_OP2_ZVAL_PTR(BP_VAR_R);
2643+
rope[opline->extended_value] = zend_string_copy(Z_STR_P(var));
2644+
} else {
2645+
SAVE_OPLINE();
2646+
var = GET_OP2_ZVAL_PTR(BP_VAR_R);
2647+
rope[opline->extended_value] = zval_get_string(var);
2648+
FREE_OP2();
2649+
CHECK_EXCEPTION();
26272650
}
2651+
ZEND_VM_NEXT_OPCODE();
2652+
}
26282653

2629-
if (Z_TYPE_P(var) != IS_STRING) {
2630-
use_copy = zend_make_printable_zval(var, &var_copy);
2631-
2632-
if (use_copy) {
2633-
var = &var_copy;
2634-
}
2654+
ZEND_VM_HANDLER(56, ZEND_ROPE_END, TMP, CONST|TMPVAR|CV)
2655+
{
2656+
USE_OPLINE
2657+
zend_free_op free_op2;
2658+
zend_string **rope;
2659+
zval *var, *ret;
2660+
uint32_t i;
2661+
size_t len = 0;
2662+
char *target;
2663+
2664+
rope = (zend_string**)EX_VAR(opline->op1.var);
2665+
if (OP2_TYPE == IS_CONST) {
2666+
var = GET_OP2_ZVAL_PTR(BP_VAR_R);
2667+
rope[opline->extended_value] = zend_string_copy(Z_STR_P(var));
2668+
} else {
2669+
SAVE_OPLINE();
2670+
var = GET_OP2_ZVAL_PTR(BP_VAR_R);
2671+
rope[opline->extended_value] = zval_get_string(var);
2672+
FREE_OP2();
2673+
CHECK_EXCEPTION();
26352674
}
2636-
add_string_to_string(str, str, var);
2637-
2638-
if (use_copy) {
2639-
zend_string_release(Z_STR_P(var));
2675+
for (i = 0; i <= opline->extended_value; i++) {
2676+
len += rope[i]->len;
26402677
}
2641-
/* original comment, possibly problematic:
2642-
* FREE_OP is missing intentionally here - we're always working on the same temporary variable
2643-
* (Zeev): I don't think it's problematic, we only use variables
2644-
* which aren't affected by FREE_OP(Ts, )'s anyway, unless they're
2645-
* string offsets or overloaded objects
2646-
*/
2647-
FREE_OP2();
2678+
ret = EX_VAR(opline->result.var);
2679+
ZVAL_STR(ret, zend_string_alloc(len, 0));
2680+
target = Z_STRVAL_P(ret);
2681+
for (i = 0; i <= opline->extended_value; i++) {
2682+
memcpy(target, rope[i]->val, rope[i]->len);
2683+
target += rope[i]->len;
2684+
zend_string_release(rope[i]);
2685+
}
2686+
*target = '\0';
26482687

2649-
CHECK_EXCEPTION();
26502688
ZEND_VM_NEXT_OPCODE();
26512689
}
26522690

0 commit comments

Comments
 (0)
Please sign in to comment.