Last active
June 27, 2017 10:36
-
-
Save vittorioromeo/a8b33ee3a0aac0288991004314d94e5d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <algorithm> | |
#include <chrono> | |
#include <iostream> | |
#include <numeric> | |
#include <sstream> | |
#include <string> | |
#include <vector> | |
#define FWD(...) ::std::forward<decltype(__VA_ARGS__)>(__VA_ARGS__) | |
std::string data0(50, ' '); | |
std::string data1(50, ' '); | |
template <typename F> | |
void for_iters(F&& f) | |
{ | |
constexpr std::size_t iters = 10000000; | |
for(std::size_t n = 0; n < iters; ++n) | |
{ | |
f(); | |
} | |
} | |
template <typename F> | |
auto bench(F&& f) | |
{ | |
using hr_clock = std::chrono::high_resolution_clock; | |
const auto tp = hr_clock::now(); | |
FWD(f)(); | |
return hr_clock::now() - tp; | |
} | |
volatile unsigned int sink; | |
template <typename C> | |
void to_sink(C&& c) | |
{ | |
sink += std::accumulate(c.begin(), c.end(), 0); | |
} | |
const auto oss_naive = [] { | |
for_iters([] { | |
std::ostringstream os; | |
os << "Data0: " << data0 << ";Data1=" << data1 << "\n"; | |
std::string result = os.str(); | |
to_sink(result); | |
}); | |
}; | |
const auto oss_avoid_ctor = [] { | |
std::ostringstream os; | |
for_iters([&os] { | |
os.str(""); | |
os << "Data0: " << data0 << ";Data1=" << data1 << "\n"; | |
std::string result = os.str(); | |
to_sink(result); | |
}); | |
}; | |
template <typename T> | |
struct Popper | |
{ | |
std::vector<T>& d_v; | |
constexpr Popper(std::vector<T>& v) noexcept : d_v{v} | |
{ | |
} | |
~Popper() noexcept | |
{ | |
d_v.pop_back(); | |
} | |
}; | |
// TODO: try storing a flag with each value for better cache coherency and | |
// simply | |
// look from left to right? Policy-based | |
template <typename T> | |
struct Pool | |
{ | |
std::vector<T> d_v; | |
std::vector<std::size_t> d_usable; | |
struct Guard | |
{ | |
Pool& d_p; | |
std::size_t d_i; | |
T& o() noexcept | |
{ | |
return d_p.at(d_i); | |
} | |
Guard(Pool& p, std::size_t i) noexcept : d_p{p}, d_i{i} | |
{ | |
} | |
~Guard() noexcept | |
{ | |
d_p.d_usable.push_back(d_i); | |
} | |
T& operator*() noexcept | |
{ | |
return o(); | |
} | |
const T& operator*() const noexcept | |
{ | |
return o(); | |
} | |
auto operator-> () noexcept | |
{ | |
return &o(); | |
} | |
auto operator-> () const noexcept | |
{ | |
return &o(); | |
} | |
}; | |
void resizeTo(std::size_t curr, std::size_t n) | |
{ | |
d_v.resize(n); | |
d_usable.reserve(n); | |
for(std::size_t i = curr; i < n; ++i) | |
{ | |
d_usable.push_back(i); | |
} | |
} | |
void resizeBy(std::size_t n) | |
{ | |
const auto curr = d_v.size(); | |
resizeTo(curr, curr + n); | |
} | |
Pool() | |
{ | |
resizeBy(5); | |
} | |
T& at(std::size_t i) noexcept | |
{ | |
return d_v[i]; | |
} | |
auto get() | |
{ | |
if(d_usable.empty()) | |
{ | |
resizeBy(5); | |
} | |
Popper<std::size_t> p{d_usable}; | |
return Guard{*this, d_usable.back()}; | |
} | |
}; | |
// TODO: try storing a flag with each value for better cache coherency and | |
// simply | |
// look from left to right? Policy-based | |
template <typename T> | |
struct Pool2 | |
{ | |
std::vector<std::pair<T, bool>> d_v; | |
struct Guard | |
{ | |
Pool2& d_p; | |
std::size_t d_i; | |
T& o() noexcept | |
{ | |
return d_p.at(d_i); | |
} | |
Guard(Pool2& p, std::size_t i) noexcept : d_p{p}, d_i{i} | |
{ | |
} | |
~Guard() noexcept | |
{ | |
d_p.d_v[d_i].second = true; | |
} | |
T& operator*() noexcept | |
{ | |
return o(); | |
} | |
const T& operator*() const noexcept | |
{ | |
return o(); | |
} | |
auto operator-> () noexcept | |
{ | |
return &o(); | |
} | |
auto operator-> () const noexcept | |
{ | |
return &o(); | |
} | |
}; | |
void resizeTo(std::size_t curr, std::size_t n) | |
{ | |
d_v.resize(n); | |
for(std::size_t i = curr; i < n; ++i) | |
{ | |
d_v[i].second = true; // usable | |
} | |
} | |
void resizeBy(std::size_t n) | |
{ | |
const auto curr = d_v.size(); | |
resizeTo(curr, curr + n); | |
} | |
Pool2() | |
{ | |
resizeBy(5); | |
} | |
T& at(std::size_t i) noexcept | |
{ | |
return d_v[i].first; | |
} | |
auto get() | |
{ | |
auto it = std::find_if(std::begin(d_v), std::end(d_v), | |
[](const auto& p) { return p.second; }); | |
if(it == std::end(d_v)) | |
{ | |
resizeBy(5); | |
it = std::next(std::begin(d_v), d_v.size() - 5); | |
} | |
return Guard{*this, static_cast<std::size_t>(it - std::begin(d_v))}; | |
} | |
}; | |
struct OssCtrlBlock final | |
{ | |
// Contains a string stream and a counter of active users. | |
// The counter is used to make sure that the string stream is only cleared | |
// when no one else is using it. This allows nested usage of the stream. | |
std::ostringstream d_oss; | |
std::size_t d_uses = 0; | |
}; | |
inline OssCtrlBlock& getOssCtrlBlock() noexcept | |
{ | |
// Return a reference to a 'thread_local' 'OssCtrlBlock'. | |
thread_local OssCtrlBlock oss; | |
return oss; | |
} | |
struct OssGuard final | |
{ | |
// Guard class that increments the number of uses of the string stream on | |
// construction. It decrements the counters (and clears the stream if | |
// appropriate) on destruction. | |
OssGuard() noexcept | |
{ | |
++(getOssCtrlBlock().d_uses); | |
} | |
// Prevent copies. | |
OssGuard(const OssGuard&) = delete; | |
OssGuard& operator=(const OssGuard&) = delete; | |
// Prevent moves. | |
OssGuard(OssGuard&&) = delete; | |
OssGuard& operator=(OssGuard&&) = delete; | |
~OssGuard() noexcept | |
{ | |
auto& cb = getOssCtrlBlock(); | |
if(--(cb.d_uses) == 0) | |
{ | |
cb.d_oss.str(""); | |
} | |
} | |
}; | |
inline auto& getOss() | |
{ | |
return getOssCtrlBlock().d_oss; | |
} | |
const auto oss_thread_local = [] { | |
for_iters([] { | |
OssGuard g; | |
getOss() << "Data0: " << data0 << ";Data1=" << data1 << "\n"; | |
std::string result = getOss().str(); | |
to_sink(result); | |
}); | |
}; | |
const auto w1arg_oss_thread_local = [] { | |
for_iters([] { | |
{ | |
OssGuard g; | |
getOss() << 'a'; | |
to_sink(getOss().str()); | |
} | |
{ | |
OssGuard g; | |
getOss() << 100; | |
to_sink(getOss().str()); | |
} | |
{ | |
OssGuard g; | |
getOss() << 25.124812; | |
to_sink(getOss().str()); | |
} | |
}); | |
}; | |
const auto w1arg_to_string = [] { | |
for_iters([] { | |
to_sink(std::to_string('a')); | |
to_sink(std::to_string(100)); | |
to_sink(std::to_string(25.124812)); | |
}); | |
}; | |
#define DO_AND_PRINT_BENCH(x) print_bench(#x, x) | |
int main() | |
{ | |
const auto print_bench = [](const char* title, auto&& f) { | |
const auto t = bench(FWD(f)); | |
const auto t_ms = | |
std::chrono::duration_cast<std::chrono::milliseconds>(t).count(); | |
std::cout << "'" << title << "' took " << t_ms << " milliseconds\n"; | |
}; | |
DO_AND_PRINT_BENCH(oss_naive); | |
DO_AND_PRINT_BENCH(oss_avoid_ctor); | |
DO_AND_PRINT_BENCH(oss_thread_local); | |
DO_AND_PRINT_BENCH(w1arg_oss_thread_local); | |
DO_AND_PRINT_BENCH(w1arg_to_string); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment