Skip to content

Commit fa18a2b

Browse files
authored
chore(crashtracker): use weaker mem ordering for OP_COUNTERS (#1744)
# What does this PR do? This PR replaces a bunch of sequentially consistent atomic accesses on ops counters by weaker relaxed accesses, cleaning a leftover TODO. # Motivation The motivation to use the weakest memory ordering applicable is two folds: 1. Performance: relaxed accesses compile to normal, non-atomic loads and stores on standard platforms (x86_64 and arm64 in particular). Whether this particular change has any performance impact is less obvious. 2. Readability: I think my main motivation is that I find it _easier_, at least as a reader, to reason about weaker orderings. For example, a relaxed access indicates that there's no other unsynchronized data that this atomic protect or interact with, which enables local reasoning (you don't have to care about what other threads might be doing). A sequentially consistent access is the converse: they lead to a global order which involves all other seqcst accesses to this atomic, which is a strong and far-reaching assumption. # Additional Notes This atomic is a counter, which is the poster child for `Relaxed` ordering (you usually only need the atomicity). This counter doesn't protect or interact with unsynchronized memory, so there's no reason to use a stronger ordering. # How to test the change? Should see no difference in behavior except maybe for performance. Co-authored-by: yann.hamdaoui <yann.hamdaoui@datadoghq.com>
1 parent 4dd532f commit fa18a2b

1 file changed

Lines changed: 11 additions & 9 deletions

File tree

libdd-crashtracker/src/collector/counters.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright 2023-Present Datadog, Inc. https://www.datadoghq.com/
22
// SPDX-License-Identifier: Apache-2.0
33

4-
use std::sync::atomic::{AtomicI64, Ordering::SeqCst};
4+
use std::sync::atomic::{AtomicI64, Ordering};
55
use thiserror::Error;
66

77
#[cfg(unix)]
@@ -48,7 +48,6 @@ impl OpTypes {
4848
#[allow(clippy::declare_interior_mutable_const)]
4949
const ATOMIC_ZERO: AtomicI64 = AtomicI64::new(0);
5050

51-
// TODO: Is this
5251
static OP_COUNTERS: [AtomicI64; OpTypes::SIZE as usize] = [ATOMIC_ZERO; OpTypes::SIZE as usize];
5352

5453
/// Track that an operation (of type op) has begun.
@@ -58,9 +57,7 @@ static OP_COUNTERS: [AtomicI64; OpTypes::SIZE as usize] = [ATOMIC_ZERO; OpTypes:
5857
/// ATOMICITY:
5958
/// This function is atomic.
6059
pub fn begin_op(op: OpTypes) -> Result<(), CounterError> {
61-
// TODO: I'm making everything SeqCst for now. Could possibly gain some
62-
// performance by using a weaker ordering.
63-
let old = OP_COUNTERS[op as usize].fetch_add(1, SeqCst);
60+
let old = OP_COUNTERS[op as usize].fetch_add(1, Ordering::Relaxed);
6461
if old == i64::MAX - 1 {
6562
return Err(CounterError::CounterOverflow(op));
6663
}
@@ -72,7 +69,7 @@ pub fn begin_op(op: OpTypes) -> Result<(), CounterError> {
7269
/// PRECONDITIONS: This function assumes that the crash-tracker is initialized.
7370
/// ATOMICITY: This function is atomic.
7471
pub fn end_op(op: OpTypes) -> Result<(), CounterError> {
75-
let old = OP_COUNTERS[op as usize].fetch_sub(1, SeqCst);
72+
let old = OP_COUNTERS[op as usize].fetch_sub(1, Ordering::Relaxed);
7673
if old <= 0 {
7774
return Err(CounterError::OperationNotStarted(op));
7875
}
@@ -103,7 +100,12 @@ pub fn emit_counters(w: &mut impl Write) -> Result<(), CounterError> {
103100

104101
writeln!(w, "{DD_CRASHTRACK_BEGIN_COUNTERS}")?;
105102
for (i, c) in OP_COUNTERS.iter().enumerate() {
106-
writeln!(w, "{{\"{}\": {}}}", OpTypes::name(i)?, c.load(SeqCst))?;
103+
writeln!(
104+
w,
105+
"{{\"{}\": {}}}",
106+
OpTypes::name(i)?,
107+
c.load(Ordering::Relaxed)
108+
)?;
107109
}
108110
writeln!(w, "{DD_CRASHTRACK_END_COUNTERS}")?;
109111
w.flush()?;
@@ -113,12 +115,12 @@ pub fn emit_counters(w: &mut impl Write) -> Result<(), CounterError> {
113115
/// Resets all counters to 0.
114116
/// Expected to be used after a fork, to reset the counters on the child
115117
/// ATOMICITY:
116-
/// This is NOT ATOMIC.
118+
/// The reset of each individual counter is atomic, but the entire reset is NOT.
117119
/// Should only be used when no conflicting updates can occur,
118120
/// e.g. after a fork but before ops start on the child.
119121
pub fn reset_counters() -> Result<(), CounterError> {
120122
for c in OP_COUNTERS.iter() {
121-
c.store(0, SeqCst);
123+
c.store(0, Ordering::Relaxed);
122124
}
123125
Ok(())
124126
}

0 commit comments

Comments
 (0)