On Fri, Aug 02, 2002 at 09:06:31PM +0100, Nicholas Clark wrote:
> On Fri, Aug 02, 2002 at 06:43:49PM +0200, Jerome Vouillon wrote:
> > Allocating a hash will certainly remain a lot more expensive than
> > allocating an array.  And we are going to allocate pads pretty
> > often...
> 
> Are we? Or are we just going to copy them a lot at runtime?
> [but actually allocate the beasts only at compile time, or when someone
> mucks around with them using string eval or %MY]

When we enter a block we need to:
- create a new pad
- copy the current pad array and add the new pad to the copy
- allocate a PMC for each lexical variable introduced in the block and
  insert the PMCs in the new pad
The creation of the pad could be done either by copying a template
pad.  This would not make any difference compared to allocating from
scratch for an array, but this must be faster for a hash.  Still, I
think using an array will remain faster.

> > I really hope %MY and caller.MY will not be able to extend the lexical
> > scope: we would get a really huge speed penalty for a feature which
> > would hardly ever be used.
> 
> I think we need to be aware of this, but I don't think it has to be that
> painful. If all scopes have a pad pointer, but lexical-free scopes start
> with it null, then there won't be a really huge speed penalty.
> [it might actually be faster, as this way all pads *are* equal, so there
> doesn't need to be special case code distinguishing between scopes with
> lexicals and scopes without.]

I think this make a large difference for variable access.  Here is a
comparison.

Consider the following example:

  $x = 1;
  my $y = 1;
  sub foo () {
    bar ();
    print "$x $y\n"; 
  }

We consider the two possibilities:

* The lexical scopes cannot be extended

We can implement the lexical scopes with an array of arrays.  For this
example, we don't need to allocate a pad for the subroutine foo, as it
has no lexical variable.  So, in subroutine foo, the layout of the
lexical scopes is the following :

  outer
  array     pad
   +--+    +--+
   | -+--->| -+--> $y PMC
   +--+    +--+

So, accessing the $y variable require 5 memory reads (5 assembly
instructions on a i386 machine):
- one read to get the address of the outer array from a parrot register
- two reads to get the address of the pad
- two reads to get the address of the PMC.

This could be further improved:
- the address of the outer array could probably be placed in a machine
  register, which would save one memory read
- we may be able to save one memory read per array look-up if we can
  avoid wrapping them in a PMC.
This would get us down to 2 memory reads.

On architectures which are not register starved (about anything but
the i386 architecture), it may be worthwhile to store some of the pad
addresses in machine register: this would save yet another memory
read.

I believe accessing a global variable such as $y can be made hardly
more costly: just one ore two more memory reads.  In particular, we
can avoid a hash look-up.

* The lexical scopes can be extended

I think we need to implement pads as hashes, not arrays, in this case.
Also, we need a pad even for blocks that does not introduce any
lexical variable, because a variable may be inserted latter.  So, for
the example, the layout of the lexical scopes looks like:

           Pads
         (hashes)
   +--+    +--+                                
   | -+--->|  |  (empty pad for subroutine foo)
   +--+    +--+
   | -+-
   +--+ \  +--+          
         ->| -+--> $y PMC
           +--+

Now, if we want to access $y, we first need to perform a look-up in
the first pad, usually empty, but which may contain a lexical variable
$y if bar is defined as:
   sub bar () {
     caller.MY{$y} = 2;
   }
Then, if this fails, we perform a look-up in the outer scope.

Most of the time, lexical variables will be defined in one of the
first pads, so I think we can expect the cost of accessing a lexical
variable to be around one or two hash lookup.  This is already a lot
more costly than a few memory reads.

For global variables ($x and &print in this example), I think this is
worse: we must look into all the hashes, just in case they become
shadowed by a lexical variable, for instance if bar is defined as:
   sub bar () {
     caller.MY{$x} = 2;
   }
or as:
   sub bar () {
     caller.OUTER.MY{$x} = 2;
   }

-- Jerome

Reply via email to