Futex contention
Futex contention occurs when multiple threads compete for access to shared resources protected by futexes (fast user-space mutexes). Futexes are efficient synchronization mechanisms in the Linux kernel, but contention can lead to performance bottlenecks in multithreaded programs.
Analyzing futex contention helps identify which locks are most contended and take the longest to resolve.
Setup
The first step is to build a profiling-tuned build of neard
:
cargo build --release --config .cargo/config.profiling.toml -p neard
The second step involves compiling a tool designed to identify futex contention: futexctn. The following snippet demonstrates how to set up a neard
node on GCP to collect lock contention data:
sudo sysctl kernel.perf_event_paranoid=0 && sudo sysctl kernel.kptr_restrict=0
sudo apt install -y clang llvm git libelf-dev
git clone https://github.com/iovisor/bcc.git
cd bcc/libbpf-tools/
git submodule update --init --recursive
make -j8
Finally, execute the futexctn
binary for a chosen duration to gather contention statistics:
sudo ./futexctn -p $(pgrep neard) -T
Understanding futexctn's output
Below is an example section of the tool's output, illustrating the contention of a lock:
neard0[55783] lock 0x79a1b1dfcfb0 contended 41 times, 22 avg msecs [max: 48 msecs, min 5 msecs]
-
syscall
_ZN10near_store4trie4Trie17get_optimized_ref17h8d5fd8c67e262ab1E
_ZN10near_store4trie4Trie3get17h11d4bcd3667e3c8fE
_ZN85_$LT$near_store..trie..update..TrieUpdate$u20$as$u20$near_store..trie..TrieAccess$GT$3get17h041c58f313ed6d6cE
_ZN10near_store5utils11get_account17hf3071e79288206c0E
_ZN12node_runtime8verifier25get_signer_and_access_key17hca85f102c5a0241bE
_ZN92_$LT$near_chain..runtime..NightshadeRuntime$u20$as$u20$near_chain..types..RuntimeAdapter$GT$24can_verify_and_charge_tx17hb1907151d54f3c6bE
_ZN11near_client11rpc_handler10RpcHandler10process_tx17h3ca7fec97c6dd01eE
_ZN110_$LT$actix..sync..SyncContextEnvelope$LT$M$GT$$u20$as$u20$actix..address..envelope..EnvelopeProxy$LT$A$GT$$GT$6handle17h0f78e99e6ce63395E
_ZN3std3sys9backtrace28__rust_begin_short_backtrace17hd5ed81d58d44f867E
_ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17hc18f360a04c1b975E
_ZN3std3sys3pal4unix6thread6Thread3new12thread_start17hcc5ed016d554f327E
[unknown]
-
msecs : count distribution
0 -> 1 : 0 | |
2 -> 3 : 0 | |
4 -> 7 : 10 |*********************** |
8 -> 15 : 10 |*********************** |
16 -> 31 : 4 |********* |
32 -> 63 : 17 |****************************************|
Notes on the output:
- The first line shows the number of times the lock was contended and the average duration of contention.
- The middle section provides the stack trace of the thread that locked the futex.
- The bottom section contains a histogram representing the contention duration distribution.
Things to watch out for
Some instances of contention are not caused by locks being held for extended periods but are instead due to programmatic waits or sleeps. These can be identified either by examining the stack trace or by noticing that their durations are consistently rounded numbers.
Example:
tokio-runtime-w[56073] lock 0x79a1ae9f9fb0 contended 9 times, 1000 avg msecs [max: 1003 msecs, min 1000 msecs]