mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
runtime: aggregate defer allocations
benchmark old ns/op new ns/op delta BenchmarkDefer 165 113 -31.52% BenchmarkDefer10 155 103 -33.55% BenchmarkDeferMany 216 158 -26.85% benchmark old allocs new allocs delta BenchmarkDefer 1 0 -100.00% BenchmarkDefer10 1 0 -100.00% BenchmarkDeferMany 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkDefer 64 0 -100.00% BenchmarkDefer10 64 0 -100.00% BenchmarkDeferMany 64 66 3.12% Fixes #2364. R=ken2 CC=golang-dev https://golang.org/cl/7001051
This commit is contained in:
parent
e09f1e7a46
commit
0de71619ce
4 changed files with 180 additions and 39 deletions
|
|
@ -121,7 +121,7 @@ runtime·cgocall(void (*fn)(void*), void *arg)
|
||||||
* Lock g to m to ensure we stay on the same stack if we do a
|
* Lock g to m to ensure we stay on the same stack if we do a
|
||||||
* cgo callback.
|
* cgo callback.
|
||||||
*/
|
*/
|
||||||
d.nofree = false;
|
d.special = false;
|
||||||
if(m->lockedg == nil) {
|
if(m->lockedg == nil) {
|
||||||
m->lockedg = g;
|
m->lockedg = g;
|
||||||
g->lockedm = m;
|
g->lockedm = m;
|
||||||
|
|
@ -131,7 +131,7 @@ runtime·cgocall(void (*fn)(void*), void *arg)
|
||||||
d.siz = 0;
|
d.siz = 0;
|
||||||
d.link = g->defer;
|
d.link = g->defer;
|
||||||
d.argp = (void*)-1; // unused because unlockm never recovers
|
d.argp = (void*)-1; // unused because unlockm never recovers
|
||||||
d.nofree = true;
|
d.special = true;
|
||||||
g->defer = &d;
|
g->defer = &d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,7 +160,7 @@ runtime·cgocall(void (*fn)(void*), void *arg)
|
||||||
m->cgomal = nil;
|
m->cgomal = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(d.nofree) {
|
if(d.special) {
|
||||||
if(g->defer != &d || d.fn != (byte*)unlockm)
|
if(g->defer != &d || d.fn != (byte*)unlockm)
|
||||||
runtime·throw("runtime: bad defer entry in cgocallback");
|
runtime·throw("runtime: bad defer entry in cgocallback");
|
||||||
g->defer = d.link;
|
g->defer = d.link;
|
||||||
|
|
@ -236,7 +236,7 @@ runtime·cgocallbackg(void (*fn)(void), void *arg, uintptr argsize)
|
||||||
d.siz = 0;
|
d.siz = 0;
|
||||||
d.link = g->defer;
|
d.link = g->defer;
|
||||||
d.argp = (void*)-1; // unused because unwindm never recovers
|
d.argp = (void*)-1; // unused because unwindm never recovers
|
||||||
d.nofree = true;
|
d.special = true;
|
||||||
g->defer = &d;
|
g->defer = &d;
|
||||||
|
|
||||||
if(raceenabled)
|
if(raceenabled)
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,106 @@
|
||||||
uint32 runtime·panicking;
|
uint32 runtime·panicking;
|
||||||
static Lock paniclk;
|
static Lock paniclk;
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
DeferChunkSize = 2048
|
||||||
|
};
|
||||||
|
|
||||||
|
// Allocate a Defer, usually as part of the larger frame of deferred functions.
|
||||||
|
// Each defer must be released with both popdefer and freedefer.
|
||||||
|
static Defer*
|
||||||
|
newdefer(int32 siz)
|
||||||
|
{
|
||||||
|
int32 total;
|
||||||
|
DeferChunk *c;
|
||||||
|
Defer *d;
|
||||||
|
|
||||||
|
c = g->dchunk;
|
||||||
|
total = sizeof(*d) + ROUND(siz, sizeof(uintptr)) - sizeof(d->args);
|
||||||
|
if(c == nil || total > DeferChunkSize - c->off) {
|
||||||
|
if(total > DeferChunkSize / 2) {
|
||||||
|
// Not worth putting in any chunk.
|
||||||
|
// Allocate a separate block.
|
||||||
|
d = runtime·malloc(total);
|
||||||
|
d->siz = siz;
|
||||||
|
d->special = 1;
|
||||||
|
d->free = 1;
|
||||||
|
d->link = g->defer;
|
||||||
|
g->defer = d;
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cannot fit in current chunk.
|
||||||
|
// Switch to next chunk, allocating if necessary.
|
||||||
|
c = g->dchunknext;
|
||||||
|
if(c == nil)
|
||||||
|
c = runtime·malloc(DeferChunkSize);
|
||||||
|
c->prev = g->dchunk;
|
||||||
|
c->off = sizeof(*c);
|
||||||
|
g->dchunk = c;
|
||||||
|
g->dchunknext = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
d = (Defer*)((byte*)c + c->off);
|
||||||
|
c->off += total;
|
||||||
|
d->siz = siz;
|
||||||
|
d->special = 0;
|
||||||
|
d->free = 0;
|
||||||
|
d->link = g->defer;
|
||||||
|
g->defer = d;
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop the current defer from the defer stack.
|
||||||
|
// Its contents are still valid until the goroutine begins executing again.
|
||||||
|
// In particular it is safe to call reflect.call(d->fn, d->argp, d->siz) after
|
||||||
|
// popdefer returns.
|
||||||
|
static void
|
||||||
|
popdefer(void)
|
||||||
|
{
|
||||||
|
Defer *d;
|
||||||
|
DeferChunk *c;
|
||||||
|
int32 total;
|
||||||
|
|
||||||
|
d = g->defer;
|
||||||
|
if(d == nil)
|
||||||
|
runtime·throw("runtime: popdefer nil");
|
||||||
|
g->defer = d->link;
|
||||||
|
if(d->special) {
|
||||||
|
// Nothing else to do.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
total = sizeof(*d) + ROUND(d->siz, sizeof(uintptr)) - sizeof(d->args);
|
||||||
|
c = g->dchunk;
|
||||||
|
if(c == nil || (byte*)d+total != (byte*)c+c->off)
|
||||||
|
runtime·throw("runtime: popdefer phase error");
|
||||||
|
c->off -= total;
|
||||||
|
if(c->off == sizeof(*c)) {
|
||||||
|
// Chunk now empty, so pop from stack.
|
||||||
|
// Save in dchunknext both to help with pingponging between frames
|
||||||
|
// and to make sure d is still valid on return.
|
||||||
|
if(g->dchunknext != nil)
|
||||||
|
runtime·free(g->dchunknext);
|
||||||
|
g->dchunknext = c;
|
||||||
|
g->dchunk = c->prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free the given defer.
|
||||||
|
// For defers in the per-goroutine chunk this just clears the saved arguments.
|
||||||
|
// For large defers allocated on the heap, this frees them.
|
||||||
|
// The defer cannot be used after this call.
|
||||||
|
static void
|
||||||
|
freedefer(Defer *d)
|
||||||
|
{
|
||||||
|
if(d->special) {
|
||||||
|
if(d->free)
|
||||||
|
runtime·free(d);
|
||||||
|
} else {
|
||||||
|
runtime·memclr((byte*)d->args, d->siz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create a new deferred function fn with siz bytes of arguments.
|
// Create a new deferred function fn with siz bytes of arguments.
|
||||||
// The compiler turns a defer statement into a call to this.
|
// The compiler turns a defer statement into a call to this.
|
||||||
// Cannot split the stack because it assumes that the arguments
|
// Cannot split the stack because it assumes that the arguments
|
||||||
|
|
@ -22,14 +122,9 @@ uintptr
|
||||||
runtime·deferproc(int32 siz, byte* fn, ...)
|
runtime·deferproc(int32 siz, byte* fn, ...)
|
||||||
{
|
{
|
||||||
Defer *d;
|
Defer *d;
|
||||||
int32 mallocsiz;
|
|
||||||
|
|
||||||
mallocsiz = sizeof(*d);
|
d = newdefer(siz);
|
||||||
if(siz > sizeof(d->args))
|
|
||||||
mallocsiz += siz - sizeof(d->args);
|
|
||||||
d = runtime·malloc(mallocsiz);
|
|
||||||
d->fn = fn;
|
d->fn = fn;
|
||||||
d->siz = siz;
|
|
||||||
d->pc = runtime·getcallerpc(&siz);
|
d->pc = runtime·getcallerpc(&siz);
|
||||||
if(thechar == '5')
|
if(thechar == '5')
|
||||||
d->argp = (byte*)(&fn+2); // skip caller's saved link register
|
d->argp = (byte*)(&fn+2); // skip caller's saved link register
|
||||||
|
|
@ -37,9 +132,6 @@ runtime·deferproc(int32 siz, byte* fn, ...)
|
||||||
d->argp = (byte*)(&fn+1);
|
d->argp = (byte*)(&fn+1);
|
||||||
runtime·memmove(d->args, d->argp, d->siz);
|
runtime·memmove(d->args, d->argp, d->siz);
|
||||||
|
|
||||||
d->link = g->defer;
|
|
||||||
g->defer = d;
|
|
||||||
|
|
||||||
// deferproc returns 0 normally.
|
// deferproc returns 0 normally.
|
||||||
// a deferred func that stops a panic
|
// a deferred func that stops a panic
|
||||||
// makes the deferproc return 1.
|
// makes the deferproc return 1.
|
||||||
|
|
@ -73,10 +165,9 @@ runtime·deferreturn(uintptr arg0)
|
||||||
if(d->argp != argp)
|
if(d->argp != argp)
|
||||||
return;
|
return;
|
||||||
runtime·memmove(argp, d->args, d->siz);
|
runtime·memmove(argp, d->args, d->siz);
|
||||||
g->defer = d->link;
|
|
||||||
fn = d->fn;
|
fn = d->fn;
|
||||||
if(!d->nofree)
|
popdefer();
|
||||||
runtime·free(d);
|
freedefer(d);
|
||||||
runtime·jmpdefer(fn, argp);
|
runtime·jmpdefer(fn, argp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,10 +178,9 @@ rundefer(void)
|
||||||
Defer *d;
|
Defer *d;
|
||||||
|
|
||||||
while((d = g->defer) != nil) {
|
while((d = g->defer) != nil) {
|
||||||
g->defer = d->link;
|
popdefer();
|
||||||
reflect·call(d->fn, (byte*)d->args, d->siz);
|
reflect·call(d->fn, (byte*)d->args, d->siz);
|
||||||
if(!d->nofree)
|
freedefer(d);
|
||||||
runtime·free(d);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,7 +207,8 @@ runtime·panic(Eface e)
|
||||||
{
|
{
|
||||||
Defer *d;
|
Defer *d;
|
||||||
Panic *p;
|
Panic *p;
|
||||||
|
void *pc, *argp;
|
||||||
|
|
||||||
p = runtime·mal(sizeof *p);
|
p = runtime·mal(sizeof *p);
|
||||||
p->arg = e;
|
p->arg = e;
|
||||||
p->link = g->panic;
|
p->link = g->panic;
|
||||||
|
|
@ -129,23 +220,23 @@ runtime·panic(Eface e)
|
||||||
if(d == nil)
|
if(d == nil)
|
||||||
break;
|
break;
|
||||||
// take defer off list in case of recursive panic
|
// take defer off list in case of recursive panic
|
||||||
g->defer = d->link;
|
popdefer();
|
||||||
g->ispanic = true; // rock for newstack, where reflect.call ends up
|
g->ispanic = true; // rock for newstack, where reflect.call ends up
|
||||||
|
argp = d->argp;
|
||||||
|
pc = d->pc;
|
||||||
reflect·call(d->fn, (byte*)d->args, d->siz);
|
reflect·call(d->fn, (byte*)d->args, d->siz);
|
||||||
|
freedefer(d);
|
||||||
if(p->recovered) {
|
if(p->recovered) {
|
||||||
g->panic = p->link;
|
g->panic = p->link;
|
||||||
if(g->panic == nil) // must be done with signal
|
if(g->panic == nil) // must be done with signal
|
||||||
g->sig = 0;
|
g->sig = 0;
|
||||||
runtime·free(p);
|
runtime·free(p);
|
||||||
// put recovering defer back on list
|
// Pass information about recovering frame to recovery.
|
||||||
// for scheduler to find.
|
g->sigcode0 = (uintptr)argp;
|
||||||
d->link = g->defer;
|
g->sigcode1 = (uintptr)pc;
|
||||||
g->defer = d;
|
|
||||||
runtime·mcall(recovery);
|
runtime·mcall(recovery);
|
||||||
runtime·throw("recovery failed"); // mcall should not return
|
runtime·throw("recovery failed"); // mcall should not return
|
||||||
}
|
}
|
||||||
if(!d->nofree)
|
|
||||||
runtime·free(d);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ran out of deferred calls - old-school panic now
|
// ran out of deferred calls - old-school panic now
|
||||||
|
|
@ -160,14 +251,15 @@ runtime·panic(Eface e)
|
||||||
static void
|
static void
|
||||||
recovery(G *gp)
|
recovery(G *gp)
|
||||||
{
|
{
|
||||||
Defer *d;
|
void *argp;
|
||||||
|
void *pc;
|
||||||
// Rewind gp's stack; we're running on m->g0's stack.
|
|
||||||
d = gp->defer;
|
// Info about defer passed in G struct.
|
||||||
gp->defer = d->link;
|
argp = (void*)gp->sigcode0;
|
||||||
|
pc = (void*)gp->sigcode1;
|
||||||
|
|
||||||
// Unwind to the stack frame with d's arguments in it.
|
// Unwind to the stack frame with d's arguments in it.
|
||||||
runtime·unwindstack(gp, d->argp);
|
runtime·unwindstack(gp, argp);
|
||||||
|
|
||||||
// Make the deferproc for this d return again,
|
// Make the deferproc for this d return again,
|
||||||
// this time returning 1. The calling function will
|
// this time returning 1. The calling function will
|
||||||
|
|
@ -179,12 +271,10 @@ recovery(G *gp)
|
||||||
// before it tests the return value.)
|
// before it tests the return value.)
|
||||||
// On the arm there are 2 saved LRs mixed in too.
|
// On the arm there are 2 saved LRs mixed in too.
|
||||||
if(thechar == '5')
|
if(thechar == '5')
|
||||||
gp->sched.sp = (uintptr)d->argp - 4*sizeof(uintptr);
|
gp->sched.sp = (uintptr)argp - 4*sizeof(uintptr);
|
||||||
else
|
else
|
||||||
gp->sched.sp = (uintptr)d->argp - 2*sizeof(uintptr);
|
gp->sched.sp = (uintptr)argp - 2*sizeof(uintptr);
|
||||||
gp->sched.pc = d->pc;
|
gp->sched.pc = pc;
|
||||||
if(!d->nofree)
|
|
||||||
runtime·free(d);
|
|
||||||
runtime·gogo(&gp->sched, 1);
|
runtime·gogo(&gp->sched, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ typedef struct Type Type;
|
||||||
typedef struct ChanType ChanType;
|
typedef struct ChanType ChanType;
|
||||||
typedef struct MapType MapType;
|
typedef struct MapType MapType;
|
||||||
typedef struct Defer Defer;
|
typedef struct Defer Defer;
|
||||||
|
typedef struct DeferChunk DeferChunk;
|
||||||
typedef struct Panic Panic;
|
typedef struct Panic Panic;
|
||||||
typedef struct Hmap Hmap;
|
typedef struct Hmap Hmap;
|
||||||
typedef struct Hchan Hchan;
|
typedef struct Hchan Hchan;
|
||||||
|
|
@ -218,6 +219,8 @@ struct G
|
||||||
int32 sig;
|
int32 sig;
|
||||||
int32 writenbuf;
|
int32 writenbuf;
|
||||||
byte* writebuf;
|
byte* writebuf;
|
||||||
|
DeferChunk *dchunk;
|
||||||
|
DeferChunk *dchunknext;
|
||||||
uintptr sigcode0;
|
uintptr sigcode0;
|
||||||
uintptr sigcode1;
|
uintptr sigcode1;
|
||||||
uintptr sigpc;
|
uintptr sigpc;
|
||||||
|
|
@ -518,7 +521,8 @@ void runtime·nilintercopy(uintptr, void*, void*);
|
||||||
struct Defer
|
struct Defer
|
||||||
{
|
{
|
||||||
int32 siz;
|
int32 siz;
|
||||||
bool nofree;
|
bool special; // not part of defer frame
|
||||||
|
bool free; // if special, free when done
|
||||||
byte* argp; // where args were copied from
|
byte* argp; // where args were copied from
|
||||||
byte* pc;
|
byte* pc;
|
||||||
byte* fn;
|
byte* fn;
|
||||||
|
|
@ -526,6 +530,12 @@ struct Defer
|
||||||
void* args[1]; // padded to actual size
|
void* args[1]; // padded to actual size
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct DeferChunk
|
||||||
|
{
|
||||||
|
DeferChunk *prev;
|
||||||
|
uintptr off;
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* panics
|
* panics
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -38,3 +38,44 @@ func BenchmarkIfaceCmpNil100(b *testing.B) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkDefer(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
defer1()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func defer1() {
|
||||||
|
defer func(x, y, z int) {
|
||||||
|
if recover() != nil || x != 1 || y != 2 || z != 3 {
|
||||||
|
panic("bad recover")
|
||||||
|
}
|
||||||
|
}(1, 2, 3)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDefer10(b *testing.B) {
|
||||||
|
for i := 0; i < b.N/10; i++ {
|
||||||
|
defer2()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func defer2() {
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
defer func(x, y, z int) {
|
||||||
|
if recover() != nil || x != 1 || y != 2 || z != 3 {
|
||||||
|
panic("bad recover")
|
||||||
|
}
|
||||||
|
}(1, 2, 3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDeferMany(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
defer func(x, y, z int) {
|
||||||
|
if recover() != nil || x != 1 || y != 2 || z != 3 {
|
||||||
|
panic("bad recover")
|
||||||
|
}
|
||||||
|
}(1, 2, 3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue