Two `std::atomic<>` (or QAtomic*) members within a few lines of each other almost certainly share a 64-byte cache line. When two cores write to atomics that share a line, MESI/MOESI invalidations bounce the line across cores -- a 50-200x slowdown vs the uncontended case (false sharin
(class_node, src: bytes, fenced)
| 939 | |
| 940 | |
| 941 | def _adjacent_atomic_findings(class_node, src: bytes, fenced) -> list: |
| 942 | """Two `std::atomic<>` (or QAtomic*) members within a few lines of |
| 943 | each other almost certainly share a 64-byte cache line. When two |
| 944 | cores write to atomics that share a line, MESI/MOESI invalidations |
| 945 | bounce the line across cores -- a 50-200x slowdown vs the uncontended |
| 946 | case (false sharing). The fix is `alignas(64)` (or |
| 947 | `std::hardware_destructive_interference_size`) on each, or explicit |
| 948 | `char _pad[64];` padding. |
| 949 | |
| 950 | Pointer-to-atomic fields are skipped: the pointer itself is set once |
| 951 | at construction, and the actual atomics live behind the indirection |
| 952 | in some other object whose layout we can't reason about from here.""" |
| 953 | findings: list = [] |
| 954 | body = class_node.child_by_field_name("body") |
| 955 | if body is None: |
| 956 | return findings |
| 957 | prev_line = -100 |
| 958 | for child in body.children: |
| 959 | if child.type != "field_declaration": |
| 960 | continue |
| 961 | text = _node_text(child, src) |
| 962 | if not _ATOMIC_DECL_RE.search(text): |
| 963 | prev_line = -100 |
| 964 | continue |
| 965 | # Skip pointer-to-atomic and reference-to-atomic fields: the |
| 966 | # atomic that would suffer false sharing lives elsewhere. |
| 967 | if re.search(r"atomic\w*\s*(?:<[^>]*>)?\s*[*&]", text): |
| 968 | prev_line = -100 |
| 969 | continue |
| 970 | if "alignas" in text: |
| 971 | prev_line = _line_of(child) |
| 972 | continue |
| 973 | line = _line_of(child) |
| 974 | if not fenced(line) and 0 < line - prev_line <= 4: |
| 975 | findings.append( |
| 976 | Finding( |
| 977 | line, |
| 978 | "perf-false-sharing-risk", |
| 979 | "adjacent atomic members will share a cache line " |
| 980 | "(64 B Intel/AArch64, up to 128 B on Apple Silicon " |
| 981 | "M-series via the 128 B speculative line). Cross-core " |
| 982 | "writes thrash MESI/MOESI invalidations (50-200x slowdown " |
| 983 | "vs uncontended). Add `alignas(64)` / " |
| 984 | "`alignas(std::hardware_destructive_interference_size)` " |
| 985 | "or insert `char _pad[64 - sizeof(prev)];` between them.", |
| 986 | ) |
| 987 | ) |
| 988 | prev_line = line |
| 989 | return findings |
| 990 | |
| 991 | |
| 992 | def _virtual_hotpath_findings(src_text: str, path: Path, fenced) -> list: |
no test coverage detected