cmd/gc: allocate select descriptor on stack

benchmark                      old ns/op     new ns/op     delta
BenchmarkSelectUncontended     220           165           -25.00%
BenchmarkSelectContended       209           161           -22.97%
BenchmarkSelectProdCons        1042          904           -13.24%

But more importantly this change will allow
to get rid of free function in runtime.

Fixes #6494.

LGTM=rsc, khr
R=golang-codereviews, rsc, dominik.honnef, khr
CC=golang-codereviews, remyoudompheng
https://golang.org/cl/107670043
This commit is contained in:
Dmitriy Vyukov 2014-07-20 15:07:10 +04:00
parent 1a116c76ae
commit 40d7d5a656
10 changed files with 98 additions and 39 deletions

View file

@ -86,7 +86,7 @@ char *runtimeimport =
"func @\"\".selectnbsend (@\"\".chanType·2 *byte, @\"\".hchan·3 chan<- any, @\"\".elem·4 *any) (? bool)\n" "func @\"\".selectnbsend (@\"\".chanType·2 *byte, @\"\".hchan·3 chan<- any, @\"\".elem·4 *any) (? bool)\n"
"func @\"\".selectnbrecv (@\"\".chanType·2 *byte, @\"\".elem·3 *any, @\"\".hchan·4 <-chan any) (? bool)\n" "func @\"\".selectnbrecv (@\"\".chanType·2 *byte, @\"\".elem·3 *any, @\"\".hchan·4 <-chan any) (? bool)\n"
"func @\"\".selectnbrecv2 (@\"\".chanType·2 *byte, @\"\".elem·3 *any, @\"\".received·4 *bool, @\"\".hchan·5 <-chan any) (? bool)\n" "func @\"\".selectnbrecv2 (@\"\".chanType·2 *byte, @\"\".elem·3 *any, @\"\".received·4 *bool, @\"\".hchan·5 <-chan any) (? bool)\n"
"func @\"\".newselect (@\"\".size·2 int32) (@\"\".sel·1 *byte)\n" "func @\"\".newselect (@\"\".sel·1 *byte, @\"\".selsize·2 int64, @\"\".size·3 int32)\n"
"func @\"\".selectsend (@\"\".sel·2 *byte, @\"\".hchan·3 chan<- any, @\"\".elem·4 *any) (@\"\".selected·1 bool)\n" "func @\"\".selectsend (@\"\".sel·2 *byte, @\"\".hchan·3 chan<- any, @\"\".elem·4 *any) (@\"\".selected·1 bool)\n"
"func @\"\".selectrecv (@\"\".sel·2 *byte, @\"\".hchan·3 <-chan any, @\"\".elem·4 *any) (@\"\".selected·1 bool)\n" "func @\"\".selectrecv (@\"\".sel·2 *byte, @\"\".hchan·3 <-chan any, @\"\".elem·4 *any) (@\"\".selected·1 bool)\n"
"func @\"\".selectrecv2 (@\"\".sel·2 *byte, @\"\".hchan·3 <-chan any, @\"\".elem·4 *any, @\"\".received·5 *bool) (@\"\".selected·1 bool)\n" "func @\"\".selectrecv2 (@\"\".sel·2 *byte, @\"\".hchan·3 <-chan any, @\"\".elem·4 *any, @\"\".received·5 *bool) (@\"\".selected·1 bool)\n"

View file

@ -112,7 +112,7 @@ func selectnbsend(chanType *byte, hchan chan<- any, elem *any) bool
func selectnbrecv(chanType *byte, elem *any, hchan <-chan any) bool func selectnbrecv(chanType *byte, elem *any, hchan <-chan any) bool
func selectnbrecv2(chanType *byte, elem *any, received *bool, hchan <-chan any) bool func selectnbrecv2(chanType *byte, elem *any, received *bool, hchan <-chan any) bool
func newselect(size int32) (sel *byte) func newselect(sel *byte, selsize int64, size int32)
func selectsend(sel *byte, hchan chan<- any, elem *any) (selected bool) func selectsend(sel *byte, hchan chan<- any, elem *any) (selected bool)
func selectrecv(sel *byte, hchan <-chan any, elem *any) (selected bool) func selectrecv(sel *byte, hchan <-chan any, elem *any) (selected bool)
func selectrecv2(sel *byte, hchan <-chan any, elem *any, received *bool) (selected bool) func selectrecv2(sel *byte, hchan <-chan any, elem *any, received *bool) (selected bool)

View file

@ -10,6 +10,8 @@
#include <libc.h> #include <libc.h>
#include "go.h" #include "go.h"
static Type* selecttype(int32 size);
void void
typecheckselect(Node *sel) typecheckselect(Node *sel)
{ {
@ -95,7 +97,7 @@ void
walkselect(Node *sel) walkselect(Node *sel)
{ {
int lno, i; int lno, i;
Node *n, *r, *a, *var, *cas, *dflt, *ch; Node *n, *r, *a, *var, *selv, *cas, *dflt, *ch;
NodeList *l, *init; NodeList *l, *init;
if(sel->list == nil && sel->xoffset != 0) if(sel->list == nil && sel->xoffset != 0)
@ -257,8 +259,13 @@ walkselect(Node *sel)
// generate sel-struct // generate sel-struct
setlineno(sel); setlineno(sel);
var = temp(ptrto(types[TUINT8])); selv = temp(selecttype(sel->xoffset));
r = nod(OAS, var, mkcall("newselect", var->type, nil, nodintconst(sel->xoffset))); selv->esc = EscNone;
r = nod(OAS, selv, N);
typecheck(&r, Etop);
init = list(init, r);
var = conv(conv(nod(OADDR, selv, N), types[TUNSAFEPTR]), ptrto(types[TUINT8]));
r = mkcall("newselect", T, nil, var, nodintconst(selv->type->width), nodintconst(sel->xoffset));
typecheck(&r, Etop); typecheck(&r, Etop);
init = list(init, r); init = list(init, r);
@ -301,6 +308,8 @@ walkselect(Node *sel)
break; break;
} }
} }
// selv is no longer alive after use.
r->nbody = list(r->nbody, nod(OVARKILL, selv, N));
r->nbody = concat(r->nbody, cas->nbody); r->nbody = concat(r->nbody, cas->nbody);
r->nbody = list(r->nbody, nod(OBREAK, N, N)); r->nbody = list(r->nbody, nod(OBREAK, N, N));
init = list(init, r); init = list(init, r);
@ -316,3 +325,50 @@ out:
walkstmtlist(sel->nbody); walkstmtlist(sel->nbody);
lineno = lno; lineno = lno;
} }
// Keep in sync with src/pkg/runtime/chan.h.
static Type*
selecttype(int32 size)
{
Node *sel, *sudog, *scase, *arr;
// TODO(dvyukov): it's possible to generate SudoG and Scase only once
// and then cache; and also cache Select per size.
sudog = nod(OTSTRUCT, N, N);
sudog->list = list(sudog->list, nod(ODCLFIELD, newname(lookup("g")), typenod(ptrto(types[TUINT8]))));
sudog->list = list(sudog->list, nod(ODCLFIELD, newname(lookup("selectdone")), typenod(ptrto(types[TUINT8]))));
sudog->list = list(sudog->list, nod(ODCLFIELD, newname(lookup("link")), typenod(ptrto(types[TUINT8]))));
sudog->list = list(sudog->list, nod(ODCLFIELD, newname(lookup("elem")), typenod(ptrto(types[TUINT8]))));
sudog->list = list(sudog->list, nod(ODCLFIELD, newname(lookup("releasetime")), typenod(types[TUINT64])));
typecheck(&sudog, Etype);
sudog->type->noalg = 1;
sudog->type->local = 1;
scase = nod(OTSTRUCT, N, N);
scase->list = list(scase->list, nod(ODCLFIELD, newname(lookup("sg")), sudog));
scase->list = list(scase->list, nod(ODCLFIELD, newname(lookup("chan")), typenod(ptrto(types[TUINT8]))));
scase->list = list(scase->list, nod(ODCLFIELD, newname(lookup("pc")), typenod(ptrto(types[TUINT8]))));
scase->list = list(scase->list, nod(ODCLFIELD, newname(lookup("kind")), typenod(types[TUINT16])));
scase->list = list(scase->list, nod(ODCLFIELD, newname(lookup("so")), typenod(types[TUINT16])));
scase->list = list(scase->list, nod(ODCLFIELD, newname(lookup("receivedp")), typenod(ptrto(types[TUINT8]))));
typecheck(&scase, Etype);
scase->type->noalg = 1;
scase->type->local = 1;
sel = nod(OTSTRUCT, N, N);
sel->list = list(sel->list, nod(ODCLFIELD, newname(lookup("tcase")), typenod(types[TUINT16])));
sel->list = list(sel->list, nod(ODCLFIELD, newname(lookup("ncase")), typenod(types[TUINT16])));
sel->list = list(sel->list, nod(ODCLFIELD, newname(lookup("pollorder")), typenod(ptrto(types[TUINT8]))));
sel->list = list(sel->list, nod(ODCLFIELD, newname(lookup("lockorder")), typenod(ptrto(types[TUINT8]))));
arr = nod(OTARRAY, nodintconst(size), scase);
sel->list = list(sel->list, nod(ODCLFIELD, newname(lookup("scase")), arr));
arr = nod(OTARRAY, nodintconst(size), typenod(ptrto(types[TUINT8])));
sel->list = list(sel->list, nod(ODCLFIELD, newname(lookup("lockorderarr")), arr));
arr = nod(OTARRAY, nodintconst(size), typenod(types[TUINT16]));
sel->list = list(sel->list, nod(ODCLFIELD, newname(lookup("pollorderarr")), arr));
typecheck(&sel, Etype);
sel->type->noalg = 1;
sel->type->local = 1;
return sel->type;
}

