Commit 92ad5eec authored by Brian Atkinson's avatar Brian Atkinson Committed by Dylan Trotter

Cache frames for fast reuse. (#187)

Rather than allocating a frame on every call, we keep an already allocated list
of frames ready to go. This trades lightly more memory usage for faster calls.
parent 00679fe8
......@@ -337,6 +337,7 @@ func builtinFrame(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) {
if raised := checkFunctionArgs(f, "__frame__", args); raised != nil {
return nil, raised
}
f.taken = true
return f.ToObject(), nil
}
......
......@@ -339,7 +339,7 @@ func captureStdout(f *Frame, fn func() *BaseException) (string, *BaseException)
func TestBuiltinPrint(t *testing.T) {
fun := wrapFuncForTest(func(f *Frame, args *Tuple, kwargs KWArgs) (string, *BaseException) {
return captureStdout(f, func() *BaseException {
_, raised := builtinPrint(newFrame(nil), args.elems, kwargs)
_, raised := builtinPrint(NewRootFrame(), args.elems, kwargs)
return raised
})
})
......
......@@ -134,10 +134,11 @@ func (c *Code) Eval(f *Frame, globals *Dict, args Args, kwargs KWArgs) (*Object,
}
}
oldExc, oldTraceback := f.ExcInfo()
next := newFrame(f)
next := newChildFrame(f)
next.code = c
next.globals = globals
ret, raised := c.fn(next, bodyArgs)
next.release()
f.FreeArgs(bodyArgs)
if raised == nil {
// Restore exc_info to what it was when we left the previous
......
......@@ -39,20 +39,45 @@ type Frame struct {
globals *Dict `attr:"f_globals"`
lineno int `attr:"f_lineno"`
code *Code `attr:"f_code"`
taken bool
}
// NewRootFrame creates a Frame that is the bottom of a new stack.
func NewRootFrame() *Frame {
return newFrame(nil)
f := &Frame{Object: Object{typ: FrameType}}
f.pushFrame(nil)
return f
}
// newFrame creates a new Frame whose parent frame is back.
func newFrame(back *Frame) *Frame {
f := &Frame{Object: Object{typ: FrameType}}
// newChildFrame creates a new Frame whose parent frame is back.
func newChildFrame(back *Frame) *Frame {
f := back.frameCache
if f == nil {
f = &Frame{Object: Object{typ: FrameType}}
} else {
back.frameCache, f.back = f.back, nil
// Reset local state late.
f.checkpoints = f.checkpoints[:0]
f.state = 0
f.lineno = 0
}
f.pushFrame(back)
return f
}
func (f *Frame) release() {
if !f.taken {
// TODO: Track cache depth and release memory.
f.frameCache, f.back = f, f.frameCache
// Clear pointers early.
f.dict = nil
f.globals = nil
f.code = nil
} else if f.back != nil {
f.back.taken = true
}
}
// pushFrame adds f to the top of the stack, above back.
func (f *Frame) pushFrame(back *Frame) {
f.back = back
......
......@@ -174,12 +174,12 @@ func TestFrameRaiseType(t *testing.T) {
func TestReprEnterLeave(t *testing.T) {
o := newObject(ObjectType)
parent := NewRootFrame()
child := newFrame(parent)
child := newChildFrame(parent)
wantParent := NewRootFrame()
wantParent.reprState = map[*Object]bool{o: true}
child.reprEnter(o)
// After child.reprEnter(), expect the parent's reprState to contain o.
if wantChild := newFrame(parent); !reflect.DeepEqual(child, wantChild) {
if wantChild := newChildFrame(parent); !reflect.DeepEqual(child, wantChild) {
t.Errorf("reprEnter: child frame was %#v, want %#v", child, wantChild)
} else if !reflect.DeepEqual(parent, wantParent) {
t.Errorf("reprEnter: parent frame was %#v, want %#v", parent, wantParent)
......@@ -187,7 +187,7 @@ func TestReprEnterLeave(t *testing.T) {
wantParent.reprState = map[*Object]bool{}
child.reprLeave(o)
// Expect the parent's reprState to be empty after reprLeave().
if wantChild := newFrame(parent); !reflect.DeepEqual(child, wantChild) {
if wantChild := newChildFrame(parent); !reflect.DeepEqual(child, wantChild) {
t.Errorf("reprLeave: child frame was %#v, want %#v", child, wantChild)
} else if !reflect.DeepEqual(parent, wantParent) {
t.Errorf("reprLeave: parent frame was %#v, want %#v", parent, wantParent)
......@@ -197,8 +197,8 @@ func TestReprEnterLeave(t *testing.T) {
func TestFrameRoot(t *testing.T) {
f1 := NewRootFrame()
f2 := newFrame(f1)
frames := []*Frame{f1, f2, newFrame(f2)}
f2 := newChildFrame(f1)
frames := []*Frame{f1, f2, newChildFrame(f2)}
for _, f := range frames {
if f.threadState != f1.threadState {
t.Errorf("frame threadState was %v, want %v", f.threadState, f1.threadState)
......
......@@ -44,6 +44,12 @@ type Generator struct {
// NewGenerator returns a new Generator object that runs the given Block b.
func NewGenerator(f *Frame, fn func(*Object) (*Object, *BaseException)) *Generator {
f.taken = true // Claim the frame from being returned.
// The code generator basically gives us the Frame, so we can tare it
// off and prevent a parasitic `taken` from creeping up the frames.
f.back = nil
return &Generator{Object: Object{typ: GeneratorType}, frame: f, fn: fn}
}
......
......@@ -470,7 +470,7 @@ func TestNativeTypeName(t *testing.T) {
}
func TestNewNativeFieldChecksInstanceType(t *testing.T) {
f := newFrame(nil)
f := NewRootFrame()
// Given a native object
native, raised := WrapNative(f, reflect.ValueOf(struct{ foo string }{}))
......
......@@ -35,6 +35,11 @@ type threadState struct {
// is full are dropped. If the cache is empty then a new args slice
// will be allocated.
argsCache []Args
// frameCache is a local cache of allocated frames almost ready for
// reuse. The cache is maintained through the Frame `back` pointer as a
// singly linked list.
frameCache *Frame
}
func newThreadState() *threadState {
......
......@@ -27,6 +27,7 @@ type Traceback struct {
}
func newTraceback(f *Frame, next *Traceback) *Traceback {
f.taken = true
return &Traceback{Object{typ: TracebackType}, f, next, f.lineno}
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment