]> git.saurik.com Git - apple/xnu.git/blame - tests/perf_vmfault.c
xnu-6153.41.3.tar.gz
[apple/xnu.git] / tests / perf_vmfault.c
CommitLineData
d9a64523
A
1#include <unistd.h>
2#include <stdlib.h>
3#include <sys/mman.h>
4#include <sys/sysctl.h>
5#include <mach/mach.h>
6#include <mach/vm_map.h>
7#include <darwintest.h>
8#include <TargetConditionals.h>
9#include <perfcheck_keys.h>
10
11T_GLOBAL_META(
12 T_META_NAMESPACE("xnu.vm.perf"),
13 T_META_CHECK_LEAKS(false),
14 T_META_TAG_PERF
0a7de745 15 );
d9a64523
A
16
17#ifdef DT_IOSMARK
0a7de745 18#define MEMSIZE (1UL<<29) /* 512 MB */
d9a64523 19#else
0a7de745 20#define MEMSIZE (1UL<<27) /* 128 MB */
d9a64523
A
21#endif
22
0a7de745
A
23#define VM_TAG1 100
24#define VM_TAG2 101
25
d9a64523
A
26enum {
27 SOFT_FAULT,
28 ZERO_FILL,
0a7de745
A
29 NUM_FAULT_TYPES
30};
31
32enum {
33 VARIANT_DEFAULT = 1,
34 VARIANT_SINGLE_REGION,
35 VARIANT_MULTIPLE_REGIONS,
36 NUM_MAPPING_VARIANTS
37};
38
39static char *variant_str[] = {
40 "none",
41 "default",
42 "single-region",
43 "multiple-regions"
d9a64523
A
44};
45
0a7de745
A
46
47typedef struct {
48 char *region_addr;
49 char *shared_region_addr;
50 size_t region_len;
51} memregion_config;
52
53static memregion_config *memregion_config_per_thread;
54
55static size_t pgsize;
d9a64523
A
56static int num_threads;
57static int ready_thread_count;
cb323159 58static int finished_thread_count;
0a7de745 59static dt_stat_time_t runtime;
d9a64523
A
60static pthread_cond_t start_cvar;
61static pthread_cond_t threads_ready_cvar;
cb323159 62static pthread_cond_t threads_finished_cvar;
d9a64523 63static pthread_mutex_t ready_thread_count_lock;
cb323159 64static pthread_mutex_t finished_thread_count_lock;
d9a64523 65
0a7de745
A
66static void map_mem_regions_default(int fault_type, size_t memsize);
67static void map_mem_regions_single(int fault_type, size_t memsize);
68static void map_mem_regions_multiple(int fault_type, size_t memsize);
69static void map_mem_regions(int fault_type, int mapping_variant, size_t memsize);
70static void unmap_mem_regions(int mapping_variant, size_t memsize);
71static void setup_per_thread_regions(char *memblock, char *memblock_share, int fault_type, size_t memsize);
d9a64523
A
72static void fault_pages(int thread_id);
73static void execute_threads(void);
74static void *thread_setup(void *arg);
0a7de745
A
75static void run_test(int fault_type, int mapping_variant, size_t memsize);
76static void setup_and_run_test(int test, int threads);
d9a64523
A
77static int get_ncpu(void);
78
0a7de745
A
79/* Allocates memory using the default mmap behavior. Each VM region created is capped at 128 MB. */
80static void
81map_mem_regions_default(int fault_type, size_t memsize)
d9a64523 82{
d9a64523
A
83 volatile char val;
84 vm_prot_t curprot, maxprot;
0a7de745 85 char *ptr, *memblock, *memblock_share = NULL;
d9a64523 86
0a7de745 87 memblock = (char *)mmap(NULL, memsize, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
d9a64523
A
88 T_QUIET; T_ASSERT_NE((void *)memblock, MAP_FAILED, "mmap");
89
0a7de745 90 if (fault_type == SOFT_FAULT) {
d9a64523 91 /* Fault in all the pages of the original region. */
0a7de745 92 for (ptr = memblock; ptr < memblock + memsize; ptr += pgsize) {
d9a64523
A
93 val = *ptr;
94 }
95 /* Remap the region so that subsequent accesses result in read soft faults. */
96 T_QUIET; T_ASSERT_MACH_SUCCESS(vm_remap(mach_task_self(), (vm_address_t *)&memblock_share,
0a7de745
A
97 memsize, 0, VM_FLAGS_ANYWHERE, mach_task_self(), (vm_address_t)memblock, FALSE,
98 &curprot, &maxprot, VM_INHERIT_DEFAULT), "vm_remap");
d9a64523 99 }
0a7de745 100 setup_per_thread_regions(memblock, memblock_share, fault_type, memsize);
d9a64523
A
101}
102
0a7de745
A
103/* Creates a single VM region by mapping in a named memory entry. */
104static void
105map_mem_regions_single(int fault_type, size_t memsize)
d9a64523 106{
0a7de745
A
107 volatile char val;
108 vm_prot_t curprot, maxprot;
109 char *ptr, *memblock = NULL, *memblock_share = NULL;
110 vm_size_t size = memsize;
111 vm_offset_t addr1 = 0;
112 mach_port_t mem_handle = MACH_PORT_NULL;
113
114 /* Allocate a region and fault in all the pages. */
115 T_QUIET; T_ASSERT_MACH_SUCCESS(vm_allocate(mach_task_self(), &addr1, size, VM_FLAGS_ANYWHERE), "vm_allocate");
116 for (ptr = (char *)addr1; ptr < (char *)addr1 + memsize; ptr += pgsize) {
117 val = *ptr;
d9a64523 118 }
0a7de745
A
119
120 /* Create a named memory entry from the region allocated above, and de-allocate said region. */
121 T_QUIET; T_ASSERT_MACH_SUCCESS(mach_make_memory_entry(mach_task_self(), &size, addr1, VM_PROT_ALL | MAP_MEM_NAMED_CREATE,
122 &mem_handle, MACH_PORT_NULL), "mach_make_memory_entry");
123 T_QUIET; T_ASSERT_MACH_SUCCESS(vm_deallocate(mach_task_self(), addr1, size), "vm_deallocate");
124
125 /* Map in the named entry and deallocate it. */
126 T_QUIET; T_ASSERT_MACH_SUCCESS(vm_map(mach_task_self(), (vm_address_t *)&memblock, size, 0, VM_FLAGS_ANYWHERE, mem_handle, 0,
127 FALSE, VM_PROT_DEFAULT, VM_PROT_ALL, VM_INHERIT_NONE), "vm_map");
128 T_QUIET; T_ASSERT_MACH_SUCCESS(mach_port_deallocate(mach_task_self(), mem_handle), "mach_port_deallocate");
129
130 if (fault_type == SOFT_FAULT) {
131 /* Fault in all the pages of the original region. */
132 for (ptr = memblock; ptr < memblock + memsize; ptr += pgsize) {
133 val = *ptr;
134 }
135 /* Remap the region so that subsequent accesses result in read soft faults. */
136 T_QUIET; T_ASSERT_MACH_SUCCESS(vm_remap(mach_task_self(), (vm_address_t *)&memblock_share,
137 memsize, 0, VM_FLAGS_ANYWHERE, mach_task_self(), (vm_address_t)memblock, FALSE,
138 &curprot, &maxprot, VM_INHERIT_DEFAULT), "vm_remap");
139 }
140 setup_per_thread_regions(memblock, memblock_share, fault_type, memsize);
d9a64523
A
141}
142
0a7de745
A
143/* Allocates a separate VM region for each thread. */
144static void
145map_mem_regions_multiple(int fault_type, size_t memsize)
d9a64523 146{
0a7de745
A
147 int i;
148 size_t region_len, num_pages;
d9a64523 149 volatile char val;
0a7de745
A
150 char *ptr, *memblock, *memblock_share;
151 vm_prot_t curprot, maxprot;
d9a64523 152
0a7de745 153 num_pages = memsize / pgsize;
d9a64523 154
0a7de745
A
155 for (i = 0; i < num_threads; i++) {
156 memblock = NULL;
157
158 region_len = num_pages / (size_t)num_threads;
159 if ((size_t)i < num_pages % (size_t)num_threads) {
160 region_len++;
161 }
162 region_len *= pgsize;
163
164 int flags = VM_MAKE_TAG((i % 2)? VM_TAG1 : VM_TAG2) | MAP_ANON | MAP_PRIVATE;
165
166 memblock = (char *)mmap(NULL, region_len, PROT_READ | PROT_WRITE, flags, -1, 0);
167 T_QUIET; T_ASSERT_NE((void *)memblock, MAP_FAILED, "mmap");
168 memregion_config_per_thread[i].region_addr = memblock;
169 memregion_config_per_thread[i].shared_region_addr = 0;
170 memregion_config_per_thread[i].region_len = region_len;
171
172 if (fault_type == SOFT_FAULT) {
173 /* Fault in all the pages of the original region. */
174 for (ptr = memblock; ptr < memblock + region_len; ptr += pgsize) {
175 val = *ptr;
176 }
177 memblock_share = NULL;
178 /* Remap the region so that subsequent accesses result in read soft faults. */
179 T_QUIET; T_ASSERT_MACH_SUCCESS(vm_remap(mach_task_self(), (vm_address_t *)&memblock_share,
180 region_len, 0, VM_FLAGS_ANYWHERE, mach_task_self(), (vm_address_t)memblock, FALSE,
181 &curprot, &maxprot, VM_INHERIT_DEFAULT), "vm_remap");
182 memregion_config_per_thread[i].shared_region_addr = memblock_share;
183 }
d9a64523 184 }
0a7de745
A
185}
186
187static void
188map_mem_regions(int fault_type, int mapping_variant, size_t memsize)
189{
190 memregion_config_per_thread = (memregion_config *)malloc(sizeof(*memregion_config_per_thread) * (size_t)num_threads);
191 switch (mapping_variant) {
192 case VARIANT_SINGLE_REGION:
193 map_mem_regions_single(fault_type, memsize);
194 break;
195 case VARIANT_MULTIPLE_REGIONS:
196 map_mem_regions_multiple(fault_type, memsize);
197 break;
198 case VARIANT_DEFAULT:
199 default:
200 map_mem_regions_default(fault_type, memsize);
d9a64523 201 }
0a7de745
A
202}
203
204static void
205setup_per_thread_regions(char *memblock, char *memblock_share, int fault_type, size_t memsize)
206{
207 int i;
208 size_t region_len, region_start, num_pages;
209
210 num_pages = memsize / pgsize;
211 for (i = 0; i < num_threads; i++) {
212 region_len = num_pages / (size_t)num_threads;
213 region_start = region_len * (size_t)i;
214
215 if ((size_t)i < num_pages % (size_t)num_threads) {
216 region_start += (size_t)i;
217 region_len++;
218 } else {
219 region_start += num_pages % (size_t)num_threads;
220 }
d9a64523 221
0a7de745
A
222 region_start *= pgsize;
223 region_len *= pgsize;
d9a64523 224
0a7de745
A
225 memregion_config_per_thread[i].region_addr = memblock + region_start;
226 memregion_config_per_thread[i].shared_region_addr = ((fault_type == SOFT_FAULT) ?
227 memblock_share + region_start : 0);
228 memregion_config_per_thread[i].region_len = region_len;
229 }
230}
231
232static void
233unmap_mem_regions(int mapping_variant, size_t memsize)
234{
235 if (mapping_variant == VARIANT_MULTIPLE_REGIONS) {
236 int i;
237 for (i = 0; i < num_threads; i++) {
238 if (memregion_config_per_thread[i].shared_region_addr != 0) {
239 T_QUIET; T_ASSERT_MACH_SUCCESS(munmap(memregion_config_per_thread[i].shared_region_addr,
240 memregion_config_per_thread[i].region_len), "munmap");
241 }
242 T_QUIET; T_ASSERT_MACH_SUCCESS(munmap(memregion_config_per_thread[i].region_addr,
243 memregion_config_per_thread[i].region_len), "munmap");
244 }
245 } else {
246 if (memregion_config_per_thread[0].shared_region_addr != 0) {
247 T_QUIET; T_ASSERT_MACH_SUCCESS(munmap(memregion_config_per_thread[0].shared_region_addr, memsize), "munmap");
248 }
249 T_QUIET; T_ASSERT_MACH_SUCCESS(munmap(memregion_config_per_thread[0].region_addr, memsize), "munmap");
250 }
251}
252
253static void
254fault_pages(int thread_id)
255{
256 char *ptr, *block;
257 volatile char val;
258
259 block = memregion_config_per_thread[thread_id].shared_region_addr ?
260 memregion_config_per_thread[thread_id].shared_region_addr :
261 memregion_config_per_thread[thread_id].region_addr;
262 for (ptr = block; ptr < block + memregion_config_per_thread[thread_id].region_len; ptr += pgsize) {
d9a64523
A
263 val = *ptr;
264 }
265}
266
0a7de745
A
267static void *
268thread_setup(void *arg)
269{
270 int my_index = *((int *)arg);
271
272 T_QUIET; T_ASSERT_POSIX_SUCCESS(pthread_mutex_lock(&ready_thread_count_lock), "pthread_mutex_lock");
273 ready_thread_count++;
274 if (ready_thread_count == num_threads) {
275 T_QUIET; T_ASSERT_POSIX_SUCCESS(pthread_cond_signal(&threads_ready_cvar), "pthread_cond_signal");
276 }
277 T_QUIET; T_ASSERT_POSIX_SUCCESS(pthread_cond_wait(&start_cvar, &ready_thread_count_lock), "pthread_cond_wait");
278 T_QUIET; T_ASSERT_POSIX_SUCCESS(pthread_mutex_unlock(&ready_thread_count_lock), "pthread_mutex_unlock");
279
280 fault_pages(my_index);
cb323159
A
281
282 /* Up the finished count */
283 T_QUIET; T_ASSERT_POSIX_SUCCESS(pthread_mutex_lock(&finished_thread_count_lock), "pthread_mutex_lock");
284 finished_thread_count++;
285 if (finished_thread_count == num_threads) {
286 /* All the threads are done. Wake up the main thread */
287 T_QUIET; T_ASSERT_POSIX_SUCCESS(pthread_cond_signal(&threads_finished_cvar), "pthread_cond_signal");
288 }
289 T_QUIET; T_ASSERT_POSIX_SUCCESS(pthread_mutex_unlock(&finished_thread_count_lock), "pthread_mutex_unlock");
0a7de745
A
290 return NULL;
291}
292
293static void
294execute_threads(void)
d9a64523
A
295{
296 int thread_index, thread_retval;
297 int *thread_indices;
0a7de745 298 void *thread_retval_ptr = &thread_retval;
d9a64523
A
299 pthread_t* threads;
300
301 T_QUIET; T_ASSERT_POSIX_SUCCESS(pthread_cond_init(&threads_ready_cvar, NULL), "pthread_cond_init");
302 T_QUIET; T_ASSERT_POSIX_SUCCESS(pthread_cond_init(&start_cvar, NULL), "pthread_cond_init");
303 T_QUIET; T_ASSERT_POSIX_SUCCESS(pthread_mutex_init(&ready_thread_count_lock, NULL), "pthread_mutex_init");
cb323159
A
304 T_QUIET; T_ASSERT_POSIX_SUCCESS(pthread_cond_init(&threads_finished_cvar, NULL), "pthread_cond_init");
305 T_QUIET; T_ASSERT_POSIX_SUCCESS(pthread_mutex_init(&finished_thread_count_lock, NULL), "pthread_mutex_init");
d9a64523 306 ready_thread_count = 0;
cb323159 307 finished_thread_count = 0;
d9a64523
A
308
309 threads = (pthread_t *)malloc(sizeof(*threads) * (size_t)num_threads);
310 thread_indices = (int *)malloc(sizeof(*thread_indices) * (size_t)num_threads);
0a7de745 311 for (thread_index = 0; thread_index < num_threads; thread_index++) {
d9a64523
A
312 thread_indices[thread_index] = thread_index;
313 T_QUIET; T_ASSERT_POSIX_SUCCESS(pthread_create(&threads[thread_index], NULL,
0a7de745 314 thread_setup, (void *)&thread_indices[thread_index]), "pthread_create");
d9a64523
A
315 }
316
317 T_QUIET; T_ASSERT_POSIX_SUCCESS(pthread_mutex_lock(&ready_thread_count_lock), "pthread_mutex_lock");
cb323159 318 while (ready_thread_count != num_threads) {
d9a64523 319 T_QUIET; T_ASSERT_POSIX_SUCCESS(pthread_cond_wait(&threads_ready_cvar, &ready_thread_count_lock),
0a7de745 320 "pthread_cond_wait");
d9a64523
A
321 }
322 T_QUIET; T_ASSERT_POSIX_SUCCESS(pthread_mutex_unlock(&ready_thread_count_lock), "pthread_mutex_unlock");
323
0a7de745 324 T_STAT_MEASURE(runtime) {
cb323159 325 /* Ungate the threads */
d9a64523 326 T_QUIET; T_ASSERT_POSIX_SUCCESS(pthread_cond_broadcast(&start_cvar), "pthread_cond_broadcast");
cb323159
A
327 /* Wait for the threads to finish */
328 T_QUIET; T_ASSERT_POSIX_SUCCESS(pthread_mutex_lock(&finished_thread_count_lock), "pthread_mutex_lock");
329 while (finished_thread_count != num_threads) {
330 T_QUIET; T_ASSERT_POSIX_SUCCESS(pthread_cond_wait(&threads_finished_cvar, &finished_thread_count_lock), "pthread_cond_wait");
d9a64523
A
331 }
332 };
333
cb323159
A
334 /* Join the threads */
335 for (thread_index = 0; thread_index < num_threads; thread_index++) {
336 T_QUIET; T_ASSERT_POSIX_SUCCESS(pthread_join(threads[thread_index], &thread_retval_ptr),
337 "pthread_join");
338 }
339
d9a64523
A
340 free(threads);
341 free(thread_indices);
342}
343
0a7de745
A
344static void
345run_test(int fault_type, int mapping_variant, size_t memsize)
d9a64523 346{
0a7de745
A
347 char metric_str[32];
348 size_t num_pages;
d9a64523
A
349 size_t sysctl_size = sizeof(pgsize);
350 int ret = sysctlbyname("vm.pagesize", &pgsize, &sysctl_size, NULL, 0);
351 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl vm.pagesize failed");
352
0a7de745 353 num_pages = memsize / pgsize;
d9a64523 354
0a7de745
A
355 T_QUIET; T_ASSERT_LT(fault_type, NUM_FAULT_TYPES, "invalid test type");
356 T_QUIET; T_ASSERT_LT(mapping_variant, NUM_MAPPING_VARIANTS, "invalid mapping variant");
d9a64523 357 T_QUIET; T_ASSERT_GT(num_threads, 0, "num_threads <= 0");
0a7de745 358 T_QUIET; T_ASSERT_GT((int)num_pages / num_threads, 0, "num_pages/num_threads <= 0");
d9a64523 359
0a7de745 360 T_LOG("No. of cpus: %d", get_ncpu());
d9a64523
A
361 T_LOG("No. of threads: %d", num_threads);
362 T_LOG("No. of pages: %ld", num_pages);
363 T_LOG("Pagesize: %ld", pgsize);
0a7de745
A
364 T_LOG("Allocation size: %ld MB", memsize / (1024 * 1024));
365 T_LOG("Mapping variant: %s", variant_str[mapping_variant]);
366
367 snprintf(metric_str, 32, "Runtime-%s", variant_str[mapping_variant]);
368 runtime = dt_stat_time_create(metric_str);
d9a64523 369
0a7de745
A
370 while (!dt_stat_stable(runtime)) {
371 map_mem_regions(fault_type, mapping_variant, memsize);
d9a64523 372 execute_threads();
0a7de745
A
373 unmap_mem_regions(mapping_variant, memsize);
374 }
375
376 dt_stat_finalize(runtime);
377 T_LOG("Throughput-%s (MB/s): %lf\n\n", variant_str[mapping_variant], (double)memsize / (1024 * 1024) / dt_stat_mean((dt_stat_t)runtime));
378}
379
380static void
381setup_and_run_test(int fault_type, int threads)
382{
383 int i, mapping_variant;
384 size_t memsize;
385 char *e;
386
387 mapping_variant = VARIANT_DEFAULT;
388 memsize = MEMSIZE;
389 num_threads = threads;
390
391 if ((e = getenv("NTHREADS"))) {
392 if (threads == 1) {
393 T_SKIP("Custom environment variables specified. Skipping single threaded version.");
394 }
395 num_threads = (int)strtol(e, NULL, 0);
396 }
397
398 if ((e = getenv("MEMSIZEMB"))) {
399 memsize = (size_t)strtol(e, NULL, 0) * 1024 * 1024;
400 }
401
402 if ((e = getenv("VARIANT"))) {
403 mapping_variant = (int)strtol(e, NULL, 0);
404 run_test(fault_type, mapping_variant, memsize);
405 } else {
406 for (i = VARIANT_DEFAULT; i < NUM_MAPPING_VARIANTS; i++) {
407 run_test(fault_type, i, memsize);
408 }
d9a64523
A
409 }
410
d9a64523
A
411 T_END;
412}
413
0a7de745
A
414static int
415get_ncpu(void)
d9a64523
A
416{
417 int ncpu;
418 size_t length = sizeof(ncpu);
419
420 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("hw.ncpu", &ncpu, &length, NULL, 0),
0a7de745 421 "failed to query hw.ncpu");
d9a64523
A
422 return ncpu;
423}
424
425T_DECL(read_soft_fault,
0a7de745 426 "Read soft faults (single thread)")
d9a64523 427{
0a7de745 428 setup_and_run_test(SOFT_FAULT, 1);
d9a64523
A
429}
430
431T_DECL(read_soft_fault_multithreaded,
0a7de745 432 "Read soft faults (multi-threaded)")
d9a64523
A
433{
434 char *e;
435 int nthreads;
436
437 /* iOSMark passes in the no. of threads via an env. variable */
438 if ((e = getenv("DT_STAT_NTHREADS"))) {
439 nthreads = (int)strtol(e, NULL, 0);
440 } else {
441 nthreads = get_ncpu();
cb323159
A
442 if (nthreads == 1) {
443 T_SKIP("Skipping multi-threaded test on single core device.");
444 }
d9a64523 445 }
0a7de745 446 setup_and_run_test(SOFT_FAULT, nthreads);
d9a64523
A
447}
448
449T_DECL(zero_fill_fault,
0a7de745 450 "Zero fill faults (single thread)")
d9a64523 451{
0a7de745 452 setup_and_run_test(ZERO_FILL, 1);
d9a64523
A
453}
454
455T_DECL(zero_fill_fault_multithreaded,
0a7de745 456 "Zero fill faults (multi-threaded)")
d9a64523
A
457{
458 char *e;
459 int nthreads;
460
461 /* iOSMark passes in the no. of threads via an env. variable */
462 if ((e = getenv("DT_STAT_NTHREADS"))) {
463 nthreads = (int)strtol(e, NULL, 0);
464 } else {
465 nthreads = get_ncpu();
cb323159
A
466 if (nthreads == 1) {
467 T_SKIP("Skipping multi-threaded test on single core device.");
468 }
d9a64523 469 }
0a7de745 470 setup_and_run_test(ZERO_FILL, nthreads);
d9a64523 471}