Space out GraphCycles ranks so that InsertEdge can often move just one node

In debug builds, absl::Mutex deadlock detection records each lock-order edge
with GraphCycles::InsertEdge(held, acquired), under a global lock. GraphCycles
keeps node ranks in topological order: rank(x) < rank(y) for each edge x->y.
When a new edge breaks this order, InsertEdge uses the Pearce-Kelly algorithm
to detect a cycle or repair the ranks.

Before this change, ranks were a dense permutation of [0, N), and each new slot
got the next rank, above all existing nodes. So when a newly created mutex x is
held while an older mutex y is acquired, which is common, the edge x->y often
breaks the order. Pearce-Kelly then visits every descendant of y that ranks
below x (nearly all of them, since x is new), and sorts and renumbers them,
just to put one node in order. With no unused ranks between nodes, even moving
one node means renumbering everything in between.

This change leaves unused ranks between nodes, so that InsertEdge can often fix
the order by moving just x or just y:

- Ranks are now int64_t, and new slots get ranks 2^32 above the highest rank.
  All slots, including free slots, are kept in a doubly linked list sorted by
  rank. As before, a free slot keeps its rank (and now its place in the list),
  which is reused with the slot.
- If rank(x) > rank(y), InsertEdge(x, y) first tries to move x below y, which
  requires all in-neighbors of x to rank below y. x moves to the midpoint
  between y and the slot just below y, or 2^32 below the lowest rank if x has
  no in-neighbors. If x cannot move, InsertEdge tries the symmetric move of y
  above x, which requires all out-neighbors of y to rank above x. If neither
  move works, Pearce-Kelly runs as before.
- Ranks stay unique by construction: each new rank is strictly between the
  ranks of two adjacent slots in the list, or beyond the lowest or highest
  rank, so no other slot can have it. As before, the Pearce-Kelly reorder
  reassigns the ranks of the reordered nodes among them. It now also gives each
  one the list place that goes with its new rank.
- A move never hides a cycle: the condition for moving x also proves that y
  cannot reach x, since every node reachable from y ranks at or above y, but
  every in-neighbor of x ranks below y. The same holds for moving y, so all
  cycle detection still happens in the Pearce-Kelly path.

Why not simply give new nodes decreasing initial ranks (below all existing
nodes)? That makes new -> old edges immediately ordered, but it inverts the
order for old -> new edges, such as when a long-lived container mutex locks a
newly created child mutex, or when a chain of mutexes is acquired in creation
order. Every old -> new edge then triggers a Pearce-Kelly traversal and dense
renumbering, making sequential chain construction (BM_StressTest) quadratic:
O(N) per edge (114 us/node at N=2048 and 2.9 ms/node at N=32768, vs. under 0.5
us/node). Spacing ranks out and moving either endpoint handles both directions
in O(degree) time.

Limits and trade-offs:

- InsertEdge does not try to move x if x has more than 16 in-neighbors to
  check, or y if y has more than 16 out-neighbors. If neither can move, it
  falls back to Pearce-Kelly. This bounds the cost of each attempt.
- The 2^32 spacing leaves room for about 32 consecutive moves into the gap
  between two adjacent new slots. When the two ranks around a gap are less than
  2 apart, Respace() first spreads out a run of slots around the gap, keeping
  their order. Starting with the two slots around the gap, the run doubles in
  size until its slots can be evenly spaced at least 2^16 apart between the
  ranks just outside it (-2^62 and 2^62 beyond the ends of the list).
- Ranks beyond the lowest and highest ones stay within [-2^62, 2^62], which
  leaves room for about 2^30 moves (or new slots) at each end. When that runs
  out, all slots are respaced evenly across [-2^61, 2^61] in O(N) time, which
  leaves room for 2^29 more at each end.
- Slot indices are still int32_t, and the ranks have room for 2^31 slots, so
  there is no new limit on node count.
- Node grows by 16 bytes, for the 64-bit rank and two 32-bit list links (with
  alignment). It was already over 400 bytes, since it holds a 40-entry stack
  trace.

New tests cover each kind of move, the fallbacks to Pearce-Kelly, and both
kinds of respacing. A new test-only method, TestOnlySetRankStride(), lets a
test make the ranks beyond the lowest and highest ones run out after a few
moves, to exercise the respacing of all slots.

