优化原子compare_exchange_weak

时间:2017-12-14 17:16:22

标签: c++ optimization c++-standard-library

我尝试创建一个模板来保存任意枚举类型(用于类型安全)并将其存储,如下面的代码段所示:

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。

1 个答案:

答案 0 :(得分:2)

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

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

通过在fetch_and_set语句中包含benchmark::DoNotOptimize()函数的每个调用,两个案例都很好地形成(我正在使用google microbenchmark lib,此调用可以避免返回值被优化掉)。因此,原始问题的要点没有实际意义,初始化值显然是正确的选择。