libdpf/include/dpf/thread_work.hpp

137 lines
4.1 KiB
C++
Raw Permalink Normal View History

/// @file dpf/thread_work.hpp
/// @brief Lend a thread's randomness source to another thread for one task,
/// and charge the task's costs back to the thread that asked for it.
/// @details A kernel posted to a compute pool runs on a pool thread while the
/// party thread waits for it (`protocol::invoke_kernel`). The party
/// thread takes `draw_context::capture()`, and the pool thread holds
/// `adopt_draws` around the kernel, so the kernel's `uniform_fill`
/// draws come from the party's source (an installed `experiment`, or
/// any other hook) in program order. `work_meter::now()` before and
/// after the kernel measures its symmetric-key blocks, random bytes,
/// and CPU on the pool thread, and `absorb_work` on the party thread
/// adds them to the party's own counters. `thread_cpu_ns()` is this
/// thread's CPU time plus the CPU time absorbed that way.
#ifndef LIBDPF_INCLUDE_DPF_THREAD_WORK_HPP__
#define LIBDPF_INCLUDE_DPF_THREAD_WORK_HPP__
#include <cstddef>
#include <cstdint>
#include <ctime>
#include "dpf/experiment_note.hpp"
#include "dpf/prg_count.hpp"
#include "dpf/random.hpp"
namespace dpf
{
namespace detail
{
inline thread_local std::uint64_t absorbed_cpu_ns_tls = 0;
inline std::uint64_t own_thread_cpu_ns() noexcept
{
timespec ts{};
#if defined(CLOCK_THREAD_CPUTIME_ID)
if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts) == 0)
return static_cast<std::uint64_t>(ts.tv_sec) * 1000000000ull
+ static_cast<std::uint64_t>(ts.tv_nsec);
#endif
return 0;
}
} // namespace detail
/// @brief True when this platform has a per-thread CPU clock.
inline bool have_thread_cpu_clock() noexcept
{
#if defined(CLOCK_THREAD_CPUTIME_ID)
timespec ts{};
return clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts) == 0;
#else
return false;
#endif
}
/// @brief This thread's CPU time plus the CPU time of tasks it handed off.
inline std::uint64_t thread_cpu_ns() noexcept
{
return detail::own_thread_cpu_ns() + detail::absorbed_cpu_ns_tls;
}
/// @brief This thread's costs at one instant.
struct work_meter
{
prg::counts sym{};
std::uint64_t random_bytes = 0;
std::uint64_t cpu_ns = 0;
static work_meter now() noexcept
{
work_meter m;
m.sym = prg::snapshot();
m.random_bytes = random_bytes_count();
m.cpu_ns = detail::own_thread_cpu_ns();
return m;
}
};
/// @brief Charge this thread for the costs between `before` and `after`,
/// both taken on the thread that did the work.
inline void absorb_work(const work_meter & before, const work_meter & after) noexcept
{
prg::counts delta{};
for (std::size_t i = 0; i < delta.size(); ++i)
delta[i] = after.sym[i] - before.sym[i];
prg::absorb(delta);
detail::random_bytes_tls += after.random_bytes - before.random_bytes;
detail::absorbed_cpu_ns_tls += after.cpu_ns - before.cpu_ns;
}
/// @brief One thread's randomness source and seed sink.
struct draw_context
{
void (*hook)(void *, std::size_t) = nullptr;
void * ctx = nullptr;
detail::experiment_seed_sink * sink = nullptr;
static draw_context capture() noexcept
{
draw_context c;
c.hook = detail::uniform_bytes_hook;
c.ctx = detail::uniform_bytes_ctx;
c.sink = detail::experiment_seed_sink_tls;
return c;
}
};
/// @brief Draw from `from` on this thread until the scope ends. The thread
/// that captured `from` must not draw while this scope is open.
class adopt_draws
{
public:
explicit adopt_draws(const draw_context & from) noexcept
: saved_(draw_context::capture())
{
set(from);
}
~adopt_draws() { set(saved_); }
adopt_draws(const adopt_draws &) = delete;
adopt_draws & operator=(const adopt_draws &) = delete;
private:
static void set(const draw_context & c) noexcept
{
detail::uniform_bytes_hook = c.hook;
detail::uniform_bytes_ctx = c.ctx;
detail::experiment_seed_sink_tls = c.sink;
}
draw_context saved_;
};
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_THREAD_WORK_HPP__