cpupin_linux.go 1.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
  1. //go:build linux && !android && !e2e_testing
  2. package util
  3. import (
  4. "runtime"
  5. "golang.org/x/sys/unix"
  6. )
  7. // PinThreadToCPU restricts the calling OS thread to the given CPU via
  8. // sched_setaffinity(2). Combined with runtime.LockOSThread on the
  9. // goroutine, this prevents the kernel from migrating us across CPUs and
  10. // in turn keeps every sendmmsg from this goroutine going through the
  11. // same XPS-selected TX ring, eliminating the wire-side reorder that
  12. // otherwise fragments one nebula flow across multiple rings.
  13. func PinThreadToCPU(cpu int) error {
  14. runtime.LockOSThread()
  15. var set unix.CPUSet
  16. set.Zero()
  17. set.Set(cpu)
  18. if err := unix.SchedSetaffinity(0, &set); err != nil {
  19. // Without the affinity the thread lock buys no TX-ring stability;
  20. // don't leave the goroutine wedded to one OS thread for nothing.
  21. runtime.UnlockOSThread()
  22. return err
  23. }
  24. return nil
  25. }
  26. // AllowedCPUs returns the CPU IDs the calling process is currently allowed to
  27. // run on, as reported by sched_getaffinity(2). Under a cgroup cpuset or a
  28. // `taskset` mask the allowed IDs are frequently not the contiguous range
  29. // 0..NumCPU-1 (e.g. pinned to CPUs 4-7: NumCPU reports 4 while the valid IDs
  30. // are 4,5,6,7). Callers that need a real CPU to pin to must choose from this
  31. // set rather than assuming i % NumCPU is runnable, or every pin fails.
  32. func AllowedCPUs() ([]int, error) {
  33. var set unix.CPUSet
  34. if err := unix.SchedGetaffinity(0, &set); err != nil {
  35. return nil, err
  36. }
  37. cpus := make([]int, 0, set.Count())
  38. for cpu := range len(set) * 64 {
  39. if set.IsSet(cpu) {
  40. cpus = append(cpus, cpu)
  41. }
  42. }
  43. return cpus, nil
  44. }