Ответ 1
"Лучшая интуиция, которая когда-либо была, - это" я должен ее измерить ". давайте узнаем:
#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, локальная версия выполняет 5е8 итераций в 4618 мс, глобальную версию - 4392 мс. Так что да, локальная версия медленнее примерно на 0,452 нс на итерацию. Хотя есть измеримая разница, она слишком мала, чтобы влиять на наблюдаемую производительность в большинстве ситуаций.
EDIT: Интересный контрапункт, переключение с clang++ на g++ изменяет порядок результатов. g++ - скомпилированные бинарные прогоны в 4418 мс (глобальные) против 4181 мс (локальные), поэтому локальные быстрее на 474 пикосекунды на итерацию. Тем не менее он подтверждает вывод о том, что разница между этими двумя методами мала.
EDIT 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 глобальный). Результаты подтверждают нашу интуицию, что глобальная техника должна быть более быстрой, чем локальная, с дельтами 2.96 наносекунд (g++) и 1,86 наносекундами (clang) на итерацию.