Skip to content

Instantly share code, notes, and snippets.

@vittorioromeo
Last active June 27, 2017 10:36
Show Gist options
  • Save vittorioromeo/a8b33ee3a0aac0288991004314d94e5d to your computer and use it in GitHub Desktop.
Save vittorioromeo/a8b33ee3a0aac0288991004314d94e5d to your computer and use it in GitHub Desktop.
#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