Skip to content

runtime: trim unnecessary fields from scase #40410

@mdempsky

Description

@mdempsky

Currently the runtime.scase structure is 5 words (6 words on 32-bit machines):

type scase struct {
	c           *hchan         // chan
	elem        unsafe.Pointer // data element
	kind        uint16
	pc          uintptr // race pc (for race detector / msan)
	releasetime int64
}

I think this can be trimmed down to just 2: c and elem. Here's how:

  1. The pc field is only needed for race-instrumented builds. Instead of embedding it directly into the scase array (and using stack space even for non-instrumented builds), we can split it out into a separate array that's prepended to the pollorder/lockorder arrays for race-instrumented builds, and omitted entirely for race-instrumented builds. (We'd prepend it because uintptr has stricter alignment than uint16.)

  2. The releasetime field is only needed for the case that actually succeeds. Rather than adding it to each scase, we can just have an extra casreleasetime local variable that parallels cas and casi. (I think: I'm least confident about this as I'm not familiar with the runtime's blocking profiling.)

  3. The kind field currently disambiguates four cases: send, recv, default, and nil. We can eliminate this field with a sequence of three optimizations:

    1. There can be only one default case. So instead of emitting a dummy scase to represent the default case, selectgo can just take an extra boolean parameter indicating whether the call should block or not. When in non-blocking mode, selectgo should just return -1 as the case index, and callers can handle this accordingly. This would simplify selectgo, because it wouldn't need to search for and remember where the caseDefault case is.

    2. caseNil is used internally within selectgo to simplify skipping over send/recv cases with c is nil. But instead of doing it this way, selectgo could simply omit nil-c cases from the pollorder and lockorder arrays when they're constructed. Then the rest of code will naturally skip over them.

    3. That leaves just send and recv cases. Because the runtime is going to randomize the order that cases will be processed anyway, the compiler can simply arrange that send cases always precede receive cases (or vice versa), and split ncases into nsends and nrecvs. Then selectgo can discern send and recv cases by just comparing the case index (e.g., if sends are ordered before receives, then casi < nsends would indicate cas is a send operation).

      Requiring sends before recvs will be slightly more work for reflect.Select, but it shouldn't be too bad. It already has to translate from []reflect.SelectCase to []runtime.scase (and currently it goes through an extra step and extra heap allocations with runtime.runtimeSelect). It should be easy to construct the latter by placing sends at the front of the slice and receives at the rear (remember: order in []runtime.scase doesn't matter), and just tracking a correspondence of array indices to translate selectgo's return value back.

/cc @aclements

Metadata

Metadata

Assignees

No one assigned

    Labels

    FrozenDueToAgeNeedsFixThe path to resolution is known, but the work has not been done.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions