优化原子 compare_exchange_weak

Optimizing atomic compare_exchange_weak

我正在尝试创建一个模板来保存任意枚举类型(为了类型安全)并按照以下代码段所示存储它们:

enum my_flags : uint8_t {
    value = 0x01,
    foo = 0x02,
    bar = 0x04
}

template <class FlagType>
class atomic_flags {
    FlagType fetch_and_set(FlagType f) {
        //FlagType old; // <- Undefined behavior! At least in theory.
        FlagType old = flag_.load(std::memory_order_relaxed); // Correct, but takes two times longer.
        while(!flag_.compare_exchange_weak(old, static_cast<FlagType>(old | f))) {}
        return old;
    }

    std::atomic<FlagType> flag_;
};

存储本身是微不足道的,没有直接关系。我感兴趣的是两条注释行。第一个是由 C++ 标准定义的未定义行为 (UB)。第二个是我应该使用的正确性。但是基准测试表明它比第一个变体慢 2 倍。同时,第一个变体始终使用 msvc 编译器产生预期的行为。 (可能是因为编译器现在不必加载 old 两次,因为这是由 compare_exchange_weak 完成的。)

现在我的问题是:是否可以在不依赖 UB 的情况下实现相同的性能?(是的,这是性能临界区的一部分。)

作为旁注。如果我直接将 uint8_t 替换为类型并使用 fetch_or 的标准函数,则性能相当于 UB 情况。可能可以尝试通过一个大到足以包含 FlagType 的类型直接替换 flag_ 定义中的 FlagType ,但这对我来说似乎很容易出错。

编辑: 这是我用于测试正确性和基准测试的代码(只有 REQUIRE 语句将在基准测试中被遗漏。)

TEST_CASE( "Testing atomic_flags", "[atomic_flags]" ) {
    enum my_enum : uint8 {
        clear  = 0x00,
        first  = 0x01,
        second = 0x02,
        third  = 0x04,
        fourth = 0x08,
        fifth  = 0x10,

        all = first | second | third | fourth | fifth,
    };

    atomic_flags<my_enum> flag(clear);

    REQUIRE(flag.fetch_and_set(first) == clear);
    REQUIRE(flag.fetch_and_set(second) == first);
    REQUIRE(flag.fetch_and_set(fifth) == (first | second));
    REQUIRE(flag.fetch_and_set(third) == (first | second | fifth));
    REQUIRE(flag.fetch_and_set(fourth) == (first | second | third | fifth));
    REQUIRE(flag.fetch_and_clear(all) == all); // Note: fetch_and_clear removes a flag.
    REQUIRE(flag.load() == clear);
}

我的基准测试结果是 UB 为 40ns,每次调用正确为 75ns。

感谢大家快速帮助指出我可能存在的错误。

这两个版本的实际性能相当,但在第二种情况下,编译器出于某种原因没有进行所有可能的优化。

通过将 fetch_and_set 函数的每次调用包装在 benchmark::DoNotOptimize() 语句中,两种情况的表现都一样好(我使用的是 google 微基准库,并且此调用避免了 return 值被优化掉了)。因此原题的观点没有实际意义,初始化值显然是正确的选择。