File: framework.hpp

package info (click to toggle)
memkind 1.14.0-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 8,508 kB
  • sloc: ansic: 72,572; cpp: 39,493; sh: 4,594; perl: 4,250; xml: 2,044; python: 1,753; makefile: 1,393; csh: 7
file content (256 lines) | stat: -rw-r--r-- 6,822 bytes parent folder | download
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