Re: [pypy-dev] [pypy-commit] pypy default: use jit.loop_unrolling_heuristic where possible

Hi Brian, On 03/25/2013 10:42 AM, bdkearns wrote:
Author: Brian Kearns <bdkearns@gmail.com> Branch: Changeset: r62735:1f7ca87c6780 Date: 2013-03-25 05:37 -0400 http://bitbucket.org/pypy/pypy/changeset/1f7ca87c6780/
Log: use jit.loop_unrolling_heuristic where possible
diff --git a/pypy/module/__builtin__/functional.py b/pypy/module/__builtin__/functional.py --- a/pypy/module/__builtin__/functional.py +++ b/pypy/module/__builtin__/functional.py @@ -202,8 +202,8 @@
@specialize.arg(2) def min_max(space, args, implementation_of): - if not jit.we_are_jitted() or (jit.isconstant(len(args.arguments_w)) and - len(args.arguments_w) == 2): + if not jit.we_are_jitted() or jit.loop_unrolling_heuristic( + args.arguments_w, len(args.arguments_w)): return min_max_unroll(space, args, implementation_of) else: return min_max_normal(space, args, implementation_of)
This is a very risky change. The unrolled pseudo-code for min (max is equivalent) with 5 elements is something like this: m = l0 if l1 < m: m = l1 if l2 < m m = l2 if l3 < m m = l3 if l3 < m m = l3 (the comparisons of course have to go through the object space, everything is more complex, but the control flow is essentially like this.) Every comparison can go either way, that means that there are 2**4 paths through this code, and it's completely unpredictable which of the paths is taken. Therefore it can lead to potentially too much code being generated. I think for min and max in particular the compromise of just unrolling twice is acceptable. BTW, another optimization that might be interesting for min/max is to add fast paths with listview_int/listview_str if no key and no comparison are given. Cheers, Carl Friedrich
diff --git a/pypy/objspace/std/dictmultiobject.py b/pypy/objspace/std/dictmultiobject.py --- a/pypy/objspace/std/dictmultiobject.py +++ b/pypy/objspace/std/dictmultiobject.py @@ -10,8 +10,7 @@ from rpython.rlib.debug import mark_dict_non_null from rpython.tool.sourcetools import func_with_new_name
-from rpython.rlib import rerased -from rpython.rlib import jit +from rpython.rlib import rerased, jit
def _is_str(space, w_key): return space.is_w(space.type(w_key), space.w_str) @@ -29,9 +28,6 @@ space.is_w(w_lookup_type, space.w_float) )
- -DICT_CUTOFF = 5 - @specialize.call_location() def w_dict_unrolling_heuristic(w_dct): """ In which cases iterating over dict items can be unrolled. @@ -39,7 +35,8 @@ an actual dict """ return jit.isvirtual(w_dct) or (jit.isconstant(w_dct) and - w_dct.length() <= DICT_CUTOFF) + w_dct.length() <= jit.UNROLL_CUTOFF) +
class W_DictMultiObject(W_Object): from pypy.objspace.std.dicttype import dict_typedef as typedef @@ -761,7 +758,8 @@ update1_keys(space, w_dict, w_data)
-@jit.look_inside_iff(lambda space, w_dict, w_data: w_dict_unrolling_heuristic(w_data)) +@jit.look_inside_iff(lambda space, w_dict, w_data: + w_dict_unrolling_heuristic(w_data)) def update1_dict_dict(space, w_dict, w_data): iterator = w_data.iteritems() while 1: diff --git a/pypy/objspace/std/intobject.py b/pypy/objspace/std/intobject.py --- a/pypy/objspace/std/intobject.py +++ b/pypy/objspace/std/intobject.py @@ -183,7 +183,8 @@
# helper for pow() -@jit.look_inside_iff(lambda space, iv, iw, iz: jit.isconstant(iw) and jit.isconstant(iz)) +@jit.look_inside_iff(lambda space, iv, iw, iz: + jit.isconstant(iw) and jit.isconstant(iz)) def _impl_int_int_pow(space, iv, iw, iz): if iw < 0: if iz != 0: diff --git a/pypy/objspace/std/kwargsdict.py b/pypy/objspace/std/kwargsdict.py --- a/pypy/objspace/std/kwargsdict.py +++ b/pypy/objspace/std/kwargsdict.py @@ -95,7 +95,8 @@ def getitem_str(self, w_dict, key): return self._getitem_str_indirection(w_dict, key)
- @jit.look_inside_iff(lambda self, w_dict, key: jit.isconstant(self.length(w_dict)) and jit.isconstant(key)) + @jit.look_inside_iff(lambda self, w_dict, key: + jit.isconstant(self.length(w_dict)) and jit.isconstant(key)) def _getitem_str_indirection(self, w_dict, key): keys, values_w = self.unerase(w_dict.dstorage) result = [] diff --git a/pypy/objspace/std/listobject.py b/pypy/objspace/std/listobject.py --- a/pypy/objspace/std/listobject.py +++ b/pypy/objspace/std/listobject.py @@ -18,7 +18,6 @@ from pypy.objspace.std.stdtypedef import StdTypeDef, SMM from sys import maxint
-UNROLL_CUTOFF = 5
class W_AbstractListObject(W_Object): __slots__ = () @@ -43,7 +42,7 @@ return W_ListObject.from_storage_and_strategy(space, storage, strategy)
@jit.look_inside_iff(lambda space, list_w, sizehint: - jit.isconstant(len(list_w)) and len(list_w) < UNROLL_CUTOFF) + jit.loop_unrolling_heuristic(list_w, len(list_w))) def get_strategy_from_list_objects(space, list_w, sizehint): if not list_w: if sizehint != -1: @@ -950,7 +949,7 @@ raise NotImplementedError("abstract base class")
@jit.look_inside_iff(lambda space, w_list, list_w: - jit.isconstant(len(list_w)) and len(list_w) < UNROLL_CUTOFF) + jit.loop_unrolling_heuristic(list_w, len(list_w))) def init_from_list_w(self, w_list, list_w): l = [self.unwrap(w_item) for w_item in list_w] w_list.lstorage = self.erase(l) @@ -999,7 +998,7 @@ return self.wrap(r)
@jit.look_inside_iff(lambda self, w_list: - jit.isconstant(w_list.length()) and w_list.length() < UNROLL_CUTOFF) + jit.loop_unrolling_heuristic(w_list, w_list.length())) def getitems_copy(self, w_list): return [self.wrap(item) for item in self.unerase(w_list.lstorage)]
@@ -1008,7 +1007,7 @@ return [self.wrap(item) for item in self.unerase(w_list.lstorage)]
@jit.look_inside_iff(lambda self, w_list: - jit.isconstant(w_list.length()) and w_list.length() < UNROLL_CUTOFF) + jit.loop_unrolling_heuristic(w_list, w_list.length())) def getitems_fixedsize(self, w_list): return self.getitems_unroll(w_list)
diff --git a/pypy/objspace/std/tupleobject.py b/pypy/objspace/std/tupleobject.py --- a/pypy/objspace/std/tupleobject.py +++ b/pypy/objspace/std/tupleobject.py @@ -10,8 +10,6 @@ from rpython.rlib import jit from rpython.tool.sourcetools import func_with_new_name
-# Tuples of known length up to UNROLL_TUPLE_LIMIT have unrolled certain methods -UNROLL_TUPLE_LIMIT = 10
class W_AbstractTupleObject(W_Object): __slots__ = () @@ -85,15 +83,8 @@ start, stop = normalize_simple_slice(space, length, w_start, w_stop) return space.newtuple(w_tuple.wrappeditems[start:stop])
-THRESHOLD = 7 - -def unroll_tuple_contains(space, w_tuple, w_obj): - if (jit.isconstant(w_tuple) or jit.isvirtual(w_tuple) and - len(w_tuple.wrappeditems) < THRESHOLD): - return True - return False - -@jit.look_inside_iff(unroll_tuple_contains) +@jit.look_inside_iff(lambda space, w_tuple, w_obj: + jit.loop_unrolling_heuristic(w_tuple, len(w_tuple.wrappeditems))) def contains__Tuple_ANY(space, w_tuple, w_obj): for w_item in w_tuple.wrappeditems: if space.eq_w(w_item, w_obj): @@ -128,10 +119,8 @@ return mul_tuple_times(space, w_tuple, w_times)
def tuple_unroll_condition(space, w_tuple1, w_tuple2): - lgt1 = len(w_tuple1.wrappeditems) - lgt2 = len(w_tuple2.wrappeditems) - return ((jit.isconstant(lgt1) and lgt1 <= UNROLL_TUPLE_LIMIT) or - (jit.isconstant(lgt2) and lgt2 <= UNROLL_TUPLE_LIMIT)) + return jit.loop_unrolling_heuristic(w_tuple1, len(w_tuple1.wrappeditems)) or \ + jit.loop_unrolling_heuristic(w_tuple2, len(w_tuple2.wrappeditems))
@jit.look_inside_iff(tuple_unroll_condition) def eq__Tuple_Tuple(space, w_tuple1, w_tuple2): @@ -151,7 +140,7 @@ def _make_tuple_comparison(name): import operator op = getattr(operator, name) - # + @jit.look_inside_iff(tuple_unroll_condition) def compare_tuples(space, w_tuple1, w_tuple2): items1 = w_tuple1.wrappeditems @@ -184,8 +173,7 @@ return space.wrap(hash_tuple(space, w_tuple.wrappeditems))
@jit.look_inside_iff(lambda space, wrappeditems: - jit.isconstant(len(wrappeditems)) and - len(wrappeditems) < UNROLL_TUPLE_LIMIT) + jit.loop_unrolling_heuristic(wrappeditems, len(wrappeditems))) def hash_tuple(space, wrappeditems): # this is the CPython 2.4 algorithm (changed from 2.3) mult = 1000003 diff --git a/rpython/rlib/jit.py b/rpython/rlib/jit.py --- a/rpython/rlib/jit.py +++ b/rpython/rlib/jit.py @@ -206,13 +206,13 @@ return NonConstant(False) isvirtual._annspecialcase_ = "specialize:call_location"
-LIST_CUTOFF = 2 +UNROLL_CUTOFF = 5
@specialize.call_location() def loop_unrolling_heuristic(lst, size): """ In which cases iterating over items of lst can be unrolled """ - return isvirtual(lst) or (isconstant(size) and size <= LIST_CUTOFF) + return isvirtual(lst) or (isconstant(size) and size <= UNROLL_CUTOFF)
class Entry(ExtRegistryEntry): _about_ = hint _______________________________________________ pypy-commit mailing list pypy-commit@python.org http://mail.python.org/mailman/listinfo/pypy-commit

