xref: /xnu-12377.41.6/tests/counter/benchmark.c (revision bbb1b6f9e71b8cdde6e5cd6f4841f207dee3d828)
1 /* Per-cpu counter microbenchmarks. */
2 
3 #include <assert.h>
4 #include <inttypes.h>
5 #include <pthread.h>
6 #include <stdatomic.h>
7 #include <stdbool.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 
12 #include <sys/types.h>
13 #include <sys/sysctl.h>
14 
15 #include "benchmark/helpers.h"
16 #include "counter/common.h"
17 
18 typedef enum test_variant {
19 	VARIANT_SCALABLE_COUNTER,
20 	VARIANT_ATOMIC,
21 	VARIANT_RACY
22 } test_variant_t;
23 
24 static const char* kScalableCounterArgument = "scalable";
25 static const char* kAtomicCounterArgument = "atomic";
26 static const char* kRacyCounterArgument = "racy";
27 
28 static const int64_t kChunkSize = 100000000;
29 
30 /* Arguments parsed from the command line */
31 typedef struct test_args {
32 	size_t n_threads;
33 	unsigned long long num_writes;
34 	test_variant_t variant;
35 	bool verbose;
36 } test_args_t;
37 
38 typedef struct {
39 	char _padding1[128];
40 	atomic_bool tg_test_start;
41 	atomic_ullong tg_num_writes_remaining;
42 	atomic_ullong tg_threads_ready;
43 	test_args_t tg_args;
44 	uint64_t tg_start_time;
45 	uint64_t tg_end_time;
46 	uint64_t tg_start_value;
47 	uint64_t tg_end_value;
48 	char _padding2[128];
49 } test_globals_t;
50 
51 static void parse_arguments(int argc, char** argv, test_args_t *args);
52 static const char *get_sysctl_name_for_test_variant(test_variant_t variant);
53 static void *writer(void *);
54 static uint64_t counter_read(test_variant_t);
55 
56 int
main(int argc,char ** argv)57 main(int argc, char** argv)
58 {
59 	test_globals_t globals = {0};
60 	pthread_t* threads = NULL;
61 	int ret;
62 	int is_development_kernel;
63 	size_t is_development_kernel_size = sizeof(is_development_kernel);
64 	pthread_attr_t pthread_attrs;
65 	uint64_t duration, writes_stored;
66 	double writes_per_second;
67 	double loss;
68 
69 	if (sysctlbyname("kern.development", &is_development_kernel,
70 	    &is_development_kernel_size, NULL, 0) != 0 || !is_development_kernel) {
71 		fprintf(stderr, "%s requires the development kernel\n", argv[0]);
72 		exit(1);
73 	}
74 
75 	parse_arguments(argc, argv, &(globals.tg_args));
76 	atomic_store(&(globals.tg_num_writes_remaining), globals.tg_args.num_writes);
77 
78 	threads = malloc(sizeof(pthread_t) * globals.tg_args.n_threads);
79 	assert(threads);
80 	ret = pthread_attr_init(&pthread_attrs);
81 	assert(ret == 0);
82 	ret = init_scalable_counter_test();
83 	assert(ret == 0);
84 	globals.tg_start_value = counter_read(globals.tg_args.variant);
85 	for (size_t i = 0; i < globals.tg_args.n_threads; i++) {
86 		ret = pthread_create(threads + i, &pthread_attrs, writer, &globals);
87 		assert(ret == 0);
88 	}
89 	for (size_t i = 0; i < globals.tg_args.n_threads; i++) {
90 		ret = pthread_join(threads[i], NULL);
91 		assert(ret == 0);
92 	}
93 	ret = fini_scalable_counter_test();
94 	assert(ret == 0);
95 	globals.tg_end_value = counter_read(globals.tg_args.variant);
96 
97 	duration = globals.tg_end_time - globals.tg_start_time;
98 	printf("-----Results-----\n");
99 	printf("rate,loss\n");
100 	writes_per_second = globals.tg_args.num_writes / ((double) duration / kNumNanosecondsInSecond);
101 	writes_stored = globals.tg_end_value - globals.tg_start_value;
102 	loss = (1.0 - ((double) writes_stored / globals.tg_args.num_writes)) * 100;
103 	printf("%.4f,%.4f\n", writes_per_second, loss);
104 	return 0;
105 }
106 
107 static void *
writer(void * arg)108 writer(void *arg)
109 {
110 	int ret;
111 	const char* sysctl_name;
112 	test_globals_t *globals = arg;
113 	int64_t value = kChunkSize;
114 	//size_t size = sizeof(value);
115 
116 	sysctl_name = get_sysctl_name_for_test_variant(globals->tg_args.variant);
117 	assert(sysctl_name != NULL);
118 
119 	if (atomic_fetch_add(&(globals->tg_threads_ready), 1) == globals->tg_args.n_threads - 1) {
120 		globals->tg_start_time = current_timestamp_ns();
121 		atomic_store(&globals->tg_test_start, true);
122 	}
123 	while (!atomic_load(&(globals->tg_test_start))) {
124 		;
125 	}
126 
127 	while (true) {
128 		unsigned long long remaining = atomic_fetch_sub(&(globals->tg_num_writes_remaining), value);
129 		if (remaining < kChunkSize || remaining > globals->tg_args.num_writes) {
130 			break;
131 		}
132 
133 		ret = sysctlbyname(sysctl_name, NULL, NULL, &value, sizeof(value));
134 		assert(ret == 0);
135 		if (remaining == kChunkSize || remaining - kChunkSize > remaining) {
136 			break;
137 		}
138 	}
139 
140 	if (atomic_fetch_sub(&(globals->tg_threads_ready), 1) == 1) {
141 		globals->tg_end_time = current_timestamp_ns();
142 	}
143 
144 	return NULL;
145 }
146 
147 static const char*
get_sysctl_name_for_test_variant(test_variant_t variant)148 get_sysctl_name_for_test_variant(test_variant_t variant)
149 {
150 	switch (variant) {
151 	case VARIANT_SCALABLE_COUNTER:
152 		return "kern.scalable_counter_write_benchmark";
153 	case VARIANT_ATOMIC:
154 		return "kern.scalable_counter_atomic_counter_write_benchmark";
155 	case VARIANT_RACY:
156 		return "kern.scalable_counter_racy_counter_benchmark";
157 	default:
158 		return NULL;
159 	}
160 }
161 
162 static const char*
get_sysctl_load_name_for_test_variant(test_variant_t variant)163 get_sysctl_load_name_for_test_variant(test_variant_t variant)
164 {
165 	switch (variant) {
166 	case VARIANT_SCALABLE_COUNTER:
167 		return "kern.scalable_counter_test_load";
168 	case VARIANT_ATOMIC:
169 		return "kern.scalable_counter_atomic_counter_load";
170 	case VARIANT_RACY:
171 		return "kern.scalable_counter_racy_counter_load";
172 	default:
173 		return NULL;
174 	}
175 }
176 
177 static uint64_t
counter_read(test_variant_t variant)178 counter_read(test_variant_t variant)
179 {
180 	const char *sysctl_name = get_sysctl_load_name_for_test_variant(variant);
181 	int result;
182 	uint64_t value;
183 	size_t size = sizeof(value);
184 	result = sysctlbyname(sysctl_name, &value, &size, NULL, 0);
185 	assert(result == 0);
186 	return value;
187 }
188 
189 static void
print_help(char ** argv)190 print_help(char** argv)
191 {
192 	fprintf(stderr, "%s: <test-variant> [-v] num_writes num_threads\n", argv[0]);
193 	fprintf(stderr, "\ntest variants:\n");
194 	fprintf(stderr, "	%s	Benchmark scalable counters.\n", kScalableCounterArgument);
195 	fprintf(stderr, "	%s	Benchmark single atomic counter.\n", kAtomicCounterArgument);
196 	fprintf(stderr, "	%s	Benchmark racy counter.\n", kRacyCounterArgument);
197 }
198 
199 static void
parse_arguments(int argc,char ** argv,test_args_t * args)200 parse_arguments(int argc, char** argv, test_args_t *args)
201 {
202 	int current_argument = 1;
203 	memset(args, 0, sizeof(test_args_t));
204 	if (argc < 4 || argc > 6) {
205 		print_help(argv);
206 		exit(1);
207 	}
208 	if (argv[current_argument][0] == '-') {
209 		if (strcmp(argv[current_argument], "-v") == 0) {
210 			args->verbose = true;
211 		} else {
212 			fprintf(stderr, "Unknown argument %s\n", argv[current_argument]);
213 			print_help(argv);
214 			exit(1);
215 		}
216 		current_argument++;
217 	}
218 	if (strncasecmp(argv[current_argument], kScalableCounterArgument, strlen(kScalableCounterArgument)) == 0) {
219 		args->variant = VARIANT_SCALABLE_COUNTER;
220 	} else if (strncasecmp(argv[current_argument], kAtomicCounterArgument, strlen(kAtomicCounterArgument)) == 0) {
221 		args->variant = VARIANT_ATOMIC;
222 	} else if (strncasecmp(argv[current_argument], kRacyCounterArgument, strlen(kRacyCounterArgument)) == 0) {
223 		args->variant = VARIANT_RACY;
224 	} else {
225 		print_help(argv);
226 		exit(1);
227 	}
228 	current_argument++;
229 
230 	long num_writes = strtol(argv[current_argument++], NULL, 10);
231 	if (num_writes == 0) {
232 		print_help(argv);
233 		exit(1);
234 	}
235 	long num_cores = strtol(argv[current_argument++], NULL, 10);
236 	if (num_cores == 0) {
237 		print_help(argv);
238 		exit(1);
239 	}
240 	assert(num_cores > 0 && num_cores <= get_ncpu());
241 	args->n_threads = (unsigned int) num_cores;
242 	args->num_writes = (unsigned long long) num_writes;
243 }
244