1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
|
// SPDX-License-Identifier: BSD-2-Clause
/* Copyright (C) 2014 - 2021 Intel Corporation. */
#pragma once
#include <memkind.h>
#include <condition_variable>
#include <mutex>
#include <thread>
#include <vector>
// Malloc, jemalloc, memkind jemalloc and memkind memory operations definitions
#include "operations.hpp"
/* Framework for testing memory allocators pefromance */
namespace performance_tests
{
// Nanoseconds in second
const uint32_t NanoSecInSec = 1e9;
// Simple barrier implementation
class Barrier
{
// Barrier mutex
std::mutex m_barrierMutex;
// Contitional variable
std::condition_variable m_cVar;
// Number of threads expected to enter the barrier
size_t m_waiting;
timespec m_releasedAt;
public:
// Called by each thread entering the barrier; returns control to caller
// only after been called from the last thread expected at the barrier
void wait();
// (Re)Initializes the barrier
void reset(unsigned waiting)
{
m_releasedAt.tv_sec = m_releasedAt.tv_nsec = 0;
m_waiting = waiting;
}
// Get time when barrier was released
timespec &releasedAt()
{
return m_releasedAt;
}
// Singleton
static Barrier &GetInstance()
{
// Automatically created and deleted one and only instance
static Barrier instance;
return instance;
}
private:
Barrier()
{
reset(0);
}
// Cannot be used with singleton, so prevent compiler from creating them
// automatically
Barrier(Barrier const &) = delete;
void operator=(Barrier const &) = delete;
};
// Data of a single test action, that is, memory operation (malloc, calloc,
// etc.) to perform and its parameters (size, alignment etc.)
class Action
{
protected:
Operation *m_operation;
Operation *m_freeOperation;
const memkind_t m_kind;
void *m_allocation;
const size_t m_size;
const size_t m_offset;
const size_t m_alignment;
public:
Action(Operation *operation, Operation *freeOperation, const memkind_t kind,
const size_t size, const size_t offset, const size_t alignment)
: m_operation(operation),
m_freeOperation(freeOperation),
m_kind(kind),
m_allocation(nullptr),
m_size(size),
m_offset(offset),
m_alignment(alignment)
{}
Action(Operation *operation, Operation *freeOperation, const memkind_t kind)
: Action(operation, freeOperation, kind, 0, 0, 0)
{}
void alloc()
{
m_operation->perform(m_kind, m_allocation, m_size, m_offset,
m_alignment);
}
void free()
{
m_freeOperation->perform(m_kind, m_allocation);
}
};
// Performs and tracks requested memory operations in a separate thread
class Worker
{
protected:
#ifdef __DEBUG
uint16_t m_threadId;
#endif
// Requested number of test actions
const uint32_t m_actionsCount;
// List of memory block sizes - for each memory allocation operation actual
// value is chosen randomly
const vector<size_t> &m_allocationSizes;
// List of test actions
vector<Action *> m_actions;
// Memory free action
Action *m_freeAction;
// Operation kind (useful for memkind only)
memkind_t m_kind;
// Working thread
thread *m_thread;
public:
Worker(uint32_t actionsCount, const vector<size_t> &allocationSizes,
Operation *freeOperation, memkind_t kind);
~Worker();
// Set operations list for the worker
void init(const vector<Operation *> &testOperations,
Operation *&freeOperation);
// Create & start thread
void run();
#ifdef __DEBUG
// Get thread id
uint16_t getId();
// Set thread id
void setId(uint16_t threadId);
#endif
// Finish thread and free all allocations made
void finish();
// Free allocated memory
virtual void clean();
private:
// Actual thread function (allow inheritance)
virtual void work();
};
enum ExecutionMode
{
SingleInteration, // Single iteration, operations listS will be distributed
// among threads sequentially
ManyIterations // Each operations list will be run in separate iteration by
// each thread
};
struct Metrics {
uint64_t executedOperations;
uint64_t totalDuration;
double operationsPerSecond;
double avgOperationDuration;
double iterationDuration;
double repeatDuration;
};
// Performance test parameters class
class PerformanceTest
{
protected:
// empirically determined % of worst results needed to be discarded
// to eliminate malloc() performance results skewness
static constexpr double distardPercent = 20.0;
protected:
// Number of test repeats
size_t m_repeatsCount;
// Number of test repeats with worst results to be discarded
size_t m_discardCount;
// Number of threads
size_t m_threadsCount;
// Number of memory operations in each thread
uint32_t m_operationsCount;
// List of allocation sizes
vector<size_t> m_allocationSizes;
// List of list of allocation operations, utlization depends on execution
// mode
vector<vector<Operation *>> m_testOperations;
// Free operation
Operation *m_freeOperation;
// List of memory kinds (for memkind allocation only)
// distributed among threads sequentially
vector<memkind_t> m_kinds;
// List of thread workers
vector<Worker *> m_workers;
// Time measurement
vector<uint64_t> m_durations;
// Execution mode
ExecutionMode m_executionMode;
public:
// Create test
PerformanceTest(size_t repeatsCount, size_t threadsCount,
size_t operationsCount);
virtual ~PerformanceTest()
{}
// Set list of block sizes
void setAllocationSizes(const vector<size_t> &allocationSizes);
// Set list of operations per thread/per iteration (depending on execution
// mode)
void setOperations(const vector<vector<Operation *>> &testOperations,
Operation *freeOperation);
// Set per-thread list of memory kinds
void setKind(const vector<memkind_t> &kinds);
// Set execution mode (different operations per each thread/same operations
// for each thread, but many iterations)
void setExecutionMode(ExecutionMode operationMode);
// Execute test
int run();
// Print test parameters
virtual void showInfo();
// Write test metrics
void writeMetrics(const string &suiteName, const string &caseName,
const string &fileName = "");
Metrics getMetrics();
private:
// Run single iteration
void runIteration();
// Setup thread workers
void prepareWorkers();
};
} // namespace performance_tests
|