False sharing of guarded member variables?
Asked Answered
F

1

6

Consider:

class Vector
{
  double x, y, z;
  // …
};

class Object
{
  Vector Vec1, Vec2;
  std::mutex Mtx1, Mtx2;

  void ModifyVec1() { std::lock_guard Lock(Mtx1); /* … */ }
  void ModifyVec2() { std::lock_guard Lock(Mtx2); /* … */ }
};

If either the mutexes or the guarded variables are stored contiguously and they share a cache line when cached, can this cause a sort of “cross-locking”?

If so, is it a good practice to declare the mutexes right after (or before) the variable they guard?

Aligning the class to std::hardware_destructive_interference_size (P0154) might avoid this effect. Are the potential benefits worth the overalignment of the object?

Formless answered 16/9, 2016 at 11:6 Comment(9)
The C++ standard does not require class members to be laid out in any particular order (with some caveats that are not applicable here). You can determine from your compiler's documentation whether it uses a deterministic order for class members (likely).Metropolitan
@SamVarshavchik For a standard-layout class they must be in the declaration order, and based on what's posted so far, this could be a standard layout class. Also that doesn't really matter as OP is asking about details of particular implementationsBysshe
@Rakete1111 Please feel free to editFormless
@Formless Wait, what? I was just "answering" your first question :) Rather to put them in a specific order above variables, I would give them names that make it clear that the mutex guards that specific variable.Rhythm
@Rhythm The question is about how member order might affect caching, not about making the relationship between the members clear to a human reader.Bowline
@Bowline yeah, just realized that :)Rhythm
Btw what compiler are you on that actually implements those two?Odeen
@SamVarshavchik: Yes, the standard does give ordering information, even for non-standard layout types. For any types, members in the same access class (public/protected/private) are given space in their declared order. There may be padding or not, as implementation-defined. But they are in that order.Legislator
@Odeen I'm just trying to figure out their usefulness.Formless
O
4

Your variables seem unrelated in your question, so rather than hardware_destructive_interference_size you probably want hardware_constructive_interference_size:

struct keep_together {
    std::mutex m;
    Vector v;
};

alignas(std::hardware_constructive_interference_size) keep_together k1;
alignas(std::hardware_constructive_interference_size) keep_together k2;

destructive you want to use for cases like lock-free queues, were threads are reading two different atomics and you want to make sure they both actually get loaded. If that's an issue here, you'll need to explain why false sharing is what you're avoiding.

Is it a good practice to declare the mutex right after (or before) the variable it guards to increase the chance they are on the same cache line?

That's constructive interference.

Odeen answered 16/9, 2016 at 11:39 Comment(1)
Thanks for the clarification. I mixed both concepts. I'd like to encourage true sharing, but also to discourage false sharing in case it might happen. Imagine the mutexes are stored contiguously. Might they share a cache line? I edited the question to hopefully make it clearer.Formless

© 2022 - 2024 — McMap. All rights reserved.