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

optimising opcodes

Thread Next
From:
Nicholas Clark
Date:
December 24, 2007 11:25
Subject:
optimising opcodes
Message ID:
20071224192518.GR20876@plum.flirble.org
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?

#ifdef BASEOP_DEFINITION
#define BASEOP BASEOP_DEFINITION
#else
#define BASEOP				\
    OP*		op_next;		\
    OP*		op_sibling;		\
    OP*		(CPERLscope(*op_ppaddr))(pTHX);		\
    MADPROP_IN_BASEOP			\
    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


nntp.perl.org: Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at ask@perl.org | Group listing | About