Overhead of Property Runtime Checks

Accessing a property on an object in Tamarin requires a bunch of checks. An object in Tamarin is essentially a Hashtable. However, ASC tries to bind, or associate some kind of type with certain objects. For example, declaring a local variable with the keyword "var" gives a slot binding. If a property is bound, there are fewer checks. Thus when accessing a property, Tamarin has this execution:

TopLevel.cpp::GetProperty() {
Binding b = obj.getBinding();
switch (b) {
case Method: // do something
case Local: // do something else
case None: // No idea what we are
if ( obj.type == Object ) {
// hashtable lookup
} else {
// prototype chain lookup
}
} // end switch
} // end method

If the case is None, Tamarin has to do a dynamic lookup for the property. It first looks at the "this" object. If the property doesn't exist, it has to go through the prototype chain. If the property exists on "this", then a few more checks are made until we finally have a hash table look up to fetch the value.

The real question is, how much performance overhead does each check require? I ran with the idea something like an inline cache and stripped out a ton of checks for a simple test case. Of course stripping out checks means things will break, but at least we can get an idea of what the overhead is like. Let's take a look at the example AS3 source code:

function printLoop(limit:int):void {
var total:Object = new Object();
total.sum = 0;

for (i = 0; i < limit; i++) {
total.sum += i; // really stressing this line
}

print(total.sum);
}

printLoop(10000000);

Total execution time: ~11.5 s.


The loop is in a function to ensure the code is compiled. To remove the checks, the getProperty function is redirected to a custom getPropertyStripped function. This new getPropertyStripped function is called only for user source code keeping all the startup costs the same. The new getPropertyStripped source code looks something like this:

TopLevel.cpp::getPropertyStripped()
if (object.type == Object) {
// Hashtable lookup
} else {
return getProperty(...) // use the original getProperty function
}

Our getPropertyStripped removes the binding lookup and the switch case assuming a case None. Ignoring these checks gives us a total runtime of ~9.7 seconds. We shaved off a good ~1.8 seconds. Can we do better? Remember those other checks prior to the hashtable lookup? Let's see what else happens prior to the hashtable lookup:

TopLevel.cpp::getProperty()
switch (binding) {
case None:
if ( obj.type == Object )
return AvmCore::atomToScriptObject(obj)->getMultinameProperty(multiname);
}
}

ScriptObject::getMultinameProperty()
if (isValidDynamicName(multiname))
{
return getStringProperty(multiname->getName());
}

ScriptObject::isValidDynamicName(const Multiname* m) const {
return m->contains(core()->publicNamespace) && !m->isAnyName() && !m->isAttr();
}


The checks consist of a few method calls and bit manipulations. What happens if we remove the isValidDynamicName and just go straight to the getStringProperty? Average total runtime: ~8.8 seconds. Saved another second. Can we do better?

Behind getStringProperty() is a bunch of other method calls that make the code a bit cleaner. What if we inline all of that into the original getPropertyStripped function removing all the method call overhead and redundant fetching of some values? Total time: ~8.7 seconds shaving another 0.1 seconds. Lesson learned? If we remove most of the checks and inline all the method calls, we can cut off ~2.8 seconds for about a ~30% speedup. What's next? Outside of a new faster hashtable, this looks like the absolute max. The only problem: its not correct, but at least we have a sense of where the overheard is.

Results for AS3 example:
All Checks: ~11.5s
No Binding: ~9.7s
No Dynamic Name: ~8.8s
Inline Everything: ~8.7s