Skip to content

Commit

Permalink
Faster logging (#153)
Browse files Browse the repository at this point in the history
* Removed StaticNode

* Removed unnecessary function.

* Added time limiter.

* Made some recursive procedures iterative.

* Blanked .scm test file.

* Hopefully made logging faster?

* Made some recursive procedures iterative.

* Small changes.

* fixed

* typo

* fixed to handle dotted lists in output
  • Loading branch information
rahularya50 authored and kavigupta committed Apr 15, 2019
1 parent e59af85 commit 57ee9ef
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 64 deletions.
4 changes: 3 additions & 1 deletion editor/datamodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

class Expression:
def __init__(self):
self.id = get_id()
self.id = None


class ValueHolder(Expression):
Expand Down Expand Up @@ -38,6 +38,7 @@ def __repr__(self):
return super().__repr__()



class Pair(Expression):
def __init__(self, first: Expression, rest: Expression):
import log
Expand Down Expand Up @@ -100,6 +101,7 @@ def __init__(self, expr: Expression, frame: 'Frame'):
self.expr = expr
self.frame = frame
self.targets = []
self.id = get_id()

def __repr__(self):
return "#[promise]"
Expand Down
9 changes: 3 additions & 6 deletions editor/evaluate_apply.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,7 @@ def evaluate(expr: Expression, frame: Frame, gui_holder: log.Holder,
if depth > RECURSION_LIMIT:
raise OutOfMemoryError("Debugger ran out of memory due to excessively deep recursion.")

if isinstance(gui_holder.expression, Expression):
visual_expression = log.VisualExpression(expr)
gui_holder.link_visual(visual_expression)
else:
visual_expression = gui_holder.expression
visual_expression = gui_holder.expression

if log_stack:
log.logger.eval_stack.append(f"{repr(expr)} [frame = {frame.id}]")
Expand Down Expand Up @@ -122,7 +118,8 @@ def evaluate(expr: Expression, frame: Frame, gui_holder: log.Holder,
if isinstance(out, Thunk):
expr, frame = out.expr, out.frame
thunks.append(out)
out.gui_holder.evaluate()
if out.gui_holder.state != log.HolderState.EVALUATING:
out.gui_holder.evaluate()
if log.logger.show_thunks:
gui_holder = out.gui_holder
else:
Expand Down
12 changes: 8 additions & 4 deletions editor/execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@
from datamodel import Undefined, Pair
from evaluate_apply import evaluate
from graphics import Canvas
from helper import pair_to_list
from log import Holder, Root
from execution_parser import get_expression
from lexer import TokenBuffer
from runtime_limiter import TimeLimitException
from scheme_exceptions import SchemeError, ParseError

MAX_TRACEBACK_LENGTH = 20
MAX_AUTODRAW_LENGTH = 50


def string_exec(strings, out, visualize_tail_calls, global_frame=None):
import log


empty = False

if global_frame is None:
Expand Down Expand Up @@ -50,9 +51,12 @@ def string_exec(strings, out, visualize_tail_calls, global_frame=None):
res = evaluate(expr, global_frame, holder)
if res is not Undefined:
out(res)
if not log.logger.fragile and log.logger.autodraw and isinstance(res, Pair):
log.logger.raw_out("AUTODRAW" +
json.dumps([log.logger.i, log.logger.heap.record(res)]) + "\n")
if not log.logger.fragile and log.logger.autodraw:
try:
log.logger.raw_out("AUTODRAW" +
json.dumps([log.logger.i, log.logger.heap.record(res)]) + "\n")
except RecursionError:
pass
except (SchemeError, ZeroDivisionError, RecursionError, ValueError) as e:
if isinstance(e, ParseError):
log.logger.new_expr()
Expand Down
75 changes: 33 additions & 42 deletions editor/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,32 @@ class HolderState(Enum):
APPLYING = 4


class FakeObj:
def __getattr__(self, item):
return fake_obj

def __getitem__(self, item):
return fake_obj

def __call__(self, *args, **kwargs):
return fake_obj


fake_obj = FakeObj()


class VisualExpression:
def __init__(self, base_expr: Expression = None, true_base_expr: Expression = None):
self.display_value = base_expr
self.base_expr = base_expr if true_base_expr is None else true_base_expr
self.value: Expression = None
self.children: List[Holder] = []
self.id = get_id()

if logger.op_count >= OP_LIMIT:
self.children = fake_obj
return

if base_expr is None:
return
if isinstance(base_expr, ValueHolder) \
Expand All @@ -41,16 +60,14 @@ def __init__(self, base_expr: Expression = None, true_base_expr: Expression = No
except OperandDeduceError:
self.set_entries([base_expr.first, base_expr.rest])
else:
raise NotImplementedError(base_expr)
raise NotImplementedError(base_expr, type(base_expr))

def set_entries(self, expressions: List[Expression]):
def set_entries(self, expressions: Union[List[Expression], List['VisualExpression']]):
self.value = None
self.children = [Holder(expression, self) for expression in expressions]
if expressions and isinstance(expressions[0], VisualExpression):
if self.id in logger.node_cache:
if isinstance(logger.node_cache[self.id], StaticNode):
curr_transition = HolderState[logger.node_cache[self.id].transition_type]
elif logger.node_cache[self.id].transitions:
if logger.node_cache[self.id].transitions:
curr_transition = HolderState[logger.node_cache[self.id].transitions[-1][-1]]
else:
return self
Expand All @@ -65,16 +82,10 @@ def __repr__(self):

class Holder:
def __init__(self, expr: Expression, parent: VisualExpression):
self.expression: Union[Expression, VisualExpression] = expr
self.expression: VisualExpression = VisualExpression(expr) if isinstance(expr, Expression) else expr
self.state = HolderState.UNEVALUATED
self.parent = parent

def link_visual(self, expr: VisualExpression):
self.expression = expr
if self.parent is not None and self.parent.id in logger.node_cache:
logger.node_cache[self.parent.id].modify(self.parent, HolderState.EVALUATING)
return expr

def evaluate(self):
self.state = HolderState.EVALUATING
announce("Evaluating", self, Root.root)
Expand Down Expand Up @@ -131,7 +142,7 @@ def __init__(self):

self.show_thunks = True

self.node_cache: Dict[str, Union[StaticNode, FatNode]] = {} # a cache of visual expressions
self.node_cache: Dict[str, Node] = {} # a cache of visual expressions
self.export_states = [] # all the nodes generated in the current evaluation, in exported form
self.roots = [] # the root node of each expr we are currently evaluating

Expand Down Expand Up @@ -165,8 +176,8 @@ def new_query(self, global_frame: 'StoredFrame'=None, curr_i=0, curr_f=0):
self.export_states = []
self.frame_updates = []
self.global_frame = global_frame
self.op_count = 0
self.graphics_open = False
self.op_count = 0

def get_canvas(self) -> 'graphics.Canvas':
self.graphics_open = True
Expand Down Expand Up @@ -218,14 +229,10 @@ def frame_create(self, frame: 'evaluate_apply.Frame'):
def frame_store(self, frame: 'evaluate_apply.Frame', name: str, value: Expression):
self.frame_lookup[id(frame)].bind(name, value)

def new_node(self, expr: Union[Expression, VisualExpression], transition_type: HolderState):
if isinstance(expr, Expression):
key = get_id()
self.node_cache[key] = StaticNode(expr, transition_type)
return key
def new_node(self, expr: VisualExpression, transition_type: HolderState):
if expr.id in self.node_cache:
return self.node_cache[expr.id].modify(expr, transition_type, force=True)
node = FatNode(expr, transition_type)
node = Node(expr, transition_type)
self.node_cache[node.id] = node
return node.id

Expand All @@ -235,22 +242,7 @@ def log_op(self):
return self.op_count < OP_LIMIT


class StaticNode:
def __init__(self, expr: Expression, transition_type: HolderState):
self.expr = expr
self.transition_type = transition_type

def export(self):
return {
"transitions": [(0, self.transition_type.name)],
"strs": [(0, repr(self.expr))],
"parent_strs": [(0, repr(self.expr))],
"children": [(0, [])],
"static": True
}


class FatNode:
class Node:
def __init__(self, expr: VisualExpression, transition_type: HolderState):
self.transitions = []
self.str = []
Expand All @@ -260,7 +252,7 @@ def __init__(self, expr: VisualExpression, transition_type: HolderState):
self.modify(expr, transition_type)

@limited
def modify(self, expr: Union[Expression, VisualExpression], transition_type: HolderState):
def modify(self, expr: VisualExpression, transition_type: HolderState):
if not self.transitions or self.transitions[-1][1] != transition_type.name:
self.transitions.append((logger.i, transition_type.name))
if not self.str or self.str[-1][1] != repr(expr):
Expand All @@ -269,17 +261,14 @@ def modify(self, expr: Union[Expression, VisualExpression], transition_type: Hol
while self.children and self.children[-1][0] == logger.i:
self.children.pop()

if isinstance(expr, VisualExpression) and expr.value is None:
if expr.value is None:
self.children.append(
(logger.i,
[logger.new_node(child.expression, child.state) for child in expr.children]))
else:
self.children.append((logger.i, []))

if isinstance(expr, VisualExpression):
new_base_str = repr(expr.base_expr)
else:
new_base_str = expr
new_base_str = repr(expr.base_expr)

if not self.base_str or self.base_str[-1][1] != new_base_str:
self.base_str.append((logger.i, new_base_str))
Expand Down Expand Up @@ -355,6 +344,8 @@ def modify(self, id):
def record(self, expr: Expression) -> 'Heap.HeapKey':
if isinstance(expr, evaluate_apply.Thunk):
return False, "thunk"
if expr.id is None:
expr.id = get_id()
if expr.id not in self.prev and expr.id not in self.curr:
if isinstance(expr, ValueHolder):
return False, repr(expr)
Expand Down
14 changes: 12 additions & 2 deletions editor/runtime_limiter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,24 @@
import threading
import time

import log
from scheme_exceptions import TerminatedError

class OperationCanceledException(Exception): pass
class TimeLimitException(Exception): pass

class OperationCanceledException(Exception):
pass


class TimeLimitException(Exception):
pass


def limiter(raise_exception, lim, func, *args):
is_event = isinstance(lim, threading.Event)
lim_is_set = lim.is_set if is_event else None # For performance
gettime = time.time # For performance
end = (gettime() + lim) if not is_event else None

def tracer(*args):
if lim_is_set() if is_event else gettime() > end:
raise_exception(OperationCanceledException() if is_event else TimeLimitException())
Expand All @@ -24,9 +32,11 @@ def tracer(*args):
finally:
sys.settrace(sys_tracer)


def scheme_limiter(*args, **kwargs):
def raise_(e): # Translate to scheme exception and throw
if isinstance(e, OperationCanceledException):
e = TerminatedError
raise e

return limiter(raise_, *args, **kwargs)
10 changes: 1 addition & 9 deletions editor/special_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ def execute(self, operands: List[Expression], frame: Frame, gui_holder: Holder,
new_frame.assign(self.var_param, make_list(operands[len(self.params):]))

out = None
# noinspection PyTypeChecker
gui_holder.expression.set_entries(
[VisualExpression(expr, gui_holder.expression.display_value) for expr in body])

Expand All @@ -70,7 +69,6 @@ def execute(self, operands: List[Expression], frame: Frame, gui_holder: Holder,
new_frame.assign(return_symbol, out)

if not self.evaluates_operands:
# noinspection PyTypeChecker
gui_holder.expression.set_entries([VisualExpression(out, gui_holder.expression.display_value)])
out = evaluate(out, frame, gui_holder.expression.children[i], True)

Expand Down Expand Up @@ -266,7 +264,6 @@ def execute(self, operands: List[Expression], frame: Frame, gui_holder: Holder):
raise OperandDeduceError(f"Unable to evaluate clause of cond, as {cond} is not a Pair.")
expanded = pair_to_list(cond)
cond_holder = gui_holder.expression.children[cond_i + 1]
cond_holder.link_visual(VisualExpression(cond))
eval_condition = SingletonTrue
if not isinstance(expanded[0], Symbol) or expanded[0].value != "else":
eval_condition = evaluate(expanded[0], frame, cond_holder.expression.children[0])
Expand Down Expand Up @@ -311,7 +308,6 @@ def execute(self, operands: List[Expression], frame: Frame, gui_holder: Holder):

new_frame = Frame("anonymous let", frame)

gui_holder.expression.children[1].link_visual(VisualExpression(bindings))
bindings_holder = gui_holder.expression.children[1]

bindings = pair_to_list(bindings)
Expand All @@ -320,7 +316,6 @@ def execute(self, operands: List[Expression], frame: Frame, gui_holder: Holder):
if not isinstance(binding, Pair):
raise OperandDeduceError(f"Expected binding to be a Pair, not {binding}.")
binding_holder = bindings_holder.expression.children[i]
binding_holder.link_visual(VisualExpression(binding))
binding = pair_to_list(binding)
if len(binding) != 2:
raise OperandDeduceError(f"Expected binding to be of length 2, not {len(binding)}.")
Expand Down Expand Up @@ -375,8 +370,7 @@ def quasiquote_evaluate(cls, expr: Expression, frame: Frame, gui_holder: Holder,
is_well_formed = not any(map(
lambda x: isinstance(x, Symbol) and x.value in ["unquote", "quasiquote", "unquote-splicing"], lst))

visual_expression = VisualExpression(expr)
gui_holder.link_visual(visual_expression)
visual_expression = gui_holder.expression
if not is_well_formed:
visual_expression.children[2:] = []

Expand Down Expand Up @@ -437,7 +431,6 @@ def execute(self, operands: List[Expression], frame: Frame, gui_holder: Holder,
code = "(begin-noexcept" + "\n".join(file.readlines()) + "\n)"
buffer = TokenBuffer([code])
expr = get_expression(buffer)
# noinspection PyTypeChecker
gui_holder.expression.set_entries([VisualExpression(expr, gui_holder.expression.display_value)])
gui_holder.apply()
return evaluate(expr, frame, gui_holder.expression.children[0], True)
Expand Down Expand Up @@ -477,7 +470,6 @@ def execute(self, operands: List[Expression], frame: Frame, gui_holder: Holder,
return operand.expr
if logger.fragile:
raise IrreversibleOperationError()
# noinspection PyTypeChecker
gui_holder.expression.set_entries([VisualExpression(operand.expr, gui_holder.expression.display_value)])
gui_holder.apply()
evaluated = evaluate(operand.expr, operand.frame, gui_holder.expression.children[0])
Expand Down

0 comments on commit 57ee9ef

Please sign in to comment.