Pack Task.gostate{,Seq,Time} into a single 64-bit atomic int. - #15005
Open
EtiennePerot wants to merge 1 commit into
Open
EtiennePerot wants to merge 1 commit into
EtiennePerot wants to merge 1 commit into
Conversation
`Task` used to have two fields (`gostate` and `gostateTime`) which are
updated very frequently in lockstep (since the latter is the modification
time of the former). To do so lock-less-ly, it used a `sync.SeqCount` to
try to update them atomically.
However, in practice, `gostateTime` (number of nanoseconds since boot) never
reaches 64 bits, because sandboxes don't run for multiple centuries. So we
can pack the `gostate` enum bits together with the timestamp bits into a
single 64-bit field which can be written with native atomic operations,
which is already much faster.
The only downside is that now, sandboxes can only run for up to
`2^61 - 1` nanoseconds, aka around 73 years, whereas before they could run
for `2^63 - 1` nanoseconds, aka around 292 years. So we've lost the ability
to run for a century.
But wait, there's more. All the readers of this field tolerate stale reads.
This means we can actually not use an atomic write at all, so long as we
can guarantee that all 64 bits either get written or don't (i.e. no partial
writes). This is the case for 64-bit architectures, but not 32-bit.
So this introduces a new `StoreRelaxed` method on `atomicbitops.Uint64`,
which is just a plain `a = b` for 64-bit architectures, but otherwise a
regular atomic store for 32-bit architectures where this second optimization
doesn't apply.
Benchmarks:
```
│ before │ after │
│ sec/op │ sec/op vs base │
laptop/platform=kvm/bench=getpid 410.0n ± 1% 380.0n ± 1% -7.32% (p=0.002 n=6)
laptop/platform=kvm/bench=getpidopt 407.5n ± 2% 376.5n ± 1% -7.61% (p=0.002 n=6)
laptop/platform=systrap/bench=getpid 2.200µ ± 0% 2.177µ ± 1% -1.05% (p=0.001 n=15)
laptop/platform=systrap/bench=getpidopt 709.8n ± 2% 682.6n ± 1% -3.83% (p=0.000 n=14+15)
n2-standard-16/platform=kvm/bench=getpid 579.4n ± 1% 537.7n ± 1% -7.19% (p=0.000 n=32)
n2-standard-16/platform=kvm/bench=getpidopt 579.3n ± 0% 533.4n ± 0% -7.92% (p=0.000 n=32)
n2-standard-16/platform=systrap/bench=getpid 3.669µ ± 1% 3.632µ ± 1% -1.01% (p=0.000 n=32+33)
n2-standard-16/platform=systrap/bench=getpidopt 1.211µ ± 1% 1.182µ ± 1% -2.39% (p=0.000 n=32+33)
```
Collaborator
|
Once again, very cool! Do you know why this improves KVM more than systrap? |
Collaborator
Author
@ayushr2 It is a flat improvement for both, around 30~40ns. Since KVM syscall roundtrip time is smaller, that flat improvement translates to a larger proportion of its roundtrip time, so on a percentage basis it looks bigger. But it's the same in absolute terms. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Taskused to have two fields (gostateandgostateTime) which are updated very frequently in lockstep (since the latter is the modification time of the former). To do so lock-less-ly, it used async.SeqCountto try to update them atomically.However, in practice,
gostateTime(number of nanoseconds since boot) never really need the full 64 bits, because sandboxes don't run for multiple centuries. So we can pack thegostateenum bits together with the timestamp bits into a single 64-bit field which can be written with native atomic operations, which is already much faster.The only downside is that now, sandboxes can only run for up to
2^61 - 1nanoseconds, aka around 73 years, whereas before they could run for2^63 - 1nanoseconds, aka around 292 years. So we've lost the ability to run for a century.But wait, there's more. All the readers of this field tolerate stale reads. This means we can actually not use an atomic write at all, so long as we can guarantee that all 64 bits either get written or don't (i.e. no partial writes). This is the case for 64-bit architectures, but not 32-bit. So this introduces a new
StoreRelaxedmethod onatomicbitops.Uint64, which is just a plaina = bfor 64-bit architectures, but otherwise a regular 64-bit atomic store for 32-bit architectures where this second optimization doesn't apply.Benchmarks: