|
import sys |
|
from peak.rules.core import Aspect, abstract, Dispatching, Engine |
|
|
try: |
try: |
set = set |
set = set |
frozenset = frozenset |
frozenset = frozenset |
from sets import Set as set |
from sets import Set as set |
from sets import ImmutableSet as frozenset |
from sets import ImmutableSet as frozenset |
|
|
|
class Ordering(Aspect): |
|
"""Track inter-expression ordering constraints""" |
|
|
class AbstractIndex: |
def __init__(self, owner, expr): |
def __init__(self): |
|
self.constraints = set() |
self.constraints = set() |
|
|
def add_constraint(self, guards): |
def requires(self, guards): |
c = frozenset(guards) |
c = frozenset(guards) |
cs = self.constraints |
cs = self.constraints |
if c in cs: |
if c in cs: |
cs.remove(oldc) |
cs.remove(oldc) |
cs.add(c) |
cs.add(c) |
|
|
def is_legal(self, indexes): |
def can_precede(self, exprs): |
if not self.constraints: |
if not self.constraints: |
return True |
return True |
for c in self.constraints: |
for c in self.constraints: |
for i in c: |
for e in c: |
if i in indexes: |
if e in exprs: |
break |
break |
else: |
else: |
return True |
return True |
return False |
return False |
|
|
|
|
|
def define_ordering(ob, seq): |
|
items = [] |
|
for key in seq: |
|
Ordering(ob, key).requires(items) |
|
items.append(key) |
|
|
|
def to_bits(ints): |
|
"""Return a bitset encoding the numbers contained in sequence `ints`""" |
|
b = 0 |
|
for i in ints: |
|
b |= 1<<i |
|
return b |
|
|
|
if sys.version<"2.4": |
|
def to_bits(ints): |
|
"""Return a bitset encoding the numbers contained in sequence `ints`""" |
|
b = 0 |
|
for i in ints: |
|
if i>31: # under 2.3, this operation does the wrong thing |
|
i = long(i) |
|
b |= 1<<i |
|
return b |
|
|
|
|
|
def from_bits(n): |
|
"""Yield the (ascending) numbers contained in bitset n""" |
|
b = 0 |
|
while n: |
|
while not n & 15: |
|
n >>= 4 |
|
b += 4 |
|
if n & 1: |
|
yield b |
|
n >>= 1 |
|
b += 1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
class TreeBuilder(object): |
|
"""Template methods for the Chambers&Chen dispatch tree algorithm""" |
|
|
class TreeBuilder: |
def build_root(self, cases, exprs): |
def __init__(self): |
|
self.memo = {} |
self.memo = {} |
|
return self.build(cases, exprs) |
|
|
def build(self, cases, indexes): |
def build(self, cases, exprs): |
key = (cases, indexes) |
key = (cases, exprs) |
if key in self.memo: |
if key in self.memo: |
return self.memo[key] |
return self.memo[key] |
|
|
if not indexes: |
if not exprs: |
node = self.build_leaf(cases) |
node = self.build_leaf(cases) |
else: |
else: |
best, rest = self.best_index(cases, indexes) |
best, rest = self.best_expr(cases, exprs) |
assert len(rest) < len(indexes) |
assert len(rest) < len(exprs) |
|
|
if best is None: |
if best is None: |
# No best index found, recurse |
# No best expression found, recurse |
node = self.build(cases, rest) |
node = self.build(cases, rest) |
else: |
else: |
node = self.build_node(best, cases, rest) |
node = self.build_node(best, cases, rest) |
self.memo[key] = node |
self.memo[key] = node |
return node |
return node |
|
|
def build_node(self, index, cases, remaining_indexes): |
def build_node(self, expr, cases, remaining_exprs): |
raise NotImplementedError |
raise NotImplementedError |
|
|
def build_leaf(self, cases): |
def build_leaf(self, cases): |
raise NotImplementedError |
raise NotImplementedError |
|
|
|
def selectivity(self, expression, cases): |
|
"""Return (seedcount,totalcases) selectivity statistics""" |
|
raise NotImplementedError |
|
|
|
def cost(self, expr, remaining_exprs): |
|
return 1 |
|
|
|
|
|
def best_expr(self, cases, exprs): |
|
best_expr = None |
|
|
|
|
|
|
|
|
|
|
|
|
def best_index(self, cases, indexes): |
|
best_index = None |
|
best_spread = None |
best_spread = None |
|
|
to_do = list(indexes) |
to_do = list(exprs) |
remaining = dict.fromkeys(indexes) |
remaining = dict.fromkeys(exprs) |
active_cases = len(cases) |
active_cases = len(cases) |
skipped = [] |
skipped = [] |
|
|
while to_do: |
while to_do: |
index = to_do.pop() |
expr = to_do.pop() |
if not index.is_legal(remaining): |
if not Ordering(self, expr).can_precede(remaining): |
# Skip criteria that have unchecked prerequisites |
# Skip criteria that have unchecked prerequisites |
skipped.append(index) |
skipped.append(expr) |
continue |
continue |
|
|
branches, total_cases = index.selectivity(cases) |
branches, total_cases = self.selectivity(expr, cases) |
|
|
if total_cases == active_cases * branches: |
if total_cases == active_cases * branches: |
# None of the index keys for this expression eliminate any |
# None of the branches for this expression eliminate any |
# cases, so this expression isn't needed for dispatching |
# cases, so this expression isn't needed for dispatching |
del remaining[index] |
del remaining[expr] |
|
|
# recheck skipped indexes that might be legal now |
# recheck skipped exprs that might be legal now |
to_do.extend(skipped) |
to_do.extend(skipped) |
skipped = [] |
skipped = [] |
continue |
continue |
|
|
spread = float(total_cases) / branches |
spread = float(total_cases) / branches |
if best_index is None or spread < best_spread: |
if best_expr is None or spread < best_spread: |
best_index, best_spread = index, spread |
best_expr, best_spread = expr, spread |
# best_cost = self.cost(index, remaining) |
best_cost = self.cost(expr, remaining) |
#elif spread==best_spread: |
elif spread==best_spread: |
# cost = self.cost(index, remaining) |
cost = self.cost(expr, remaining) |
# if cost < best_cost: |
if cost < best_cost: |
# best_index, best_cost = index, cost |
best_expr, best_cost = expr, cost |
|
|
if best_index is not None: |
if best_expr is not None: |
del remaining[best_index] |
del remaining[best_expr] |
return best_index, frozenset(remaining) |
return best_expr, frozenset(remaining) |
|
|