develooper Front page | perl.perl5.porters | Postings from December 2007

optimising opcodes

Thread Next
Nicholas Clark
December 24, 2007 11:25
optimising opcodes
Message ID:
I was thinking about something Jim Cromie mailed me privately, and wondered:
"How many null ops do we have, and how many cache misses do they cause?"
For example:

$ perl -MO=Concise -e 'print foreach @ARGV' 
g  <@> leave[1 ref] vKP/REFC ->(end)
1     <0> enter ->2
2     <;> nextstate(main 1 -e:1) v ->3
3     <;> nextstate(main 1 -e:1) v ->4
f     <2> leaveloop vK/2 ->g
8        <{> enteriter(next->c last->f redo->9) lKS ->d
-           <0> ex-pushmark s ->4
-           <1> ex-list lKM ->7
4              <0> pushmark sM ->5
6              <1> rv2av[t1] sKRM/1 ->7
5                 <$> gv(*ARGV) s ->6
7           <$> gv(*_) s ->8
-        <1> null vK/1 ->f
e           <|> and(other->9) vK/1 ->f
d              <0> iter s ->e
-              <@> lineseq vK ->-
b                 <@> print vK ->c
9                    <0> pushmark s ->a
-                    <1> ex-rv2sv sK/1 ->b
a                       <$> gvsv(*_) s ->b
c                 <0> unstack v ->d

lots of ex-ops. Pining for the fjords. Taking up space.

So I wondered:

0: How hard would it be to write some code to take an optree, and copy out all
   the live ops into a new optree, skipping any nulled out ops?
1: Would that break the runtime?
2: How much would it break B::Deparse?
3: If you configured perl to use a slab allocator, and happened to do two
   parses (first to determine the total size of ops, second to copy them in)
   and thereby allocated a slab of the exact size, would that help more?
4: If you copied them in execution order, would that help more?

$ perl -MO=Concise,-exec -e 'print foreach @ARGV'
1  <0> enter 
2  <;> nextstate(main 1 -e:1) v
3  <;> nextstate(main 1 -e:1) v
4  <0> pushmark sM
5  <$> gv(*ARGV) s
6  <1> rv2av[t1] sKRM/1
7  <$> gv(*_) s
8  <{> enteriter(next->c last->f redo->9) lKS
d  <0> iter s
e  <|> and(other->9) vK/1
9      <0> pushmark s
a      <$> gvsv(*_) s
b      <@> print vK
c      <0> unstack v
           goto d
f  <2> leaveloop vK/2
g  <@> leave[1 ref] vKP/REFC

[note, by doing things with a slab allocator, I believe that even though the
transformed ops are contiguous in memory, they are also indistinguishable from
regular ops created by the perl compiler]

5: How do you spot code executed only once, and avoid doing this?
   (main program, eval blocks, top level code from do/require/use,
    BEGIN/UNITCHECK/CHECK/INIT/END - ie basically anything not a "real"
    subroutine definition)
6: Is the op_opt flag bit free at this point? Does having a bit help?

#define BASEOP				\
    OP*		op_next;		\
    OP*		op_sibling;		\
    OP*		(CPERLscope(*op_ppaddr))(pTHX);		\
    PADOFFSET	op_targ;		\
    unsigned	op_type:9;		\
    unsigned	op_opt:1;		\

and for bonus points - why are there two nextstate ops in that example, and
can we optimise one out? How often does it happen?

Nicholas Clark

Thread Next Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at | Group listing | About