Hi Brian, hi Carl Friedrich, On Mon, Mar 25, 2013 at 1:26 PM, Carl Friedrich Bolz <cfbolz@gmx.de> wrote:
m = l0 if l1 < m: m = l1 if l2 < m m = l2 if l3 < m m = l3 if l3 < m m = l3
Alternatively, we could work a bit more and have it jitted as a single non-branching piece of code. For example, with "space.min(w_a, w_b)", which would check the types, and if both are ints, then just return "space.newint(min(w_a.value, w_b.value))". There are various way to compute min(x, y) with two integer arguments in RPython, without branching. At worst we make it a special ResOperation, but there are also other ways. A bientôt, Armin.

Hi, On Thu, Mar 28, 2013 at 5:33 PM, Armin Rigo <arigo@tunes.org> wrote:
There are various way to compute min(x, y) with two integer arguments in RPython, without branching.
The best might be to implement min(x, y) in RPython as returning "int_select(x < y, x, y)", using a new rarithmetic.int_select(a, b, c) function similar to rarithmetic.int_between(), which would turn into the C code "a ? b : c". We can easily support this operation a bit everywhere, down to the JIT backends --- and then on x86 it is done with a single conditional move instruction. A bientôt, Armin.
participants (2)
-
Armin Rigo
-
Carl Friedrich Bolz