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