Hi, I've already posted this patch and it has since been reviewed and improved. I'm re-posting it for discussion before eventually commiting it.
The ternary operator always copies its second or third operand, which is very slow compared to an if/else when the operand is an array for example: $a = range(0,9); // this takes 0.3 seconds here: for ($i = 0; $i < 5000000; ++$i) { if (true) { $b = $a; } else { $b = $a; } } // this takes 3.8 seconds: for ($i = 0; $i < 5000000; ++$i) { $b = true ? $a : $a; } I've tried to reduce the performance hit by avoiding the copy when possible (patch attached). Benchmark: Without patch: (the numbers are the time taken to run the code a certain amount of times) $int = 0; $ary = array(1,2,3,4,5,6,7,8,9); true ? 1 : 0 0.124 true ? 1+0 : 0 0.109 true ? $ary : 0 2.020 ! true ? $int : 0 0.103 true ? ${'ary'} : 0 2.290 ! true ?: 0 0.091 1+0 ?: 0 0.086 $ary ?: 0 2.151 ! ${'var'} ?: 0 2.317 ! With patch: true ? 1 : 0 0.124 true ? 1+0 : 0 0.195 true ? $ary : 0 0.103 true ? $int : 0 0.089 true ? ${'ary'} : 0 0.103 true ?: 0 0.086 1+0 ?: 0 0.159 $cv ?: 0 0.090 ${'var'} ?: 0 0.089 The array copying overhead is eliminated. There is however a slowdown in some of the cases, but overall there is no completely unexpected performance hit as it is the case currently. What do you think ? Is there any objection ? Best regards,
diff --git a/Zend/micro_bench.php b/Zend/micro_bench.php index 87a1b15..7052588 100644 --- a/Zend/micro_bench.php +++ b/Zend/micro_bench.php @@ -202,6 +202,35 @@ function read_str_offset($n) { } } +function issetor($n) { + $val = array(0,1,2,3,4,5,6,7,8,9); + for ($i = 0; $i < $n; ++$i) { + $x = $val ?: null; + } +} + +function issetor2($n) { + $f = false; $j = 0; + for ($i = 0; $i < $n; ++$i) { + $x = $f ?: $j + 1; + } +} + +function ternary($n) { + $val = array(0,1,2,3,4,5,6,7,8,9); + $f = false; + for ($i = 0; $i < $n; ++$i) { + $x = $f ? null : $val; + } +} + +function ternary2($n) { + $f = false; $j = 0; + for ($i = 0; $i < $n; ++$i) { + $x = $f ? $f : $j + 1; + } +} + /*****/ function empty_loop($n) { @@ -318,4 +347,12 @@ read_hash(N); $t = end_test($t, '$x = $hash[\'v\']', $overhead); read_str_offset(N); $t = end_test($t, '$x = $str[0]', $overhead); +issetor(N); +$t = end_test($t, '$x = $a ?: null', $overhead); +issetor2(N); +$t = end_test($t, '$x = $f ?: tmp', $overhead); +ternary(N); +$t = end_test($t, '$x = $f ? $f : $a', $overhead); +ternary2(N); +$t = end_test($t, '$x = $f ? $f : tmp', $overhead); total($t0, "Total"); diff --git a/Zend/tests/ternary.phpt b/Zend/tests/ternary.phpt new file mode 100644 index 0000000..d1a6d45 --- /dev/null +++ b/Zend/tests/ternary.phpt @@ -0,0 +1,429 @@ +--TEST-- +?: and ternary operators +--FILE-- +<?php +$a = "val_of_a"; +$b = "val_of_b"; + +echo "-- 1 --\n"; +var_dump(true ? "foo" : "bar"); +true ? "foo" : "bar"; +$z = true ? "foo" : "bar"; + +echo "-- 2 --\n"; +var_dump(true ? "foo" : "c"."d"); +true ? "foo" : "c"."d"; +$z = true ? "foo" : "c"."d"; + +echo "-- 3 --\n"; +var_dump(true ? "foo" : $b); +true ? "foo" : $b; +$z = true ? "foo" : $b; + +echo "-- 4 --\n"; +var_dump(true ? "foo" : ${'b'}); +true ? "foo" : ${'b'}; +$z = true ? "foo" : ${'b'}; + +echo "-- 5 --\n"; +var_dump(true ? "a"."b" : "bar"); +true ? "a"."b" : "bar"; +$z = true ? "a"."b" : "bar"; + +echo "-- 6 --\n"; +var_dump(true ? "a"."b" : "c"."d"); +true ? "a"."b" : "c"."d"; +$z = true ? "a"."b" : "c"."d"; + +echo "-- 7 --\n"; +var_dump(true ? "a"."b" : $b); +true ? "a"."b" : $b; +$z = true ? "a"."b" : $b; + +echo "-- 8 --\n"; +var_dump(true ? "a"."b" : ${'b'}); +true ? "a"."b" : ${'b'}; +$z = true ? "a"."b" : ${'b'}; + +echo "-- 9 --\n"; +var_dump(true ? $a : "bar"); +true ? $a : "bar"; +$z = true ? $a : "bar"; + +echo "-- 10 --\n"; +var_dump(true ? $a : "c"."d"); +true ? $a : "c"."d"; +$z = true ? $a : "c"."d"; + +echo "-- 11 --\n"; +var_dump(true ? $a : $b); +true ? $a : $b; +$z = true ? $a : $b; + +echo "-- 12 --\n"; +var_dump(true ? $a : ${'b'}); +true ? $a : ${'b'}; +$z = true ? $a : ${'b'}; + +echo "-- 13 --\n"; +var_dump(true ? ${'a'} : "bar"); +true ? ${'a'} : "bar"; +$z = true ? ${'a'} : "bar"; + +echo "-- 14 --\n"; +var_dump(true ? ${'a'} : "c"."d"); +true ? ${'a'} : "c"."d"; +$z = true ? ${'a'} : "c"."d"; + +echo "-- 15 --\n"; +var_dump(true ? ${'a'} : $b); +true ? ${'a'} : $b; +$z = true ? ${'a'} : $b; + +echo "-- 16 --\n"; +var_dump(true ? ${'a'} : ${'b'}); +true ? ${'a'} : ${'b'}; +$z = true ? ${'a'} : ${'b'}; + +echo "-- 17 --\n"; +var_dump(false ? "foo" : "bar"); +false ? "foo" : "bar"; +$z = false ? "foo" : "bar"; + +echo "-- 18 --\n"; +var_dump(false ? "foo" : "c"."d"); +false ? "foo" : "c"."d"; +$z = false ? "foo" : "c"."d"; + +echo "-- 19 --\n"; +var_dump(false ? "foo" : $b); +false ? "foo" : $b; +$z = false ? "foo" : $b; + +echo "-- 20 --\n"; +var_dump(false ? "foo" : ${'b'}); +false ? "foo" : ${'b'}; +$z = false ? "foo" : ${'b'}; + +echo "-- 21 --\n"; +var_dump(false ? "a"."b" : "bar"); +false ? "a"."b" : "bar"; +$z = false ? "a"."b" : "bar"; + +echo "-- 22 --\n"; +var_dump(false ? "a"."b" : "c"."d"); +false ? "a"."b" : "c"."d"; +$z = false ? "a"."b" : "c"."d"; + +echo "-- 23 --\n"; +var_dump(false ? "a"."b" : $b); +false ? "a"."b" : $b; +$z = false ? "a"."b" : $b; + +echo "-- 24 --\n"; +var_dump(false ? "a"."b" : ${'b'}); +false ? "a"."b" : ${'b'}; +$z = false ? "a"."b" : ${'b'}; + +echo "-- 25 --\n"; +var_dump(false ? $a : "bar"); +false ? $a : "bar"; +$z = false ? $a : "bar"; + +echo "-- 26 --\n"; +var_dump(false ? $a : "c"."d"); +false ? $a : "c"."d"; +$z = false ? $a : "c"."d"; + +echo "-- 27 --\n"; +var_dump(false ? $a : $b); +false ? $a : $b; +$z = false ? $a : $b; + +echo "-- 28 --\n"; +var_dump(false ? $a : ${'b'}); +false ? $a : ${'b'}; +$z = false ? $a : ${'b'}; + +echo "-- 29 --\n"; +var_dump(false ? ${'a'} : "bar"); +false ? ${'a'} : "bar"; +$z = false ? ${'a'} : "bar"; + +echo "-- 30 --\n"; +var_dump(false ? ${'a'} : "c"."d"); +false ? ${'a'} : "c"."d"; +$z = false ? ${'a'} : "c"."d"; + +echo "-- 31 --\n"; +var_dump(false ? ${'a'} : $b); +false ? ${'a'} : $b; +$z = false ? ${'a'} : $b; + +echo "-- 32 --\n"; +var_dump(false ? ${'a'} : ${'b'}); +false ? ${'a'} : ${'b'}; +$z = false ? ${'a'} : ${'b'}; + + + +$a0 = array(); +$a1 = array(1); + +echo "-- 33 --\n"; +var_dump("0" ?: "bar"); +"0" ?: "bar"; +$z = "0" ?: "bar"; + +echo "-- 34 --\n"; +var_dump("0" ?: "c"."d"); +"0" ?: "c"."d"; +$z = "0" ?: "c"."d"; + +echo "-- 35 --\n"; +var_dump("0" ?: $b); +"0" ?: $b; +$z = "0" ?: $b; + +echo "-- 36 --\n"; +var_dump("0" ?: ${'b'}); +"0" ?: ${'b'}; +$z = "0" ?: ${'b'}; + +echo "-- 37 --\n"; +var_dump("1" ?: "bar"); +"1" ?: "bar"; +$z = "1" ?: "bar"; + +echo "-- 38 --\n"; +var_dump("1" ?: "c"."d"); +"1" ?: "c"."d"; +$z = "1" ?: "c"."d"; + +echo "-- 39 --\n"; +var_dump("1" ?: $b); +"1" ?: $b; +$z = "1" ?: $b; + +echo "-- 40 --\n"; +var_dump("1" ?: ${'b'}); +"1" ?: ${'b'}; +$z = "1" ?: ${'b'}; + +echo "-- 41 --\n"; +var_dump($a0 ?: "bar"); +$a0 ?: "bar"; +$z = $a0 ?: "bar"; + +echo "-- 42 --\n"; +var_dump($a0 ?: "c"."d"); +$a0 ?: "c"."d"; +$z = $a0 ?: "c"."d"; + +echo "-- 43 --\n"; +var_dump($a0 ?: $b); +$a0 ?: $b; +$z = $a0 ?: $b; + +echo "-- 44 --\n"; +var_dump($a0 ?: ${'b'}); +$a0 ?: ${'b'}; +$z = $a0 ?: ${'b'}; + +echo "-- 45 --\n"; +var_dump($a1 ?: "bar"); +$a1 ?: "bar"; +$z = $a1 ?: "bar"; + +echo "-- 46 --\n"; +var_dump($a1 ?: "c"."d"); +$a1 ?: "c"."d"; +$z = $a1 ?: "c"."d"; + +echo "-- 47 --\n"; +var_dump($a1 ?: $b); +$a1 ?: $b; +$z = $a1 ?: $b; + +echo "-- 48 --\n"; +var_dump($a1 ?: ${'b'}); +$a1 ?: ${'b'}; +$z = $a1 ?: ${'b'}; + +echo "-- 49 --\n"; +var_dump(${'a0'} ?: "bar"); +${'a0'} ?: "bar"; +$z = ${'a0'} ?: "bar"; + +echo "-- 50 --\n"; +var_dump(${'a0'} ?: "c"."d"); +${'a0'} ?: "c"."d"; +$z = ${'a0'} ?: "c"."d"; + +echo "-- 51 --\n"; +var_dump(${'a0'} ?: $b); +${'a0'} ?: $b; +$z = ${'a0'} ?: $b; + +echo "-- 52 --\n"; +var_dump(${'a0'} ?: ${'b'}); +${'a0'} ?: ${'b'}; +$z = ${'a0'} ?: ${'b'}; + +echo "-- 53 --\n"; +var_dump(${'a1'} ?: "bar"); +${'a1'} ?: "bar"; +$z = ${'a1'} ?: "bar"; + +echo "-- 54 --\n"; +var_dump(${'a1'} ?: "c"."d"); +${'a1'} ?: "c"."d"; +$z = ${'a1'} ?: "c"."d"; + +echo "-- 55 --\n"; +var_dump(${'a1'} ?: $b); +${'a1'} ?: $b; +$z = ${'a1'} ?: $b; + +echo "-- 56 --\n"; +var_dump(${'a1'} ?: ${'b'}); +${'a1'} ?: ${'b'}; +$z = ${'a1'} ?: ${'b'}; + +--EXPECT-- +-- 1 -- +string(3) "foo" +-- 2 -- +string(3) "foo" +-- 3 -- +string(3) "foo" +-- 4 -- +string(3) "foo" +-- 5 -- +string(2) "ab" +-- 6 -- +string(2) "ab" +-- 7 -- +string(2) "ab" +-- 8 -- +string(2) "ab" +-- 9 -- +string(8) "val_of_a" +-- 10 -- +string(8) "val_of_a" +-- 11 -- +string(8) "val_of_a" +-- 12 -- +string(8) "val_of_a" +-- 13 -- +string(8) "val_of_a" +-- 14 -- +string(8) "val_of_a" +-- 15 -- +string(8) "val_of_a" +-- 16 -- +string(8) "val_of_a" +-- 17 -- +string(3) "bar" +-- 18 -- +string(2) "cd" +-- 19 -- +string(8) "val_of_b" +-- 20 -- +string(8) "val_of_b" +-- 21 -- +string(3) "bar" +-- 22 -- +string(2) "cd" +-- 23 -- +string(8) "val_of_b" +-- 24 -- +string(8) "val_of_b" +-- 25 -- +string(3) "bar" +-- 26 -- +string(2) "cd" +-- 27 -- +string(8) "val_of_b" +-- 28 -- +string(8) "val_of_b" +-- 29 -- +string(3) "bar" +-- 30 -- +string(2) "cd" +-- 31 -- +string(8) "val_of_b" +-- 32 -- +string(8) "val_of_b" +-- 33 -- +string(3) "bar" +-- 34 -- +string(2) "cd" +-- 35 -- +string(8) "val_of_b" +-- 36 -- +string(8) "val_of_b" +-- 37 -- +string(1) "1" +-- 38 -- +string(1) "1" +-- 39 -- +string(1) "1" +-- 40 -- +string(1) "1" +-- 41 -- +string(3) "bar" +-- 42 -- +string(2) "cd" +-- 43 -- +string(8) "val_of_b" +-- 44 -- +string(8) "val_of_b" +-- 45 -- +array(1) { + [0]=> + int(1) +} +-- 46 -- +array(1) { + [0]=> + int(1) +} +-- 47 -- +array(1) { + [0]=> + int(1) +} +-- 48 -- +array(1) { + [0]=> + int(1) +} +-- 49 -- +string(3) "bar" +-- 50 -- +string(2) "cd" +-- 51 -- +string(8) "val_of_b" +-- 52 -- +string(8) "val_of_b" +-- 53 -- +array(1) { + [0]=> + int(1) +} +-- 54 -- +array(1) { + [0]=> + int(1) +} +-- 55 -- +array(1) { + [0]=> + int(1) +} +-- 56 -- +array(1) { + [0]=> + int(1) +} diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index f8fe4ef..221d4f9 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1469,7 +1469,8 @@ void zend_do_free(znode *op1 TSRMLS_DC) /* {{{ */ && opline->result.var == op1->u.op.var) { if (opline->opcode == ZEND_FETCH_R || opline->opcode == ZEND_FETCH_DIM_R || - opline->opcode == ZEND_FETCH_OBJ_R) { + opline->opcode == ZEND_FETCH_OBJ_R || + opline->opcode == ZEND_QM_ASSIGN_VAR) { /* It's very rare and useless case. It's better to use additional FREE opcode and simplify the FETCH handlers their selves */ @@ -6308,8 +6309,13 @@ void zend_do_jmp_set(const znode *value, znode *jmp_token, znode *colon_token TS int op_number = get_next_op_number(CG(active_op_array)); zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC); - opline->opcode = ZEND_JMP_SET; - opline->result_type = IS_TMP_VAR; + if (value->op_type == IS_VAR || value->op_type == IS_CV) { + opline->opcode = ZEND_JMP_SET_VAR; + opline->result_type = IS_VAR; + } else { + opline->opcode = ZEND_JMP_SET; + opline->result_type = IS_TMP_VAR; + } opline->result.var = get_temporary_variable(CG(active_op_array)); SET_NODE(opline->op1, value); SET_UNUSED(opline->op2); @@ -6326,9 +6332,20 @@ void zend_do_jmp_set_else(znode *result, const znode *false_value, const znode * { zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC); - opline->opcode = ZEND_QM_ASSIGN; - opline->extended_value = 0; SET_NODE(opline->result, colon_token); + if (colon_token->op_type == IS_TMP_VAR) { + if (false_value->op_type == IS_VAR || false_value->op_type == IS_CV) { + CG(active_op_array)->opcodes[jmp_token->u.op.opline_num].opcode = ZEND_JMP_SET_VAR; + CG(active_op_array)->opcodes[jmp_token->u.op.opline_num].result_type = IS_VAR; + opline->opcode = ZEND_QM_ASSIGN_VAR; + opline->result_type = IS_VAR; + } else { + opline->opcode = ZEND_QM_ASSIGN; + } + } else { + opline->opcode = ZEND_QM_ASSIGN_VAR; + } + opline->extended_value = 0; SET_NODE(opline->op1, false_value); SET_UNUSED(opline->op2); @@ -6363,8 +6380,13 @@ void zend_do_qm_true(const znode *true_value, znode *qm_token, znode *colon_toke CG(active_op_array)->opcodes[qm_token->u.op.opline_num].op2.opline_num = get_next_op_number(CG(active_op_array))+1; /* jmp over the ZEND_JMP */ - opline->opcode = ZEND_QM_ASSIGN; - opline->result_type = IS_TMP_VAR; + if (true_value->op_type == IS_VAR || true_value->op_type == IS_CV) { + opline->opcode = ZEND_QM_ASSIGN_VAR; + opline->result_type = IS_VAR; + } else { + opline->opcode = ZEND_QM_ASSIGN; + opline->result_type = IS_TMP_VAR; + } opline->result.var = get_temporary_variable(CG(active_op_array)); SET_NODE(opline->op1, true_value); SET_UNUSED(opline->op2); @@ -6383,8 +6405,19 @@ void zend_do_qm_false(znode *result, const znode *false_value, const znode *qm_t { zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC); - opline->opcode = ZEND_QM_ASSIGN; SET_NODE(opline->result, qm_token); + if (qm_token->op_type == IS_TMP_VAR) { + if (false_value->op_type == IS_VAR || false_value->op_type == IS_CV) { + CG(active_op_array)->opcodes[colon_token->u.op.opline_num - 1].opcode = ZEND_QM_ASSIGN_VAR; + CG(active_op_array)->opcodes[colon_token->u.op.opline_num - 1].result_type = IS_VAR; + opline->opcode = ZEND_QM_ASSIGN_VAR; + opline->result_type = IS_VAR; + } else { + opline->opcode = ZEND_QM_ASSIGN; + } + } else { + opline->opcode = ZEND_QM_ASSIGN_VAR; + } SET_NODE(opline->op1, false_value); SET_UNUSED(opline->op2); diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 5ae15f4..888058d 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -1299,6 +1299,7 @@ void execute_new_code(TSRMLS_D) /* {{{ */ case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: case ZEND_JMP_SET: + case ZEND_JMP_SET_VAR: opline->op2.jmp_addr = &CG(active_op_array)->opcodes[opline->op2.opline_num]; break; } diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 9d4f3e7..65b9aa5 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -529,6 +529,7 @@ ZEND_API int pass_two(zend_op_array *op_array TSRMLS_DC) case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: case ZEND_JMP_SET: + case ZEND_JMP_SET_VAR: opline->op2.jmp_addr = &op_array->opcodes[opline->op2.opline_num]; break; } diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 4e944a1..238864e 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -4661,8 +4661,45 @@ ZEND_VM_HANDLER(152, ZEND_JMP_SET, CONST|TMP|VAR|CV, ANY) if (i_zend_is_true(value)) { ZVAL_COPY_VALUE(&EX_T(opline->result.var).tmp_var, value); - zendi_zval_copy_ctor(EX_T(opline->result.var).tmp_var); - FREE_OP1(); + if (!IS_OP1_TMP_FREE()) { + zendi_zval_copy_ctor(EX_T(opline->result.var).tmp_var); + } + FREE_OP1_IF_VAR(); +#if DEBUG_ZEND>=2 + printf("Conditional jmp to %d\n", opline->op2.opline_num); +#endif + ZEND_VM_JMP(opline->op2.jmp_addr); + } + + FREE_OP1(); + CHECK_EXCEPTION(); + ZEND_VM_NEXT_OPCODE(); +} + +ZEND_VM_HANDLER(158, ZEND_JMP_SET_VAR, CONST|TMP|VAR|CV, ANY) +{ + USE_OPLINE + zend_free_op free_op1; + zval *value, *ret; + + SAVE_OPLINE(); + value = GET_OP1_ZVAL_PTR(BP_VAR_R); + + if (i_zend_is_true(value)) { + if (OP1_TYPE == IS_VAR || OP1_TYPE == IS_CV) { + Z_ADDREF_P(value); + EX_T(opline->result.var).var.ptr = value; + EX_T(opline->result.var).var.ptr_ptr = NULL; + } else { + ALLOC_ZVAL(ret); + INIT_PZVAL_COPY(ret, value); + EX_T(opline->result.var).var.ptr = ret; + EX_T(opline->result.var).var.ptr_ptr = NULL; + if (!IS_OP1_TMP_FREE()) { + zval_copy_ctor(EX_T(opline->result.var).var.ptr); + } + } + FREE_OP1_IF_VAR(); #if DEBUG_ZEND>=2 printf("Conditional jmp to %d\n", opline->op2.opline_num); #endif @@ -4692,6 +4729,34 @@ ZEND_VM_HANDLER(22, ZEND_QM_ASSIGN, CONST|TMP|VAR|CV, ANY) ZEND_VM_NEXT_OPCODE(); } +ZEND_VM_HANDLER(157, ZEND_QM_ASSIGN_VAR, CONST|TMP|VAR|CV, ANY) +{ + USE_OPLINE + zend_free_op free_op1; + zval *value, *ret; + + SAVE_OPLINE(); + value = GET_OP1_ZVAL_PTR(BP_VAR_R); + + if (OP1_TYPE == IS_VAR || OP1_TYPE == IS_CV) { + Z_ADDREF_P(value); + EX_T(opline->result.var).var.ptr = value; + EX_T(opline->result.var).var.ptr_ptr = NULL; + } else { + ALLOC_ZVAL(ret); + INIT_PZVAL_COPY(ret, value); + EX_T(opline->result.var).var.ptr = ret; + EX_T(opline->result.var).var.ptr_ptr = NULL; + if (!IS_OP1_TMP_FREE()) { + zval_copy_ctor(EX_T(opline->result.var).var.ptr); + } + } + + FREE_OP1_IF_VAR(); + CHECK_EXCEPTION(); + ZEND_VM_NEXT_OPCODE(); +} + ZEND_VM_HANDLER(101, ZEND_EXT_STMT, ANY, ANY) { SAVE_OPLINE(); diff --git a/Zend/zend_vm_opcodes.h b/Zend/zend_vm_opcodes.h index ed80ddc..9b76b0e 100644 --- a/Zend/zend_vm_opcodes.h +++ b/Zend/zend_vm_opcodes.h @@ -157,3 +157,5 @@ #define ZEND_ADD_TRAIT 154 #define ZEND_BIND_TRAITS 155 #define ZEND_SEPARATE 156 +#define ZEND_QM_ASSIGN_VAR 157 +#define ZEND_JMP_SET_VAR 158
-- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: http://www.php.net/unsub.php