c++ - 在 C++11 中使用静态变量是否有惩罚

标签 c++ multithreading performance c++11 static

在 C++11 中,这个:

const std::vector<int>& f() {
    static const std::vector<int> x { 1, 2, 3 };
    return x;
}

是线程安全的。但是,由于这种额外的线程安全保证,在第一次(即初始化时)调用此函数是否有额外的惩罚?我想知道该函数是否会比使用全局变量的函数慢,因为它必须获取一个互斥锁来检查它是否在每次调用时都被另一个线程初始化,或者其他什么。

最佳答案

"The best intution to be ever had is 'I should measure this.'"所以let's find out :

#include <atomic>
#include <chrono>
#include <cstdint>
#include <iostream>
#include <numeric>
#include <vector>

namespace {
class timer {
    using hrc = std::chrono::high_resolution_clock;
    hrc::time_point start;

    static hrc::time_point now() {
      // Prevent memory operations from reordering across the
      // time measurement. This is likely overkill, needs more
      // research to determine the correct fencing.
      std::atomic_thread_fence(std::memory_order_seq_cst);
      auto t = hrc::now();
      std::atomic_thread_fence(std::memory_order_seq_cst);
      return t;
    }

public:
    timer() : start(now()) {}

    hrc::duration elapsed() const {
      return now() - start;
    }

    template <typename Duration>
    typename Duration::rep elapsed() const {
      return std::chrono::duration_cast<Duration>(elapsed()).count();
    }

    template <typename Rep, typename Period>
    Rep elapsed() const {
      return elapsed<std::chrono::duration<Rep,Period>>();
    }
};

const std::vector<int>& f() {
    static const auto x = std::vector<int>{ 1, 2, 3 };
    return x;
}

static const auto y = std::vector<int>{ 1, 2, 3 };
const std::vector<int>& g() {
    return y;
}

const unsigned long long n_iterations = 500000000;

template <typename F>
void test_one(const char* name, F f) {
  f(); // First call outside the timer.

  using value_type = typename std::decay<decltype(f()[0])>::type;
  std::cout << name << ": " << std::flush;

  auto t = timer{};
  auto sum = uint64_t{};
  for (auto i = n_iterations; i > 0; --i) {
    const auto& vec = f();
    sum += std::accumulate(begin(vec), end(vec), value_type{});
  }
  const auto elapsed = t.elapsed<std::chrono::milliseconds>();
  std::cout << elapsed << " ms (" << sum << ")\n";
}
} // anonymous namespace

int main() {
  test_one("local static", f);
  test_one("global static", g);
}

在 Coliru 运行,本地版本在 4618 毫秒内执行 5e8 次迭代,全局版本在 4392 毫秒内执行。所以是的,本地版本每次迭代要慢大约 0.452 纳秒。尽管存在可测量的差异,但在大多数情况下,它太小而不会影响观察到的性能。


编辑:有趣的对位,switching from clang++ to g++ changes the result ordering . g++ 编译的二进制文件运行时间为 4418 毫秒(全局)与 4181 毫秒(本地),因此 local 每次迭代快 474 皮秒。尽管如此,它确实重申了两种方法之间的差异很小的结论。
编辑 2:检查生成的程序集,我决定将函数指针转换为函数对象以实现更好的内联。通过函数指针间接调用的时间并不是 OP 中代码的真正特征。所以我使用了这个程序:

#include <atomic>
#include <chrono>
#include <cstdint>
#include <iostream>
#include <numeric>
#include <vector>

namespace {
class timer {
    using hrc = std::chrono::high_resolution_clock;
    hrc::time_point start;

    static hrc::time_point now() {
      // Prevent memory operations from reordering across the
      // time measurement. This is likely overkill.
      std::atomic_thread_fence(std::memory_order_seq_cst);
      auto t = hrc::now();
      std::atomic_thread_fence(std::memory_order_seq_cst);
      return t;
    }

public:
    timer() : start(now()) {}

    hrc::duration elapsed() const {
      return now() - start;
    }

    template <typename Duration>
    typename Duration::rep elapsed() const {
      return std::chrono::duration_cast<Duration>(elapsed()).count();
    }

    template <typename Rep, typename Period>
    Rep elapsed() const {
      return elapsed<std::chrono::duration<Rep,Period>>();
    }
};

class f {
public:
    const std::vector<int>& operator()() {
        static const auto x = std::vector<int>{ 1, 2, 3 };
        return x;
    }
};

class g {
    static const std::vector<int> x;
public:
    const std::vector<int>& operator()() {
        return x;
    }
};

const std::vector<int> g::x{ 1, 2, 3 };

const unsigned long long n_iterations = 500000000;

template <typename F>
void test_one(const char* name, F f) {
  f(); // First call outside the timer.

  using value_type = typename std::decay<decltype(f()[0])>::type;
  std::cout << name << ": " << std::flush;

  auto t = timer{};
  auto sum = uint64_t{};
  for (auto i = n_iterations; i > 0; --i) {
    const auto& vec = f();
    sum += std::accumulate(begin(vec), end(vec), value_type{});
  }
  const auto elapsed = t.elapsed<std::chrono::milliseconds>();
  std::cout << elapsed << " ms (" << sum << ")\n";
}
} // anonymous namespace

int main() {
  test_one("local static", f());
  test_one("global static", g());
}

毫不奇怪,g++ (3803ms local, 2323ms global) 下的运行时间都更快和 clang (4183ms local, 3253ms global) .结果证实了我们的直觉,即全局技术应该比局部技术更快,每次迭代的增量分别为 2.96 纳秒 (g++) 和 1.86 纳秒 (clang)。

关于c++ - 在 C++11 中使用静态变量是否有惩罚,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21488559/

相关文章:

c++ - 将 std::bind 与成员函数一起使用,是否为该参数使用对象指针?

c++ - 在可选结构成员中分配成员

c++ - 查看visual studio 2010中一个线程获得了多少锁?

java - 线程池中的awaitTermination不会终止线程池

javascript - 如何使用 greensock JavaScript 库控制主线程?

performance - 关于超线程中 L1 Cache 的自适应模式

c# - RavenDB 中的索引太多

c++ - 找不到用户定义的 getline。自动扣款有问题?

c++ - OpenCV 错误 : Assertion failed in matrix. cpp 第 522 行,/matrix.cpp:522:错误:(-215)

c# - 用于查找项目的字典与列表