The idea
systrap decides whether the Sentry and stub threads should spin waiting for each other (the "fast path") in a state machine that runs on a dedicated goroutine, waking every 400 µs while the sandbox is active and parking only when it goes idle. Under a CPU quota those timer wakeups cost host context switches and CPU in every sandbox, and on a loaded host the timer oversleeps (1.6-1.9 ms between evaluations was measured), so its decisions lag behind the workload.
systrap could instead evaluate the state machine inline from contextQueue.add, at most once per recording period: it's just an atomic timestamp check in the common case, a TryLock keeps concurrent submitters from evaluating twice. If two whole periods passed with no submission, it resets the fast path to off (as parking does today). Without that reset, a bad "on" state has no way out, and hackbench pipe/thread gets 1.8x slower. This is complementary to not spinning for contexts whose app runs outlast the spin (the bigger cost of the fast path today): that removes the spinning, this removes the timer.
Performance impact
- CPU-bound sandboxes under a quota: Sentry/host overhead per unit of app CPU -10..-23% on 2-4 vCPU hosts, and -10..-13% at 0.5-2 CPU on 16 vCPUs, -4% at 0.25 CPU on 16-48 vCPUs, neutral at 1 CPU on 48. Host context switches -8..-14% on 16-48 vCPU hosts. Dense
getppid()loops unchanged. - The bad: Loops that sleep 1 ms between short syscall bursts lose 2-5% throughput and get +22..78% host context switches, because the precise idle detection now resets the fast path after each sleep.
//test/benchmarks: fio libaio 4K read on rootfs -3.4% bandwidth (other libaio rows lean the same way, -1.5..-3.3%, p>0.01), node QPS -2.0% at concurrency 1 and -1.4% at concurrency 5.- Also in
//test/benchmarks: sysbench Mutex -7.5%,hackbenchpipe/thread -2.4%, ffmpeg -0.9%, redis max latency -8..-15%; syscallbench, startup, nginx, httpd, ruby, redis rps and iperf are neutral.
Beat the autoresearch
The following benchmark results were benchmarked using this patch: https://github.com/google/gvisor/compare/master...EtiennePerot:gvisor:autoresearch/inline-fastpath-monitor
Add value by beating these benchmark numbers, or reducing any regressions they cause, or both
goos: linux
goarch: amd64
pkg: gvisor.dev/gvisor/test/benchmarks/base
│ before │ after │
│ sec/op │ sec/op vs base │
StartupEmpty-16 182.1m ± 2% 180.8m ± 2% ~ (p=0.210 n=16)
StartupNginx-16 499.7m ± 1% 501.5m ± 3% ~ (p=0.669 n=16)
SizeEmpty-16 542.3m ± 2% 542.3m ± 2% ~ (p=0.838 n=16)
Sysbench/testname.CPU-16 520.2µ ± 1% 516.8µ ± 2% ~ (p=0.445 n=16)
Sysbench/testname.CPU/threads.16-16 65.85µ ± 1% 66.48µ ± 2% ~ (p=0.138 n=16)
Sysbench/testname.Memory-16 1.355 ± 0% 1.349 ± 0% ~ (p=0.128 n=16)
Sysbench/testname.Memory/threads.16-16 1.284 ± 1% 1.287 ± 1% ~ (p=0.210 n=16)
Sysbench/testname.Mutex-16 5.340µ ± 4% 4.939µ ± 3% -7.51% (p=0.001 n=16)
Syscallbench/syscall.getpid-16 3.464µ ± 1% 3.473µ ± 2% ~ (p=0.439 n=16)
Syscallbench/syscall.getpidopt-16 1.175µ ± 3% 1.188µ ± 2% ~ (p=0.224 n=16)
Syscallbench/syscall.getpid/run=2-16 2.959µ ± 2% 2.950µ ± 2% ~ (p=1.000 n=16)
Syscallbench/syscall.getpidopt/run=2-16 1.028µ ± 3% 1.044µ ± 4% ~ (p=0.759 n=16)
Hackbench/ipcMode.pipe/processMode.thread-16 5.468m ± 1% 5.336m ± 2% -2.41% (p=0.001 n=16)
Hackbench/ipcMode.socket/processMode.process-16 12.77m ± 7% 13.42m ± 10% ~ (p=0.270 n=16)
Hackbench/ipcMode.pipe/processMode.thread/run=2-16 4.770m ± 2% 4.673m ± 3% -2.04% (p=0.029 n=16)
Hackbench/ipcMode.socket/processMode.process/run=2-16 9.868m ± 5% 9.798m ± 11% ~ (p=0.381 n=16)
geomean 1.497m 1.492m -0.34%
pkg: gvisor.dev/gvisor/test/benchmarks/database
│ before │ after │
│ max_latency_ms │ max_latency_ms vs base │
Redis/operation.LPUSH-16 3.355 ± 14% 2.867 ± 11% -14.55% (p=0.001 n=16)
Redis/operation.LRANGE_100-16 2.967 ± 19% 2.883 ± 12% ~ (p=0.649 n=16)
Redis/operation.SET-16 3.063 ± 15% 2.823 ± 8% -7.84% (p=0.008 n=16)
geomean 3.124 2.858 -8.53%
│ before │ after │
│ rps │ rps vs base │
Redis/operation.LPUSH-16 58.79k ± 2% 58.62k ± 1% ~ (p=0.696 n=16)
Redis/operation.LRANGE_100-16 46.70k ± 1% 46.63k ± 1% ~ (p=0.583 n=16)
Redis/operation.SET-16 58.92k ± 1% 58.71k ± 1% ~ (p=0.361 n=16)
geomean 54.49k 54.34k -0.27%
pkg: gvisor.dev/gvisor/test/benchmarks/fs
│ before │ after │
│ bandwidth.bytes_per_second │ bandwidth.bytes_per_second vs base │
FioRead/operation.read/ioEngine.libaio/jobs.1/blockSize.4K/directIO.false/filesystem.rootfs-16 353.5M ± 1% 341.6M ± 1% -3.39% (p=0.000 n=16)
FioRead/operation.read/ioEngine.libaio/jobs.1/blockSize.4K/directIO.false/filesystem.tmpfs-16 369.9M ± 2% 364.5M ± 1% -1.45% (p=0.032 n=16)
FioRead/operation.read/ioEngine.libaio/jobs.8/blockSize.4K/directIO.true/filesystem.rootfs-16 76.46M ± 2% 75.39M ± 2% ~ (p=0.184 n=16)
FioRead/operation.read/ioEngine.libaio/jobs.8/blockSize.4K/directIO.true/filesystem.tmpfs-16 78.64M ± 3% 76.04M ± 3% -3.31% (p=0.035 n=16)
FioWrite/operation.write/ioEngine.libaio/jobs.8/blockSize.4K/directIO.true/filesystem.rootfs-16 77.44M ± 4% 74.89M ± 6% ~ (p=0.100 n=16)
FioWrite/operation.write/ioEngine.libaio/jobs.8/blockSize.4K/directIO.true/filesystem.tmpfs-16 78.55M ± 5% 76.22M ± 2% ~ (p=0.224 n=16)
FioWrite/operation.write/ioEngine.sync/jobs.1/blockSize.4K/directIO.false/filesystem.rootfs-16 999.6M ± 1% 1000.3M ± 1% ~ (p=0.956 n=16)
FioWrite/operation.write/ioEngine.sync/jobs.1/blockSize.4K/directIO.false/filesystem.tmpfs-16 1.628G ± 1% 1.611G ± 2% ~ (p=0.287 n=16)
geomean 229.8M 225.0M -2.10%
pkg: gvisor.dev/gvisor/test/benchmarks/media
│ before │ after │
│ sec/op │ sec/op vs base │
Ffmpeg-16 28.82 ± 0% 28.57 ± 0% -0.87% (p=0.002 n=8)
pkg: gvisor.dev/gvisor/test/benchmarks/network
│ before │ after │
│ bandwidth.bytes_per_second │ bandwidth.bytes_per_second vs base │
IperfOneConnection/operation.Download/bytes=10GB-16 4.377G ± 2% 4.407G ± 2% ~ (p=0.590 n=16)
IperfOneConnection/operation.Upload/bytes=10GB-16 3.931G ± 2% 3.894G ± 2% ~ (p=0.539 n=16)
geomean 4.148G 4.143G -0.14%
│ before │ after │
│ requests_per_second.QPS │ requests_per_second.QPS vs base │
NginxDocSize/filesize.10Kb/concurrency.1/filesystem.bind-16 7.068k ± 1% 7.100k ± 1% ~ (p=0.171 n=16)
NginxDocSize/filesize.10Kb/concurrency.1/filesystem.tmpfs-16 7.620k ± 0% 7.649k ± 1% ~ (p=0.116 n=16)
NginxDocSize/filesize.10Kb/concurrency.100/filesystem.bind-16 21.55k ± 1% 21.28k ± 2% ~ (p=0.539 n=16)
NginxDocSize/filesize.10Kb/concurrency.100/filesystem.tmpfs-16 26.86k ± 1% 26.53k ± 2% ~ (p=0.539 n=16)
NginxDocSize/filesize.1Mb/concurrency.1/filesystem.bind-16 975.4 ± 1% 978.2 ± 1% ~ (p=0.724 n=16)
NginxDocSize/filesize.1Mb/concurrency.1/filesystem.tmpfs-16 1.087k ± 2% 1.077k ± 1% ~ (p=0.125 n=16)
NginxDocSize/filesize.1Mb/concurrency.100/filesystem.bind-16 1.091k ± 2% 1.092k ± 2% ~ (p=0.291 n=16)
NginxDocSize/filesize.1Mb/concurrency.100/filesystem.tmpfs-16 1.246k ± 1% 1.241k ± 2% ~ (p=0.235 n=16)
Httpd/filesize.10Kb/concurrency.1-16 2.733k ± 2% 2.779k ± 2% +1.68% (p=0.016 n=16)
Httpd/filesize.10Kb/concurrency.100-16 19.89 ± 0% 19.87 ± 0% ~ (p=0.115 n=16)
Httpd/filesize.1Mb/concurrency.1-16 201.3 ± 1% 202.1 ± 1% ~ (p=0.717 n=16)
Httpd/filesize.1Mb/concurrency.100-16 19.77 ± 0% 19.76 ± 0% ~ (p=0.687 n=16)
Node/concurrency.1-16 910.1 ± 2% 891.8 ± 2% -2.01% (p=0.000 n=16)
Node/concurrency.10-16 1.295k ± 2% 1.302k ± 3% ~ (p=0.860 n=16)
Node/concurrency.25-16 1.488k ± 3% 1.435k ± 4% -3.53% (p=0.024 n=16)
Node/concurrency.5-16 1.265k ± 2% 1.248k ± 2% -1.38% (p=0.003 n=16)
Ruby/concurrency.1-16 1.085k ± 2% 1.082k ± 5% ~ (p=0.846 n=16)
Ruby/concurrency.10-16 3.693k ± 1% 3.669k ± 0% ~ (p=0.206 n=16)
Ruby/concurrency.25-16 3.982k ± 1% 3.970k ± 1% ~ (p=0.521 n=16)
Ruby/concurrency.5-16 3.124k ± 1% 3.128k ± 0% ~ (p=0.948 n=16)
geomean 1.418k 1.412k -0.41%
pkg: many-sandbox steady-state harness (perf over 15 s window)
│ before │ after │
│ overhead/app-cpu │ overhead/app-cpu vs base │
Burner/vcpus=2/sandboxes=5/cpu=0.25 121.20m ± 6% 93.16m ± 10% -23.13% (p=0.002 n=6)
Burner/vcpus=2/sandboxes=5/cpu=1 107.52m ± 7% 97.05m ± 18% -9.73% (p=0.041 n=6)
Tick1ms/vcpus=2/sandboxes=1/cpu=0.25 8.403 ± 53% 6.594 ± 57% ~ (p=0.394 n=6)
Tick1ms/vcpus=2/sandboxes=1/cpu=1 9.159 ± 56% 5.728 ± 45% ~ (p=0.132 n=6)
Tick10ms/vcpus=2/sandboxes=7/cpu=1 6.596 ± 3% 6.359 ± 2% -3.60% (p=0.015 n=6)
Burner/vcpus=4/sandboxes=10/cpu=0.25 130.9m ± 4% 107.2m ± 6% -18.13% (p=0.002 n=6)
Burner/vcpus=4/sandboxes=10/cpu=1 123.00m ± 7% 99.59m ± 9% -19.04% (p=0.002 n=6)
Tick1ms/vcpus=4/sandboxes=2/cpu=0.25 6.793 ± 19% 6.907 ± 19% ~ (p=0.699 n=6)
Tick1ms/vcpus=4/sandboxes=2/cpu=1 6.496 ± 24% 6.933 ± 16% ~ (p=0.485 n=6)
Tick10ms/vcpus=4/sandboxes=14/cpu=1 6.501 ± 2% 6.461 ± 2% ~ (p=0.394 n=6)
Burner/vcpus=16/sandboxes=28/cpu=0.5 150.0m ± 8% 129.9m ± 15% -13.40% (p=0.026 n=6)
Burner/vcpus=16/sandboxes=40/cpu=0.25 168.5m ± 2% 162.5m ± 2% -3.57% (p=0.000 n=12)
Burner/vcpus=16/sandboxes=40/cpu=1 133.1m ± 9% 120.3m ± 3% -9.65% (p=0.000 n=12)
Burner/vcpus=16/sandboxes=40/cpu=2 135.7m ± 22% 118.4m ± 4% -12.72% (p=0.002 n=6)
Tick1ms/vcpus=16/sandboxes=8/cpu=0.25 6.310 ± 1% 6.352 ± 5% ~ (p=0.699 n=6)
Tick1ms/vcpus=16/sandboxes=8/cpu=1 6.550 ± 3% 6.604 ± 2% ~ (p=0.378 n=12)
Tick10ms/vcpus=16/sandboxes=56/cpu=1 5.735 ± 1% 5.558 ± 2% -3.10% (p=0.002 n=6)
Sparse50ms/vcpus=16/sandboxes=56/cpu=1 7.177 ± 2% 7.150 ± 4% ~ (p=0.485 n=6)
Burner/vcpus=48/sandboxes=120/cpu=0.25 176.7m ± 1% 169.8m ± 2% -3.90% (p=0.002 n=6)
Burner/vcpus=48/sandboxes=120/cpu=1 151.6m ± 6% 154.8m ± 4% ~ (p=0.485 n=6)
Sparse50ms/vcpus=48/sandboxes=120/cpu=1 7.006 ± 1% 7.193 ± 1% +2.67% (p=0.004 n=6)
Tick1ms/vcpus=48/sandboxes=24/cpu=1 6.682 ± 1% 6.784 ± 1% +1.52% (p=0.009 n=6)
geomean 1.167 1.072 -8.13%
│ before │ after │
│ host-ctxsw/s │ host-ctxsw/s vs base │
Burner/vcpus=2/sandboxes=5/cpu=0.25 12.86k ± 6% 12.71k ± 2% ~ (p=0.937 n=6)
Burner/vcpus=2/sandboxes=5/cpu=1 13.66k ± 3% 12.06k ± 17% -11.70% (p=0.041 n=6)
Getppid/vcpus=2/sandboxes=1/cpu=1 7.017k ± 23% 9.897k ± 23% +41.04% (p=0.009 n=6)
Tick1ms/vcpus=2/sandboxes=1/cpu=0.25 4.567k ± 8% 7.213k ± 3% +57.91% (p=0.002 n=6)
Tick1ms/vcpus=2/sandboxes=1/cpu=1 11.97k ± 6% 21.36k ± 7% +78.39% (p=0.002 n=6)
Tick10ms/vcpus=2/sandboxes=7/cpu=1 410.7k ± 2% 421.4k ± 2% +2.61% (p=0.009 n=6)
Burner/vcpus=4/sandboxes=10/cpu=0.25 25.73k ± 3% 25.45k ± 2% ~ (p=0.394 n=6)
Burner/vcpus=4/sandboxes=10/cpu=1 25.78k ± 5% 25.51k ± 7% ~ (p=0.589 n=6)
Getppid/vcpus=4/sandboxes=1/cpu=1 7.580k ± 11% 4.501k ± 8% -40.62% (p=0.002 n=6)
Tick1ms/vcpus=4/sandboxes=2/cpu=0.25 9.421k ± 12% 13.905k ± 4% +47.60% (p=0.002 n=6)
Tick1ms/vcpus=4/sandboxes=2/cpu=1 19.25k ± 12% 30.77k ± 12% +59.83% (p=0.002 n=6)
Tick10ms/vcpus=4/sandboxes=14/cpu=1 682.4k ± 1% 687.7k ± 2% ~ (p=0.310 n=6)
Burner/vcpus=16/sandboxes=28/cpu=0.5 98.97k ± 7% 84.86k ± 8% -14.26% (p=0.002 n=6)
Burner/vcpus=16/sandboxes=40/cpu=0.25 132.0k ± 1% 121.4k ± 2% -8.04% (p=0.000 n=12)
Burner/vcpus=16/sandboxes=40/cpu=1 122.0k ± 2% 109.1k ± 4% -10.51% (p=0.000 n=12)
Burner/vcpus=16/sandboxes=40/cpu=2 126.8k ± 11% 112.3k ± 6% -11.42% (p=0.015 n=6)
Tick1ms/vcpus=16/sandboxes=8/cpu=0.25 41.76k ± 12% 50.92k ± 5% +21.94% (p=0.002 n=6)
Tick1ms/vcpus=16/sandboxes=8/cpu=1 98.87k ± 3% 136.19k ± 1% +37.74% (p=0.000 n=12)
Tick10ms/vcpus=16/sandboxes=56/cpu=1 3.973M ± 0% 4.127M ± 0% +3.86% (p=0.002 n=6)
Sparse50ms/vcpus=16/sandboxes=56/cpu=1 272.4k ± 1% 307.1k ± 3% +12.73% (p=0.002 n=6)
Getppid/vcpus=16/sandboxes=1/cpu=1 6.100k ± 15% 3.600k ± 8% -40.98% (p=0.002 n=6)
Burner/vcpus=48/sandboxes=120/cpu=0.25 400.5k ± 0% 359.1k ± 0% -10.34% (p=0.002 n=6)
Burner/vcpus=48/sandboxes=120/cpu=1 377.2k ± 1% 335.4k ± 3% -11.08% (p=0.002 n=6)
Sparse50ms/vcpus=48/sandboxes=120/cpu=1 636.1k ± 0% 512.2k ± 1% -19.48% (p=0.002 n=6)
Tick1ms/vcpus=48/sandboxes=24/cpu=1 312.1k ± 1% 401.6k ± 2% +28.69% (p=0.002 n=6)
geomean 65.79k 68.69k +4.42%
│ before │ after │
│ work/s │ work/s vs base │
Getppid/vcpus=2/sandboxes=1/cpu=1 152.8k ± 1% 151.5k ± 1% ~ (p=0.065 n=6)
Tick1ms/vcpus=2/sandboxes=1/cpu=0.25 24.67k ± 2% 23.97k ± 1% -2.83% (p=0.002 n=6)
Tick1ms/vcpus=2/sandboxes=1/cpu=1 109.0k ± 3% 107.6k ± 2% ~ (p=0.485 n=6)
Tick10ms/vcpus=2/sandboxes=7/cpu=1 196.1k ± 2% 204.9k ± 2% +4.48% (p=0.002 n=6)
Getppid/vcpus=4/sandboxes=1/cpu=1 138.0k ± 2% 138.5k ± 2% ~ (p=0.699 n=6)
Tick1ms/vcpus=4/sandboxes=2/cpu=0.25 41.91k ± 6% 39.60k ± 7% -5.51% (p=0.026 n=6)
Tick1ms/vcpus=4/sandboxes=2/cpu=1 168.9k ± 5% 168.1k ± 3% ~ (p=0.394 n=6)
Tick10ms/vcpus=4/sandboxes=14/cpu=1 322.7k ± 1% 335.1k ± 1% +3.82% (p=0.002 n=6)
Tick1ms/vcpus=16/sandboxes=8/cpu=0.25 230.5k ± 3% 219.7k ± 1% -4.70% (p=0.002 n=6)
Tick1ms/vcpus=16/sandboxes=8/cpu=1 908.2k ± 2% 886.7k ± 2% -2.37% (p=0.028 n=12)
Tick10ms/vcpus=16/sandboxes=56/cpu=1 1.912M ± 0% 2.025M ± 0% +5.93% (p=0.002 n=6)
Sparse50ms/vcpus=16/sandboxes=56/cpu=1 1.096M ± 1% 1.226M ± 1% +11.85% (p=0.002 n=6)
Getppid/vcpus=16/sandboxes=1/cpu=1 187.6k ± 4% 183.0k ± 7% ~ (p=0.589 n=6)
Sparse50ms/vcpus=48/sandboxes=120/cpu=1 2.422M ± 0% 3.412M ± 1% +40.87% (p=0.002 n=6)
Tick1ms/vcpus=48/sandboxes=24/cpu=1 2.598M ± 0% 2.523M ± 0% -2.87% (p=0.002 n=6)
geomean 298.3k 305.5k +2.43%Benchmark environment
GCE c4-standard-2/4/16/48 (vcpus=), COS cos-129-19506-448-8 (kernel 6.12.105+), hyperdisk-balanced. before is master at 2511bc60218a, after is the patch above (some runs used a build whose runsc is identical and only the test file differs). The //test/benchmarks blocks are the real targets run on c4-standard-16 as master and patched docker runtimes (systrap, no CPU limit), interleaved; nginx/httpd and fio ran a subset of configurations, iperf was run with a 10 GB transfer, and run=2 rows are a second independent run. The steady-state harness starts N runsc run --cpu-num-from-quota sandboxes with the given cpu.max quota, runs them 45 s, and measures the last 15 s with perf record -a; overhead/app-cpu is non-app CPU divided by the workload binary's CPU, host-ctxsw/s is host context switches. Workloads: Burner burns CPU 50 ms out of every 200 ms (its own work counter is clock-budgeted and confounded by thread CPU accounting, so only overhead is shown), Tick1ms does about 20 µs of work every 1 ms, Tick10ms every 10 ms, Sparse50ms about 100 µs every 50 ms, Getppid is a dense getppid() loop. The vcpus=16 rows with n=12 pool two sessions on different VMs.
Activity
0No activity yet.