futriix/test/performance/framework.hpp
John Sully 09a263e607 Squashed 'deps/memkind/src/' content from commit bb9f19dd1
git-subtree-dir: deps/memkind/src
git-subtree-split: bb9f19dd1b3ed6cc5e1b35919564ccf6f4b32f69
2019-02-01 14:45:39 -05:00

281 lines
8.8 KiB
C++

/*
* Copyright (C) 2014 - 2018 Intel Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice(s),
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice(s),
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``AS IS'' AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <memkind.h>
#include <thread>
#include <vector>
#include <mutex>
#include <condition_variable>
// 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 measurment
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();
};
}