In this question a code bit is presented and the questioner wants to make it faster by eliminating the use of variables. Seems to me he's looking in the wrong place, but far be it from me to know. Here's the code
while ($item = current($data))
{
echo '<ATTR>',$item, '</ATTR>', "\n";
next($data);
}
Seems to me that the recreation of the strings <ATTR> etc. -- more than once on each line and every time the line is processed -- would have a cost associated with them (both in terms of speed and memory). Or perhaps the PHP processor smart enough so that there's no penalty to not putting the strings into variables before the loop?
I use variables for clarity and centralization in any case, but: is there a cost associated with using variables, not using variables, or what? (Anybody who wants to answer for other similar languages please feel free.)
If you really want to micro-optimize this way (I don't think it is that relevant or useful, btw -- but I understand it's fun ^^ ), you can have a look at a PHP extension called Vulcan Logic Disassembler
It allows you to get the bytecode generated for a PHP script.
Then, you must use a command like this one, in command line, to launch the script :
php -dextension=vld.so -dvld.active=1 tests/temp/temp.php
For instance, with this script :
$data = array('a', 'b', 'c', 'd');
while ($item = current($data))
{
echo '<ATTR>',$item, '</ATTR>', "\n";
next($data);
}
You will get this bytecode dump :
line # op fetch ext return operands
-------------------------------------------------------------------------------
8 0 EXT_STMT
1 INIT_ARRAY ~0 'a'
2 ADD_ARRAY_ELEMENT ~0 'b'
3 ADD_ARRAY_ELEMENT ~0 'c'
4 ADD_ARRAY_ELEMENT ~0 'd'
5 ASSIGN !0, ~0
9 6 EXT_STMT
7 EXT_FCALL_BEGIN
8 SEND_REF !0
9 DO_FCALL 1 'current'
10 EXT_FCALL_END
11 ASSIGN $3 !1, $2
12 JMPZ $3, ->24
11 13 EXT_STMT
14 ECHO '%3CATTR%3E'
15 ECHO !1
16 ECHO '%3C%2FATTR%3E'
17 ECHO '%0A'
12 18 EXT_STMT
19 EXT_FCALL_BEGIN
20 SEND_REF !0
21 DO_FCALL 1 'next'
22 EXT_FCALL_END
13 23 JMP ->7
37 24 RETURN 1
25* ZEND_HANDLE_EXCEPTION
And with this script :
$data = array('a', 'b', 'c', 'd');
while ($item = current($data))
{
echo "<ATTR>$item</ATTR>\n";
next($data);
}
You will get :
line # op fetch ext return operands
-------------------------------------------------------------------------------
19 0 EXT_STMT
1 INIT_ARRAY ~0 'a'
2 ADD_ARRAY_ELEMENT ~0 'b'
3 ADD_ARRAY_ELEMENT ~0 'c'
4 ADD_ARRAY_ELEMENT ~0 'd'
5 ASSIGN !0, ~0
20 6 EXT_STMT
7 EXT_FCALL_BEGIN
8 SEND_REF !0
9 DO_FCALL 1 'current'
10 EXT_FCALL_END
11 ASSIGN $3 !1, $2
12 JMPZ $3, ->25
22 13 EXT_STMT
14 INIT_STRING ~4
15 ADD_STRING ~4 ~4, '%3CATTR%3E'
16 ADD_VAR ~4 ~4, !1
17 ADD_STRING ~4 ~4, '%3C%2FATTR%3E%0A'
18 ECHO ~4
23 19 EXT_STMT
20 EXT_FCALL_BEGIN
21 SEND_REF !0
22 DO_FCALL 1 'next'
23 EXT_FCALL_END
24 24 JMP ->7
39 25 RETURN 1
26* ZEND_HANDLE_EXCEPTION
(This ouput is with PHP 5.2.6, which is the default on Ubuntu Jaunty)
In the end , you will probably notice there is not that much differences, and that it's often really just micro-optimisation ^^
What might be more interesting is to look at the differences between versions of PHP : you might seen that some operations have been optimized between PHP 5.1 and 5.2, for instance.
For more informations, you can also have a look at Understanding Opcodes
Have fun !
EDIT : adding another test :
With this code :
$attr_open = '<ATTR>';
$attr_close = '</ATTR>';
$eol = "\n";
$data = array('a', 'b', 'c', 'd');
while ($item = current($data))
{
echo $attr_open, $item, $attr_close, $eol;
next($data);
}
You get :
line # op fetch ext return operands
-------------------------------------------------------------------------------
19 0 EXT_STMT
1 ASSIGN !0, '%3CATTR%3E'
20 2 EXT_STMT
3 ASSIGN !1, '%3C%2FATTR%3E'
21 4 EXT_STMT
5 ASSIGN !2, '%0A'
23 6 EXT_STMT
7 INIT_ARRAY ~3 'a'
8 ADD_ARRAY_ELEMENT ~3 'b'
9 ADD_ARRAY_ELEMENT ~3 'c'
10 ADD_ARRAY_ELEMENT ~3 'd'
11 ASSIGN !3, ~3
24 12 EXT_STMT
13 EXT_FCALL_BEGIN
14 SEND_REF !3
15 DO_FCALL 1 'current'
16 EXT_FCALL_END
17 ASSIGN $6 !4, $5
18 JMPZ $6, ->30
26 19 EXT_STMT
20 ECHO !0
21 ECHO !4
22 ECHO !1
23 ECHO !2
27 24 EXT_STMT
25 EXT_FCALL_BEGIN
26 SEND_REF !3
27 DO_FCALL 1 'next'
28 EXT_FCALL_END
28 29 JMP ->13
43 30 RETURN 1
31* ZEND_HANDLE_EXCEPTION
And, with this one (concatenations instead of ',') :
$attr_open = '<ATTR>';
$attr_close = '</ATTR>';
$eol = "\n";
$data = array('a', 'b', 'c', 'd');
while ($item = current($data))
{
echo $attr_open . $item . $attr_close . $eol;
next($data);
}
you get :
line # op fetch ext return operands
-------------------------------------------------------------------------------
19 0 EXT_STMT
1 ASSIGN !0, '%3CATTR%3E'
20 2 EXT_STMT
3 ASSIGN !1, '%3C%2FATTR%3E'
21 4 EXT_STMT
5 ASSIGN !2, '%0A'
23 6 EXT_STMT
7 INIT_ARRAY ~3 'a'
8 ADD_ARRAY_ELEMENT ~3 'b'
9 ADD_ARRAY_ELEMENT ~3 'c'
10 ADD_ARRAY_ELEMENT ~3 'd'
11 ASSIGN !3, ~3
24 12 EXT_STMT
13 EXT_FCALL_BEGIN
14 SEND_REF !3
15 DO_FCALL 1 'current'
16 EXT_FCALL_END
17 ASSIGN $6 !4, $5
18 JMPZ $6, ->30
26 19 EXT_STMT
20 CONCAT ~7 !0, !4
21 CONCAT ~8 ~7, !1
22 CONCAT ~9 ~8, !2
23 ECHO ~9
27 24 EXT_STMT
25 EXT_FCALL_BEGIN
26 SEND_REF !3
27 DO_FCALL 1 'next'
28 EXT_FCALL_END
28 29 JMP ->13
43 30 RETURN 1
31* ZEND_HANDLE_EXCEPTION
So, never much of a difference ^^
Here is an interesting one, my initial tests show that storing the newline char into a variable instead of PHP parsing it with each iteration is faster. See below:
$nl = "\n";
while ($item = current($data))
{
echo '<ATTR>',$item, '</ATTR>',$nl;
next($data);
}
There seems to be no measurable difference in using the string literals inside the loop vs. moving them to variables outside the loop. I threw together the following simple script to test this:
$length = 100000;
$data = array();
$totals = array();
for ($i = 0; $i < $length; $i++) {
$data[] = rand(1,1000);
}
$start = xdebug\_time\_index();
while ($item = current($data))
{
echo '<ATTR>',$item,'</ATTR>',PHP_EOL;
next($data);
}
$end = xdebug\_time\_index();
$total = $end - $start;
$totals["Warmup:"] = $total;
reset($data);
$start = xdebug\_time\_index();
while ($item = current($data))
{
echo '<ATTR>',$item,'</ATTR>',PHP_EOL;
next($data);
}
$end = xdebug\_time\_index();
$total = $end - $start;
$totals["First:"] = $total;
reset($data);
$startTag = '<ATTR>';
$endTag = '</ATTR>';
$start = xdebug\_time\_index();
while ($item = current($data))
{
echo $startTag,$item,$endTag,PHP_EOL;
next($data);
}
$end = xdebug\_time\_index();
$total = $end - $start;
$totals["Second:"] = $total;
foreach ($totals as $label => $data) {
echo $label,' ', $data,PHP_EOL;
}
I ran this several times and saw no discernable difference between the differing methods. In fact, sometimes the warmup was the fastest of the three.
When trying to microoptimize things such as this you really end up measuring the performance of the machine you are on more often than the actual code. Of note, you may want to use PHP_EOL instead of \n or defining a variable containing such.
Actually this is probably the fastest implementation. You could try to concat all in to one string but all of the concat operations are pretty expensive.
Everything has a cost. The goal is to minimize that cost as much as possible.
If you were thinking about concatenation check this resource for information on its performance. It's probably best to leave the code as-is.
If you really want to speed this up, use this instead:
ob_start();
while ($item = current($data))
{
echo '<ATTR>',$item, '</ATTR>', "\n";
next($data);
}
Output buffering flushes content more efficiently to the client, which speeds up your code much more than any micro-optimization can.
As an aside, in my experience micro-optimization is a useless endeavour when it comes to PHP code. I've never seen a performance problem get solved by clever use of a particular concatenation or variable declaration method. Real solutions tend to involve change to design or architecture or the use of less complicated algorithms.
Related
Let's say I run the following code :
function isLucky() : bool
{
for ($i = 0; $i < 50; ++$i) {
try {
if (!rand(0, 9)) {
return true;
};
throw new Exception();
} catch (Exception $e) {
}
}
}
Some software (Vulcan Logic Dumper) gets me generated opcode:
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
5 0 E > ASSIGN !0, 0
1 > JMP ->15
7 2 > INIT_FCALL 'rand'
3 SEND_VAL 0
4 SEND_VAL 9
5 DO_ICALL $3
6 BOOL_XOR ~4 $3
7 > JMPZ ~4, ->9
8 8 > > RETURN <true>
10 9 > NEW $5 :20
10 DO_FCALL 0
11 > THROW 0 $5
12* JMP ->14
11 13 E > > CATCH last 'Exception'
5 14 > PRE_INC !0
15 > IS_SMALLER_OR_EQUAL ~8 !0, 50
16 > JMPNZ ~8, ->2
14 17 > VERIFY_RETURN_TYPE
18 > RETURN null
This is nice, but is there a way to get what the system really does ?
Is this zend framework re-interpreting each token ?
Where actually are the system calls ?
Do each opcode instruction cost the same ?
When I check the generated output of a C++ program with objdump, a program is a list of instructions, and jumps are are made to a memory adress.
A dummy c++ function from a c++ file compiled with -O0 and -c objdump-ed:
0000000000000000 <_Z14dummy_functionb>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 89 f8 mov %edi,%eax
6: 88 45 fc mov %al,-0x4(%rbp)
9: 80 7d fc 00 cmpb $0x0,-0x4(%rbp)
d: 74 07 je 16 <_Z14dummy_functionb+0x16>
f: b8 01 00 00 00 mov $0x1,%eax
14: eb 05 jmp 1b <_Z14dummy_functionb+0x1b>
16: b8 00 00 00 00 mov $0x0,%eax
1b: 5d pop %rbp
1c: c3 retq
Is that the case with zend's opcode ?
For instance, assuming a simple function :
<?php
(function (){
return true;
throw new Exception();
});
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
4 0 E > > RETURN <true>
5 1* NEW $0 :4
2* DO_FCALL 0
3* THROW 0 $0
6 4* > RETURN null
Will the throw expression ever be read by something ? Or will the jump completely ignore it ?
Opcodes are executed by the Zend executor. If you want to know how it works exactly, you need to read its source files.
You will find a general presentation here:
http://blog.jpauli.tech/2015-02-05-zend-vm-executor-html/
<?php
tree($Comments, 0, 0);
$var = -1;
function tree($Comments, $parent_id = 0, $level=0) {
global $var;
foreach($Comments as $Comment){
if($Comment['parent_id'] == $parent_id) {
If ($level > $var) {$var++;} else {echo ($var-$level+1); $var=$level;};
for ($i=$level; $i>0; $i--)
echo "-";
echo $Comment['text']."<br>";
tree($Comments, $Comment['id'], $level+1);
}
}
}
?>
This is my recursive method of displaying the tree of comments(thread comments),
the problem is I have to make, in that (if ($level > $var) statement get variable $var of how many (</div> for a bootstrap design) I should use for making the design for the comment threads. The problem is that I get first result 1, which supposedly I shouldn't, the rest gets as It should have to.
Edit 1, Example of data in database(to easy understand):
id id_theme parent_id user text
16 14 0 DomainFlag This is 1!
17 14 16 DomainFlag This is 2!
18 14 16 DomainFlag This is 3!
20 14 17 DomainFlag This is 4!
21 14 17 DomainFlag This is 5!
22 14 21 DomainFlag This is 7!
23 14 22 DomainFlag This is 8!
24 14 18 DomainFlag This is 6!
25 14 0 DomainFlag This is 9!
26 14 25 DomainFlag This is 10!
27 14 25 DomainFlag This is 11!
My table:
id
parent_id
id_theme
user
text
upVotes
downVotes
My idea is to make a threaded system of comments like(with $level which I get from the recursive function):
0
1
2
3
2
1
1
0
1
0
So I get this result with that 1 at first no matter what, which I shouldn't( 1 This is 1!). The rest is working perfectly.
1This is 1!
-This is 2!
--This is 4!
1--This is 5!
---This is 7!
----This is 8!
4-This is 3!
--This is 6!
3This is 9!
-This is 10!
1-This is 11!
Why that 1 at the beginning appears? I heard that global scopes isn't good to implement, why? and what I should do to not use it?
I have to write numbers in groups of 1000.
so, if I have 7000, I want to write:
1 2 3 4 5 6 7
but If I have 7001 or more, I want to write:
1 2 3 4 5 6 7 8
php
$num = 22501;
$num = round($num/1000);
for ($i=1; $i<=$num; $i++){
echo $i;
}
if my number is 22501 I will write 1 to 23. but if it is 22001 it will write 1 to 22.
I want to write 1 to 23. how can I do this?
This question already has answers here:
Stacking Multiple Ternary Operators in PHP
(11 answers)
Closed 2 years ago.
I was in class the other day and this snippet of code was presented:
<?php
//Intialize the input
$score=rand(50,100);
//Determine the Grade
$grade=($score>=90)?'A':(
($score>=80)?'B':(
($score>=70)?'C':(
($score>=60)?'D':'F')));
//Output the Results
echo "<h1>A score of $score = $grade</h1>";
?>
At the time I questioned the order of operations within the nested ternary operators, thinking that they would evaluate from the inside out, that is it would evaluate if $score were >= 60 first, then if $score >= 70, etc -- working through the whole stack every time regardless of the score.
To me it seems that this construct should follow the same order of precedence given to mathematical operators -- resolving the inner-most set of parentheses first and then working out, unless there is some order of operations unique to the ternary.
Unfortunately the discussion in class quickly became about winning an argument, when I really just wanted to understand how it works. So my questions are two:
(1)How would this statement me interpreted and why?
and
(2)Is the some sort of stack trace or step through tool that would allow me to watch how this code executes?
PHP respects brackets. Expressions inside the innermost ( ... ) are evaluated first, like we are taught in elementary school.
PHP is unusual in that ternary operators are left-associative. This means without brackets, the ternary expression is evaluated left to right.
But in this particular case, the brackets force the expression to be evaluated right to left. This code is equivalent to:
if ($score >= 90) {
$grade = 'A';
}
elseif ($score >= 80) {
$grade = 'B';
}
elseif ($score >= 70) {
$grade = 'C';
}
...
The ternary operator is left associative, but with brackets applied, it evaluates right to left.
You might use xdebug or phpdbg as a step debugger to step through your code and see how it evaluates.
There is also VulcanLogicDumper around, which shows the instructions:
http://3v4l.org/QeF9i/vld#tabs compared to an if-elseif-else structure http://3v4l.org/bZE6M/vld#tabs
line # * op fetch ext return operands
---------------------------------------------------------------------------------
3 0 > SEND_VAL 50
1 SEND_VAL 100
2 DO_FCALL 2 $0 'rand'
3 ASSIGN !0, $0
5 4 IS_SMALLER_OR_EQUAL ~2 90, !0
5 > JMPZ ~2, ->8
6 > QM_ASSIGN ~3 'A'
7 > JMP ->24
6 8 > IS_SMALLER_OR_EQUAL ~4 80, !0
9 > JMPZ ~4, ->12
10 > QM_ASSIGN ~5 'B'
11 > JMP ->23
7 12 > IS_SMALLER_OR_EQUAL ~6 70, !0
13 > JMPZ ~6, ->16
14 > QM_ASSIGN ~7 'C'
15 > JMP ->22
8 16 > IS_SMALLER_OR_EQUAL ~8 60, !0
17 > JMPZ ~8, ->20
18 > QM_ASSIGN ~9 'D'
19 > JMP ->21
20 > QM_ASSIGN ~9 'F'
21 > QM_ASSIGN ~7 ~9
22 > QM_ASSIGN ~5 ~7
23 > QM_ASSIGN ~3 ~5
24 > ASSIGN !1, ~3
10 25 ADD_STRING ~11 '%3Ch1%3EA+score+of+'
26 ADD_VAR ~11 ~11, !0
27 ADD_STRING ~11 ~11, '+%3D+'
28 ADD_VAR ~11 ~11, !1
29 ADD_STRING ~11 ~11, '%3C%2Fh1%3E'
30 ECHO ~11
31 > RETURN 1
How to read these Opcodes
I will try to explain the first JMPZ in the opcode, in order to understand how it evaluates:
Of interest is Line 5, Opcode Number 5:
5 > JMPZ ~2, ->8
This means: If compare with 90 (opcode 4) is false, then JUMP to Opcode 8.
Warning: ->8 doesn't mean jump to Line 8.
Now, what is Opcode 8? The comparison with 80
6 8 > IS_SMALLER_OR_EQUAL ~4 80, !0
And now it's safe to say that this doesn't evaluate, like you expected from inside out (90->60->70), but like an if-elseif-else structure (90->80->70).
The ternary operator short-circuits. Only the appropriate operand is evaluated. This means that the parens do not matter until they are actually tested.
echo false ? (crash() / 0) : "Worked.";
ternary are left to right:
http://php.net/manual/en/language.operators.comparison.php#language.operators.comparison.ternary
So it will evaluate what is to the left of the ? and based on that, evaluate either the 1st or 2nd side of the :.
You could put in a function call that has a side effect to demonstrate this:
function p($a,$b) { echo $a . " >= " . $b; return $a>=$b; }
$grade=(p($score,90))?'A':(
p($score,80)?'B':(
p($score,70)?'C':(
p($score,60)?'D':'F')));
I'm trying to understand the op code for an simple code.
The code is:
<?php
$a = TRUE;
$b = FALSE;
if($a && $b) {
echo 'done';
}
The op code for the above code is:
php -dvld.active=1 test.php
Finding entry points
Branch analysis from position: 0
Jump found. Position 1 = 3, Position 2 = 4
Branch analysis from position: 3
Jump found. Position 1 = 5, Position 2 = 7
Branch analysis from position: 5
Jump found. Position 1 = 7
Branch analysis from position: 7
Return found
Branch analysis from position: 7
Branch analysis from position: 4
filename: /home/starlays/learning/test.php
function name: (null)
number of ops: 8
compiled vars: !0 = $a, !1 = $b
line # * op fetch ext return operands
---------------------------------------------------------------------------------
3 0 > ASSIGN !0, true
5 1 ASSIGN !1, false
7 2 > JMPZ_EX ~2 !0, ->4
3 > BOOL ~2 !1
4 > > JMPZ ~2, ->7
8 5 > ECHO 'done'
9 6 > JMP ->7
10 7 > > RETURN 1
branch: # 0; line: 3- 7; sop: 0; eop: 2; out1: 3; out2: 4
branch: # 3; line: 7- 7; sop: 3; eop: 3; out1: 4
branch: # 4; line: 7- 7; sop: 4; eop: 4; out1: 5; out2: 7
branch: # 5; line: 8- 9; sop: 5; eop: 6; out1: 7
branch: # 7; line: 10- 10; sop: 7; eop: 7
path #1: 0, 3, 4, 5, 7,
path #2: 0, 3, 4, 7,
path #3: 0, 4, 5, 7,
path #4: 0, 4, 7,
I'm trying to understand what is happening on line 7, how is the evaluation done? How many values does it enter in the expression of if for evaluation? It enters 3 values, or it enters the 2 values the value of $a and value of $b and the expression from the parentheses of if is evaluated afterwards?
I have read the manual for JMPZ_EX, I've understand what is happening in the op code until step 2 after that is a little bit mixed up and it is very hard to me to understand what are exact steps that php is doing.
Another thing that I need to understand is what are all the branches in the op code, which of all that branches will be used at the end?
Unless you are proficient at ASM, I think the easiest way to understand what is happening is looking at the same code by reading its (almost) 1:1 representation in PHP:
if(!$a) goto end;
if(!$b) goto end;
echo 'done';
end: return 0;
The intermediate representation is based on the negations of your actual clauses to make a jump over the code contained in the if block.
If you want to really understand how PHP transforms its input to this opcode array, you'll have to learn about PHP internals, but not before studying the dragon book, particularily the parts about intermediate representation, which is part of the compilation pipeline.
The rest of the opcodes are "background noise", intermediate values, or even one instruction which makes no sense 9 6 > JMP ->7 which simply exists probably because it didn't make sense to put the effort into making the PHP parser spit out the most optimal opcode array for the ZendVM to be run by.
line # * op fetch ext return operands
---------------------------------------------------------------------------------
3 0 > ASSIGN !0, true
5 1 ASSIGN !1, false
7 2 > JMPZ_EX ~2 !0, ->4
3 > BOOL ~2 !1
4 > > JMPZ ~2, ->7
8 5 > ECHO 'done'
9 6 > JMP ->7
10 7 > > RETURN 1
Going by the line numbers #
0 assigns true to !0, !0 is just the internal representation of $A
1 assigns true to !1, !1 is $B
JMPZ means to jump to code if the value is 0. I'm not sure the specific difference of JMPZ_EX it looks like it allows the return of a boolean result.
So:
2 JMPZ_EX, Jump to #4 (->4) if !0 ($A) is 0 (FALSE) and assign the result to ~2
3 BOOL !1 return ~2. ~2 is now equal to the BOOLean value of !1 ($B)
4 JMPZ ~2, Jump to #7 if ~2 is zero
5 ECHO, our echo statement. If any of the JMPZ had jumped, this part would be skipped.
6 JMP -7, jumps to #7
7 RETURN, ends the function call
Some notes:
It seems like the JMPZ_EX is unnecessary in this case, but would be useful in more complex if statements where you need to use the value in calculating further values.
6 JMP -7 is probably in there to allow for an else block. If this was the main part of the if block, finishing it could then jump over the portion of code that was the else block.