On Mon, 22 Jul 2002, Angel Faus wrote:
> In my opinion, there are actually two different things to dicuss:
>
> - keyed opcodes
> - keyed methods on the PMC vtable
>
...
>
> Keyed opcodes can stay in the interest of code density.
>
> >
> > No. Keyed access for all methods stays. You're forgetting one very
> > important case, the one where the contents of an aggregate isn't a
> > PMC. This scheme would then require the aggregate PMC to construct
> > fake PMCs to do operations on. Bad, definitely bad.
>
> Is really so bad? As long that what is ultimately stored in the
> aggregate is one of our base types (pmc, int, string, float), the
> opcode shouldn't need to build the fake PMC.
>
> Or, ¿are you talking about multi-dimensional PMCs? I don't see why
> these should need to build fake PMCs: you just call the get_keyed
> method, and it will do the Right Thing (probably getting the value
> directly, maybe propagating a portion of the key to a inner PMC).
By the time you told Parrot what you've actually returned, you might
as well have just returned it in the form of a PMC.
With fetching PMCs its easy. Atomic values are harder. A few things
spring to mind [for keyed access to atomic data items]:
1. Returning a reference. This would work, since the atomic types
have no active behavior. This would require all PMCs that store
things to work in terms of references in addition to values, and
would create another varient of ops [I could be wrong].
2. Load/Store. This would require a fetch, the operation performed,
then a store. Twice as many traversals would be required in the
case of:
%ref->{$x;$y;$z}++;
However,
print %ref->{$x;$y;$z}, "\n";
%ref->{$x;$y;$z} = 100;
...would both be unaffected, since only a fetch or store would be required.
3. Very light wrapper PMCs. A special int, num, string wrapper could
be created on startup for each operand position, and persist throughout
the life of the interpreter, being reused each time a keyed atomic value
is addressed. Keeping a finate dedicated pool eliminates overhead of
calculating which to use, adding and removing to free lists, etc.
This would require a mere 3*3=9 dedicated wrappers.
This would seriously damage the value of the primitive register pools
and place undo strain on the PMC registers. I'm just being thorough ;)
4. Establish an interpreter-global register ("Foo") that contains information
(an enum) about which operation is to be performed, and a UnionVal
describing the other operand.
This would require Parrot to look up one operand - either from the
register file directly or via get_pmc_*, populate the single, fixed
UnionVal with its information, then invoke do_operation_keyed on
the other operand. When the 2nd operand was able to eventually track
down the value in question, it could look to "Foo" to discover
which operation to perform, and with which other value, and implement
an internal switch() on the operation to be performed.
This would allow for 4 get_* methods, 4 set_* methods, and one do_
method.
The overhead that this would create is merely the switch() at the
end of the second resolution, which is negligable compared to the
handfull of method calls already being performed.
1 seems to be the fastest while being elegant and flexible. 4 follows
closely, having the benefit of not generating additional op permutations
but being a bit more contrived. 2 certainly has elegance on its side
but would actually cost significantly more in a frequent case. 3
is not elegant or fast but makes a case for "everything is an object"
not being quite so bad.
>
> As I said, there should no be any speed cost at all, and a significant
> improvement on code size. But this is only an opinion. I am willing
> to submit a benchmarking patch if it has some interest.
The reason I replied to this was not to rant [ranters anonymous?] but
to express interest in this comparison. I'd like to see that. With my
8k instruction cache, any refactoring is welcome =)
>
> Best,
>
> -àngel
>
Cheers,
-scott