View file

@ -12,5 +12,6 @@ enum {
#else #else
PhysPageSize = 4096, PhysPageSize = 4096,
#endif #endif
PCQuantum = 1 PCQuantum = 1,
Int64Align = 4
}; };

View file

@ -20,5 +20,6 @@ enum {
#endif // Windows #endif // Windows
#endif // Solaris #endif // Solaris
PhysPageSize = 4096, PhysPageSize = 4096,
PCQuantum = 1 PCQuantum = 1,
Int64Align = 8
}; };

View file

@ -12,5 +12,6 @@ enum {
#else #else
PhysPageSize = 4096, PhysPageSize = 4096,
#endif #endif
PCQuantum = 1 PCQuantum = 1,
Int64Align = 8
}; };

View file

@ -12,5 +12,6 @@ enum {
#else #else
PhysPageSize = 4096, PhysPageSize = 4096,
#endif #endif
PCQuantum = 4 PCQuantum = 4,
Int64Align = 4
}; };

View file

@ -434,32 +434,25 @@ func reflect·chanrecv(t *ChanType, c *Hchan, nb bool, elem *byte) (selected boo
selected = chanrecv(t, c, elem, !nb, &received); selected = chanrecv(t, c, elem, !nb, &received);
} }
static Select* newselect(int32); static int64
selectsize(int32 size)
{
Select *sel;
int64 selsize;
#pragma textflag NOSPLIT selsize = sizeof(*sel) +
func newselect(size int32) (sel *byte) { (size-1)*sizeof(sel->scase[0]) +
sel = (byte*)newselect(size); size*sizeof(sel->lockorder[0]) +
size*sizeof(sel->pollorder[0]);
return ROUND(selsize, Int64Align);
} }
static Select* #pragma textflag NOSPLIT
newselect(int32 size) func newselect(sel *Select, selsize int64, size int32) {
{ if(selsize != selectsize(size)) {
int32 n; runtime·printf("runtime: bad select size %D, want %D\n", selsize, selectsize(size));
Select *sel; runtime·throw("bad select size");
}
n = 0;
if(size > 1)
n = size-1;
// allocate all the memory we need in a single allocation
// start with Select with size cases
// then lockorder with size entries
// then pollorder with size entries
sel = runtime·mal(sizeof(*sel) +
n*sizeof(sel->scase[0]) +
size*sizeof(sel->lockorder[0]) +
size*sizeof(sel->pollorder[0]));
sel->tcase = size; sel->tcase = size;
sel->ncase = 0; sel->ncase = 0;
sel->lockorder = (void*)(sel->scase + size); sel->lockorder = (void*)(sel->scase + size);
@ -467,7 +460,6 @@ newselect(int32 size)
if(debug) if(debug)
runtime·printf("newselect s=%p size=%d\n", sel, size); runtime·printf("newselect s=%p size=%d\n", sel, size);
return sel;
} }
// cut in half to give stack a chance to split // cut in half to give stack a chance to split
@ -960,7 +952,6 @@ retc:
} }
if(cas->sg.releasetime > 0) if(cas->sg.releasetime > 0)
runtime·blockevent(cas->sg.releasetime - t0, 2); runtime·blockevent(cas->sg.releasetime - t0, 2);
runtime·free(sel);
return pc; return pc;
sclose: sclose:
@ -997,7 +988,9 @@ func reflect·rselect(cases Slice) (chosen int, recvOK bool) {
rcase = (runtimeSelect*)cases.array; rcase = (runtimeSelect*)cases.array;
sel = newselect(cases.len); // FlagNoScan is safe here, because all objects are also referenced from cases.
sel = runtime·mallocgc(selectsize(cases.len), 0, FlagNoScan);
runtime·newselect(sel, selectsize(cases.len), cases.len);
for(i=0; i<cases.len; i++) { for(i=0; i<cases.len; i++) {
rc = &rcase[i]; rc = &rcase[i];
switch(rc->dir) { switch(rc->dir) {

View file

@ -9,13 +9,15 @@ typedef struct SudoG SudoG;
typedef struct Select Select; typedef struct Select Select;
typedef struct Scase Scase; typedef struct Scase Scase;
// Known to compiler.
// Changes here must also be made in src/cmd/gc/select.c's selecttype.
struct SudoG struct SudoG
{ {
G* g; G* g;
uint32* selectdone; uint32* selectdone;
SudoG* link; SudoG* link;
int64 releasetime;
byte* elem; // data element byte* elem; // data element
int64 releasetime;
}; };
struct WaitQ struct WaitQ
@ -55,6 +57,8 @@ enum
CaseDefault, CaseDefault,
}; };
// Known to compiler.
// Changes here must also be made in src/cmd/gc/select.c's selecttype.
struct Scase struct Scase
{ {
SudoG sg; // must be first member (cast to Scase) SudoG sg; // must be first member (cast to Scase)
@ -65,6 +69,8 @@ struct Scase
bool* receivedp; // pointer to received bool (recv2) bool* receivedp; // pointer to received bool (recv2)
}; };
// Known to compiler.
// Changes here must also be made in src/cmd/gc/select.c's selecttype.
struct Select struct Select
{ {
uint16 tcase; // total count of scase[] uint16 tcase; // total count of scase[]

View file

@ -138,7 +138,7 @@ var b bool
// this used to have a spurious "live at entry to f11a: ~r0" // this used to have a spurious "live at entry to f11a: ~r0"
func f11a() *int { func f11a() *int {
select { // ERROR "live at call to selectgo: autotmp" select { // ERROR "live at call to newselect: autotmp" "live at call to selectgo: autotmp"
case <-c: // ERROR "live at call to selectrecv: autotmp" case <-c: // ERROR "live at call to selectrecv: autotmp"
return nil return nil
case <-c: // ERROR "live at call to selectrecv: autotmp" case <-c: // ERROR "live at call to selectrecv: autotmp"
@ -153,7 +153,7 @@ func f11b() *int {
// get to the bottom of the function. // get to the bottom of the function.
// This used to have a spurious "live at call to printint: p". // This used to have a spurious "live at call to printint: p".
print(1) // nothing live here! print(1) // nothing live here!
select { // ERROR "live at call to selectgo: autotmp" select { // ERROR "live at call to newselect: autotmp" "live at call to selectgo: autotmp"
case <-c: // ERROR "live at call to selectrecv: autotmp" case <-c: // ERROR "live at call to selectrecv: autotmp"
return nil return nil
case <-c: // ERROR "live at call to selectrecv: autotmp" case <-c: // ERROR "live at call to selectrecv: autotmp"
@ -170,7 +170,7 @@ func f11c() *int {
// Unlike previous, the cases in this select fall through, // Unlike previous, the cases in this select fall through,
// so we can get to the println, so p is not dead. // so we can get to the println, so p is not dead.
print(1) // ERROR "live at call to printint: p" print(1) // ERROR "live at call to printint: p"
select { // ERROR "live at call to newselect: p" "live at call to selectgo: autotmp.* p" select { // ERROR "live at call to newselect: autotmp.* p" "live at call to selectgo: autotmp.* p"
case <-c: // ERROR "live at call to selectrecv: autotmp.* p" case <-c: // ERROR "live at call to selectrecv: autotmp.* p"
case <-c: // ERROR "live at call to selectrecv: autotmp.* p" case <-c: // ERROR "live at call to selectrecv: autotmp.* p"
} }