Permalink
Browse files

Optimise slot lookups for the JIT.

Having finally got around the final part of what Carl Friedrich was
suggesting in
<http://morepypy.blogspot.com/2011/03/controlling-tracing-of-interpreter-with_21.html>,
I've implemented a similar scheme for Converge. The assumption is that
classes rarely change their fields (most classes never change their fields
after their initial creation), so that in general we can simply elide field
lookups entirely. When a field is changed, all subsequent field lookups on
that class and its subclasses need to be regenerated. This gives a fairly
hefty speed-up to a lot of code, because the trace optimiser can now do a
much better job.
  • Loading branch information...
1 parent f17b55f commit c6128eb0850499135afee8808b1732f06b7a44b5 @ltratt committed Jan 23, 2012
Showing with 126 additions and 48 deletions.
  1. +1 −0 tests/lang/builtins/.gitignore
  2. +1 −1 tests/lang/builtins/Makefile.in
  3. +60 −0 tests/lang/builtins/class1.cv
  4. +1 −0 tests/lang/builtins/tests.cv
  5. +63 −47 vm/Builtins.py
@@ -1,3 +1,4 @@
+class1
list1
str1
tests
@@ -1,7 +1,7 @@
include @abs_top_srcdir@/Makefile.inc
-TESTS = int1 list1 str1
+TESTS = class1 int1 list1 str1
all:
@@ -0,0 +1,60 @@
+// Copyright (c) 2011 King's College London, created by Laurence Tratt
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+
+import Builtins, Exceptions, Sys
+
+
+
+func test_simple_add_field():
+ class C:
+ pass
+
+ c := C.new()
+ C.set_field("x", 1)
+ assert c.x == 1
+
+
+func test_nested_add_field():
+ class B:
+ pass
+
+ class C(B):
+ pass
+
+ c := C.new()
+ B.set_field("x", 1)
+ assert c.x == 1
+
+
+func main():
+
+ // We want to check that the JIT handles nested adding of fields, so we embed everything in a
+ // sufficiently long loop that we can be sure that the JIT is operating.
+
+ i := 0
+ while i < 100000:
+ test_simple_add_field()
+ i += 1
+
+ i := 0
+ while i < 100000:
+ test_nested_add_field()
+ i += 1
@@ -26,6 +26,7 @@ import Lang_Test
tests := $<<Lang_Test::tests>>:
+ "class1.cv"
"int1.cv"
"list1.cv"
"str1.cv"
View
@@ -18,7 +18,7 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
-from pypy.rlib import debug, jit, objectmodel, rarithmetic
+from pypy.rlib import debug, jit, objectmodel, rarithmetic, rweakref
from pypy.rpython.lltypesystem import lltype, rffi
NUM_BUILTINS = 41
@@ -146,22 +146,32 @@ def has_slot(self, vm, n):
if i != -1:
return True
- if self.has_slot_override(vm, n) or self.instance_of.has_field(vm, n):
- return True
-
return False
- # This is the method to override in subclasses.
+ def find_slot(self, vm, n):
+ o = None
+ if self.slots is not None:
+ m = jit.promote(self.slots_map)
+ i = m.find(n)
+ if i != -1:
+ o = self.slots[i]
+
+ if o is None:
+ o = self.instance_of.find_field(vm, n)
+ if o is None:
+ if n == "instance_of":
+ o = self.instance_of
+ if o is None:
+ return o
- def has_slot_override(self, vm, n):
- if n == "instance_of":
- return True
+ if isinstance(o, Con_Func) and o.is_bound:
+ return Con_Partial_Application(vm, self, o)
- return False
+ return o
- def get_slot(self, vm, n, find_mode=False):
+ def get_slot(self, vm, n):
o = None
if self.slots is not None:
m = jit.promote(self.slots_map)
@@ -170,31 +180,19 @@ def get_slot(self, vm, n, find_mode=False):
o = self.slots[i]
if o is None:
- o = self.get_slot_override(vm, n)
-
- if o is None:
o = self.instance_of.find_field(vm, n)
-
- if o is None:
- if find_mode:
- return None
- else:
- vm.raise_helper("Slot_Exception", [Con_String(vm, n), self])
+ if o is None:
+ if n == "instance_of":
+ o = self.instance_of
+ if o is None:
+ vm.raise_helper("Slot_Exception", [Con_String(vm, n), self])
if isinstance(o, Con_Func) and o.is_bound:
return Con_Partial_Application(vm, self, o)
return o
- # This is the method to override in subclasses.
-
- def get_slot_override(self, vm, n):
- if n == "instance_of":
- return self.instance_of
-
- return None
-
def set_slot(self, vm, n, o):
assert o is not None
@@ -278,7 +276,7 @@ def _Con_Object_find_slot(vm):
(self, sn_o),_ = vm.decode_args("OS")
assert isinstance(sn_o, Con_String)
- v = self.get_slot(vm, sn_o.v, find_mode=True)
+ v = self.find_slot(vm, sn_o.v)
if not v:
v = vm.get_builtin(BUILTIN_FAIL_OBJ)
return v
@@ -405,8 +403,8 @@ def bootstrap_con_object(vm):
#
class Con_Class(Con_Boxed_Object):
- __slots__ = ("supers", "fields_map", "fields", "new_func")
- _immutable_fields = ("supers", "fields")
+ __slots__ = ("supers", "fields_map", "fields", "new_func", "version", "dependents")
+ _immutable_fields = ("supers", "fields", "dependents")
def __init__(self, vm, name, supers, container, instance_of=None, new_func=None):
@@ -437,18 +435,36 @@ def __init__(self, vm, name, supers, container, instance_of=None, new_func=None)
self.supers = supers
self.fields_map = _EMPTY_MAP
self.fields = []
+
+ # To optimise slot lookups, we need to be a little more cunning. We make a (reasonable)
+ # assumption that classes rarely change their fields (most classes never change their fields
+ # after their initial creation at all), so that in general we can simply elide field
+ # lookups entirely. When a field is changed, all subsequent field lookups on that class and
+ # its subclasses need to be regenerated. To force this, every class a "version" which is
+ # incremented whenever its fields are changed. As well as changing the class itself, all
+ # subclasses must be changed too. We maintain a list of all subclasses (even indirect ones!)
+ # to do this.
+
+ self.version = 0
+ self.dependents = []
+ sc_stack = supers[:]
+ while len(sc_stack) > 0:
+ sc = type_check_class(vm, sc_stack.pop())
+ sc.dependents.append(rweakref.ref(sc))
+ sc_stack.extend(sc.supers)
self.set_slot(vm, "name", name)
if container:
self.set_slot(vm, "container", container)
- def find_field(self, vm, n):
+ @jit.elidable_promote("0")
+ def _get_field_i(self, vm, n, version):
m = jit.promote(self.fields_map)
i = m.find(n)
if i != -1:
return self.fields[i]
-
+
for s in self.supers:
assert isinstance(s, Con_Class)
o = s.find_field(vm, n)
@@ -458,27 +474,17 @@ def find_field(self, vm, n):
return None
+ def find_field(self, vm, n):
+ return self._get_field_i(vm, n, jit.promote(self.version))
+
+
def get_field(self, vm, n):
- o = self.find_field(vm, n)
+ o = self._get_field_i(vm, n, jit.promote(self.version))
if o is None:
vm.raise_helper("Field_Exception", [Con_String(vm, n), self])
return o
- def has_field(self, vm, n):
- m = jit.promote(self.fields_map)
- i = m.find(n)
- if i != -1:
- return True
-
- for s in self.supers:
- assert isinstance(s, Con_Class)
- if s.has_field(vm, n):
- return True
-
- return False
-
-
def set_field(self, vm, n, o):
assert o is not None
m = jit.promote(self.fields_map)
@@ -488,6 +494,16 @@ def set_field(self, vm, n, o):
self.fields.append(o)
else:
self.fields[i] = o
+ self.version += 1
+
+ j = 0
+ while j < len(self.dependents):
+ dep = self.dependents[j]()
+ if dep is None:
+ del self.dependents[j]
+ continue
+ dep.version += 1
+ j += 1
@con_object_proc

0 comments on commit c6128eb

Please sign in to comment.