-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnanosleep_bench.cpp
More file actions
302 lines (253 loc) · 10 KB
/
nanosleep_bench.cpp
File metadata and controls
302 lines (253 loc) · 10 KB
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
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
/**
* @file nanosleep_bench.cpp
* @brief A benchmarking tool for measuring nanosleep accuracy and overhead
*
* This application benchmarks the nanosleep function by performing multiple sleep
* calls and measuring the actual time taken. It can also simulate system load by
* running CPU-intensive worker threads in parallel. Statistics including percentiles,
* overhead, and interruption rates are calculated and displayed.
*
* @license Unlicense - This is free and unencumbered software released into the public domain
* @author Cline (Claude-3-Opus)
* @server OpenRouter (Claude-3-Opus)
* @agent Cline (v1.0)
* @operator Aleksei Gutikov (gutikoff@gmail.com)
* @date 3/5/2025
*/
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cmath>
#include <cstdio>
#include <format>
#include <numeric>
#include <string>
#include <thread>
#include <time.h>
#include <unistd.h>
#include <vector>
#include <sys/prctl.h>
// Fast, variadic printf replacement using write() system call and std::format
template<typename... Args>
void printF(std::format_string<Args...> fmt, Args&&... args)
{
// Format the string using std::format
std::string formatted = std::format(fmt, std::forward<Args>(args)...);
// Write directly to stdout (file descriptor 1)
write(STDOUT_FILENO, formatted.data(), formatted.size());
}
// Global application configuration
struct AppConfig
{
long iterations = 1000; // Number of nanosleep calls to perform
long nanoseconds = 1000000; // Number of nanoseconds to sleep in each call
int worker_threads = 0; // Number of CPU-intensive worker threads
long timerslack = -1; // Timer slack value in nanoseconds (-1 means don't set)
};
// Print usage information
void print_usage(const char* program_name)
{
printF("Usage: {} <iterations> <nanoseconds> [worker_threads] [timerslack]\n", program_name);
printF(" iterations - Number of nanosleep calls to perform\n");
printF(" nanoseconds - Number of nanoseconds to sleep in each call\n");
printF(" worker_threads - Number of CPU-intensive worker threads (default: 0)\n");
printF(" timerslack - Timer slack value in nanoseconds (default: -1, don't set)\n");
printF(" Uses PR_SET_TIMERSLACK prctl to set thread timer slack\n");
}
// Parse and validate command line arguments
bool parse_arguments(int argc, char* argv[], AppConfig& config)
{
if (argc < 3 || argc > 5) {
print_usage(argv[0]);
return false;
}
config.iterations = std::atol(argv[1]);
config.nanoseconds = std::atol(argv[2]);
// Parse optional worker_threads argument
config.worker_threads = 0; // Default value
if (argc >= 4) {
config.worker_threads = std::atoi(argv[3]);
}
// Parse optional timerslack argument
config.timerslack = -1; // Default value (don't set)
if (argc >= 5) {
config.timerslack = std::atol(argv[4]);
}
if (config.iterations <= 0 || config.nanoseconds < 0 || config.worker_threads < 0) {
printF("Error: iterations must be positive, nanoseconds and worker_threads must be non-negative\n");
print_usage(argv[0]);
return false;
}
return true;
}
// CPU-intensive worker function
void cpu_worker(std::atomic<bool>& should_stop)
{
// Dummy variables for computation
double result = 0.0;
// Keep doing meaningless calculations until told to stop
while (!should_stop.load(std::memory_order_relaxed)) {
// CPU-intensive operations that are hard to optimize away
for (int i = 0; i < 10000; ++i) {
result += std::sin(result + i) * std::cos(result * i);
result = std::fmod(result, 10.0); // Keep result bounded
}
}
// Use the result to prevent the compiler from optimizing away the loop
volatile double dummy = result;
}
// Structure to hold information about interrupted calls
struct InterruptionStats
{
int count = 0; // Number of interrupted calls
double total_remaining_ns = 0; // Total remaining time in nanoseconds
};
// Structure to hold benchmark results
struct BenchmarkResult
{
std::vector<double> durations; // Individual duration measurements
InterruptionStats interruptions; // Statistics about interrupted calls
};
// Run the benchmark and collect measurements
BenchmarkResult run_benchmark(const AppConfig& config)
{
BenchmarkResult result;
// Prepare timespec structure for nanosleep
struct timespec req, rem;
req.tv_sec = 0;
req.tv_nsec = config.nanoseconds;
// Reserve space for duration measurements
result.durations.reserve(config.iterations);
// Run the benchmark
for (long i = 0; i < config.iterations; ++i) {
auto start = std::chrono::high_resolution_clock::now();
int ret = nanosleep(&req, &rem);
auto end = std::chrono::high_resolution_clock::now();
if (ret != 0) {
// Collect interruption statistics
result.interruptions.count++;
double remaining_ns = rem.tv_sec * 1e9 + rem.tv_nsec;
result.interruptions.total_remaining_ns += remaining_ns;
}
// Calculate duration in nanoseconds
std::chrono::nanoseconds duration = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start);
result.durations.push_back(duration.count());
}
return result;
}
// Structure to hold statistics
struct Statistics
{
double mean = 0.0;
double min = 0.0;
double p50 = 0.0;
double p90 = 0.0;
double p95 = 0.0;
double p99 = 0.0;
double max = 0.0;
double overhead = 0.0;
// Interruption statistics
int interruption_count = 0;
double total_remaining_ns = 0.0;
double avg_remaining_ns = 0.0;
double interruption_rate = 0.0; // Percentage of calls interrupted
};
// Calculate statistics from benchmark results
Statistics calculate_statistics(const BenchmarkResult& benchmark, long nanoseconds)
{
Statistics stats;
const auto& durations = benchmark.durations;
const auto& interruptions = benchmark.interruptions;
// Calculate mean using std::accumulate
double sum = std::accumulate(durations.begin(), durations.end(), 0.0);
stats.mean = sum / durations.size();
// Create a copy for percentile calculations
auto sorted_durations = durations;
std::sort(sorted_durations.begin(), sorted_durations.end());
// Calculate percentiles
size_t size = sorted_durations.size();
stats.min = sorted_durations.front();
stats.p50 = sorted_durations[size * 50 / 100];
stats.p90 = sorted_durations[size * 90 / 100];
stats.p95 = sorted_durations[size * 95 / 100];
stats.p99 = sorted_durations[size * 99 / 100];
stats.max = sorted_durations.back();
// Calculate overhead
stats.overhead = stats.mean - nanoseconds;
// Add interruption statistics
stats.interruption_count = interruptions.count;
stats.total_remaining_ns = interruptions.total_remaining_ns;
stats.avg_remaining_ns = interruptions.count > 0 ? interruptions.total_remaining_ns / interruptions.count : 0;
stats.interruption_rate = 100.0 * interruptions.count / size;
return stats;
}
// Print results using printF
void print_results(const Statistics& stats, const AppConfig& config)
{
printF("\nResults (all times in nanoseconds):\n");
printF(" Requested sleep time: {:.2f}\n", static_cast<double>(config.nanoseconds));
printF(" Worker threads: {}\n", config.worker_threads);
if (config.timerslack >= 0) {
printF(" Timer slack: {}\n", config.timerslack);
}
printF(" Average actual time: {:.2f}\n", stats.mean);
printF(" Percentiles:\n");
printF(" Min: {:.2f}\n", stats.min);
printF(" 50th (median): {:.2f}\n", stats.p50);
printF(" 90th: {:.2f}\n", stats.p90);
printF(" 95th: {:.2f}\n", stats.p95);
printF(" 99th: {:.2f}\n", stats.p99);
printF(" Max: {:.2f}\n", stats.max);
printF(" Overhead: {:.2f}\n", stats.overhead);
// Print interruption statistics
printF("\nInterruption Statistics:\n");
printF(" Interrupted calls: {}\n", stats.interruption_count);
printF(" Interruption rate: {:.2f}%\n", stats.interruption_rate);
if (stats.interruption_count > 0) {
printF(" Total remaining time: {:.2f} ns\n", stats.total_remaining_ns);
printF(" Avg remaining time: {:.2f} ns\n", stats.avg_remaining_ns);
}
}
int main(int argc, char* argv[])
{
// Parse command line arguments
AppConfig config;
if (!parse_arguments(argc, argv, config)) {
return 1;
}
printF("Running {} iterations with {} nanoseconds sleep time...\n", config.iterations, config.nanoseconds);
// Set timer slack if requested
if (config.timerslack >= 0) {
printF("Setting timer slack to {} nanoseconds...\n", config.timerslack);
if (prctl(PR_SET_TIMERSLACK, config.timerslack) != 0) {
printF("Warning: Failed to set timer slack\n");
}
}
// Start worker threads if requested
std::atomic<bool> should_stop{ false };
std::vector<std::thread> workers;
if (config.worker_threads > 0) {
printF("Starting {} CPU-intensive worker threads...\n", config.worker_threads);
workers.reserve(config.worker_threads);
for (int i = 0; i < config.worker_threads; ++i) {
// Using std::jthread instead of std::thread (C++20)
// std::jthread automatically joins on destruction
workers.emplace_back([&should_stop]() { cpu_worker(should_stop); });
}
}
// Run the benchmark
BenchmarkResult benchmark = run_benchmark(config);
// Stop worker threads
if (config.worker_threads > 0) {
printF("Stopping worker threads...\n");
should_stop.store(true, std::memory_order_relaxed);
for (auto& worker : workers) {
worker.join();
}
}
// Calculate statistics
Statistics stats = calculate_statistics(benchmark, config.nanoseconds);
// Print results
print_results(stats, config);
return 0;
}