PiperOrigin-RevId: 999040867
Change-Id: I3353404e681c094b3cbd4978f384c0415200b2ec
4 files changed
tree: ab1de59d49b6af65c43341483f7696f19545848a
  1. .github/
  2. absl/
  3. ci/
  4. CMake/
  5. .clang-format
  6. .gitignore
  7. ABSEIL_ISSUE_TEMPLATE.md
  8. AUTHORS
  9. BUILD.bazel
  10. CMakeLists.txt
  11. conanfile.py
  12. CONTRIBUTING.md
  13. create_lts.py
  14. FAQ.md
  15. LICENSE
  16. MODULE.bazel
  17. PrivacyInfo.xcprivacy
  18. README.md
  19. UPGRADES.md
README.md

Abseil - C++ Common Libraries

The repository contains the Abseil C++ library code. Abseil is an open-source collection of C++ code (compliant to C++17) designed to augment the C++ standard library.

Table of Contents

About Abseil

Abseil is an open-source collection of C++ library code designed to augment the C++ standard library. The Abseil library code is collected from Google's own C++ code base, has been extensively tested and used in production, and is the same code we depend on in our daily coding lives.

In some cases, Abseil provides pieces missing from the C++ standard; in others, Abseil provides alternatives to the standard for special needs we've found through usage in the Google code base. We denote those cases clearly within the library code we provide you.

Abseil is not meant to be a competitor to the standard library; we've just found that many of these utilities serve a purpose within our code base, and we now want to provide those resources to the C++ community as a whole.

Quickstart

If you want to just get started, make sure you at least run through the Abseil Quickstart. The Quickstart contains information about setting up your development environment, downloading the Abseil code, running tests, and getting a simple binary working.

Building Abseil

Bazel and CMake are the official build systems for Abseil. See the quickstart for more information on building Abseil using the Bazel build system. If you require CMake support, please check the CMake build instructions and CMake Quickstart.

Support

Abseil follows Google's Foundational C++ Support Policy. See this table for a list of currently supported versions compilers, platforms, and build tools.

Codemap

Abseil contains the following C++ library components:

  • base
    The base library contains initialization code and other code which all other Abseil code depends on. Code within base may not depend on any other code (other than the C++ standard library).
  • algorithm
    The algorithm library contains additions to the C++ <algorithm> library and container-based versions of such algorithms.
  • cleanup
    The cleanup library contains the control-flow-construct-like type absl::Cleanup which is used for executing a callback on scope exit.
  • container
    The container library contains additional STL-style containers, including Abseil's unordered “Swiss table” containers.
  • crc The crc library contains code for computing error-detecting cyclic redundancy checks on data.
  • debugging
    The debugging library contains code useful for enabling leak checks, and stacktrace and symbolization utilities.
  • flags
    The flags library contains code for handling command line flags for libraries and binaries built with Abseil.
  • hash
    The hash library contains the hashing framework and default hash functor implementations for hashable types in Abseil.
  • log
    The log library contains LOG and CHECK macros and facilities for writing logged messages out to disk, stderr, or user-extensible destinations.
  • memory
    The memory library contains memory management facilities that augment C++'s <memory> library.
  • meta
    The meta library contains type checks similar to those available in the C++ <type_traits> library.
  • numeric
    The numeric library contains 128-bit integer types as well as implementations of C++20's bitwise math functions.
  • profiling
    The profiling library contains utility code for profiling C++ entities. It is currently a private dependency of other Abseil libraries.
  • random
    The random library contains functions for generating pseudorandom values.
  • status
    The status library contains abstractions for error handling, specifically absl::Status and absl::StatusOr<T>.
  • strings
    The strings library contains a variety of strings routines and utilities.
  • synchronization
    The synchronization library contains concurrency primitives (Abseil's absl::Mutex class, an alternative to std::mutex) and a variety of synchronization abstractions.
  • time
    The time library contains abstractions for computing with absolute points in time, durations of time, and formatting and parsing time within time zones.
  • types
    The types library contains non-container utility types.
  • utility
    The utility library contains utility and helper code.

Releases

Abseil recommends users “live-at-head” (update to the latest commit from the master branch as often as possible). However, we realize this philosophy doesn't work for every project, so we also provide Long Term Support Releases to which we backport fixes for severe bugs. See our release management document for more details.

License

The Abseil C++ library is licensed under the terms of the Apache license. See LICENSE for more information.

Links

For more information about Abseil: