├── README.md
├── UNLICENSE
└── src
├── common
├── meson.build
├── time.c
└── time.h
├── create_files.c
├── create_processes.c
├── create_threads.c
├── launch_programs.c
├── mem_alloc.c
└── meson.build
/README.md:
--------------------------------------------------------------------------------
1 | ## This repo has moved to: https://gitlab.com/mbitsnbites/osbench
2 |
3 | # OSBench
4 |
5 | This is a collection of benchmarks that aim to measure the performance of operating system primitives, such as process and thread creation.
6 |
7 | ## Building
8 |
9 | To build the benchmarks you need a C compiler, [meson](http://mesonbuild.com/) and [ninja](https://ninja-build.org/).
10 |
11 | ```bash
12 | mkdir out
13 | cd out
14 | meson --buildtype=release ../src
15 | ninja
16 | ```
17 |
18 | ## Micro benchmarks
19 |
20 | All benchmarks run their work for five seconds, and the fastest pass is reported.
21 |
22 | ### create_threads
23 |
24 | Create 100 threads and wait for them to finish. Each thread does nothing (just return).
25 |
26 | * POSIX: `pthread_create()`, `pthread_join()`
27 | * WIN32: `_beginthreadex()`, `WaitForSingleObject()`, `CloseHandle()`
28 |
29 | ### create_processes
30 |
31 | Create 100 processes and wait for them to finish. Each process does nothing (just exit).
32 |
33 | * Unix: `fork()`, `waitpid()`
34 | * WIN32: Not implemented (Windows lacks the corresponding functionality).
35 |
36 | ### launch_programs
37 |
38 | Launch 100 programs and wait for them to finish. Each program does nothing (just exit).
39 |
40 | * Unix: `fork()`, `execlp()`, `waitpid()`
41 | * WIN32: `CreateProcess()`, `WaitForSingleObject()`, `CloseHandle()`
42 |
43 | ### create_files
44 |
45 | Create 65,534 files, write 32 byts of data to each file, and then delete them.
46 |
47 | * `fopen()`, `fwrite()`, `fclose()`, `remove()`
48 |
49 | NOTE: To measure filesystem/kernel performance rather than storage medium performance, consider using a RAM disk.
50 |
51 | ### mem_alloc
52 |
53 | Allocate 1,000,000 small chunks of memory, and then free them. Each chunk is 4-128 bytes in size.
54 |
55 | * `malloc()`, `free()`
56 |
57 |
--------------------------------------------------------------------------------
/UNLICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/src/common/meson.build:
--------------------------------------------------------------------------------
1 | # This is free and unencumbered software released into the public domain.
2 | # For more information, see UNLICENSE.
3 |
4 | common_src = [
5 | 'time.c',
6 | 'time.h'
7 | ]
8 | common_lib = static_library(
9 | 'common',
10 | common_src,
11 | include_directories: [root_inc]
12 | )
13 | common_dep = declare_dependency(link_with: common_lib)
14 |
15 |
--------------------------------------------------------------------------------
/src/common/time.c:
--------------------------------------------------------------------------------
1 | // This is free and unencumbered software released into the public domain.
2 | // For more information, see UNLICENSE.
3 |
4 | #include "common/time.h"
5 |
6 | #if defined(_WIN32)
7 | #define WIN32_LEAN_AND_MEAN
8 | #include
9 | #else
10 | #include
11 | #endif
12 |
13 | #if defined(_WIN32)
14 | static LARGE_INTEGER s_start;
15 | static double s_period = 0.0;
16 | #endif
17 |
18 | double get_time() {
19 | double result;
20 | #if defined(_WIN32)
21 | LARGE_INTEGER count;
22 | if (s_period == 0.0) {
23 | LARGE_INTEGER frequency;
24 | if (QueryPerformanceFrequency(&frequency)) {
25 | s_period = 1.0 / (double)frequency.QuadPart;
26 | QueryPerformanceCounter(&s_start);
27 | }
28 | }
29 | if (QueryPerformanceCounter(&count)) {
30 | result = (count.QuadPart - s_start.QuadPart) * s_period;
31 | } else {
32 | result = 0.0;
33 | }
34 | #else
35 | struct timeval tv;
36 | if (gettimeofday(&tv, (struct timezone *)0) == 0) {
37 | result = ((double)tv.tv_sec) + 0.000001 * (double)tv.tv_usec;
38 | } else {
39 | result = 0.0;
40 | }
41 | #endif
42 | return result;
43 | }
44 |
45 |
--------------------------------------------------------------------------------
/src/common/time.h:
--------------------------------------------------------------------------------
1 | // This is free and unencumbered software released into the public domain.
2 | // For more information, see UNLICENSE.
3 |
4 | #ifndef COMMON_TIME_H_
5 | #define COMMON_TIME_H_
6 |
7 | double get_time();
8 |
9 | #endif // COMMON_TIME_H_
10 |
11 |
--------------------------------------------------------------------------------
/src/create_files.c:
--------------------------------------------------------------------------------
1 | // This is free and unencumbered software released into the public domain.
2 | // For more information, see UNLICENSE.
3 |
4 | #include "common/time.h"
5 |
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | static const double BENCHMARK_TIME = 5.0;
12 |
13 | // Note: The maximum number of files in a folder for different file systems:
14 | // - 65534 (FAT32)
15 | // - 4294967295 (NTFS, ext4)
16 | static const int NUM_FILES = 65534;
17 |
18 | static double my_log2(double x) {
19 | static const double LOG2SCALE = 1.442695040888963; // 1.0 / log(2.0);
20 | return log(x) * LOG2SCALE;
21 | }
22 |
23 | static int num_hex_chars(int max_int) {
24 | int num_bits = (int)ceil(my_log2((double)max_int));
25 | return (num_bits + 3) / 4;
26 | }
27 |
28 | static char path_separator() {
29 | #if defined(_WIN32)
30 | return '\\';
31 | #else
32 | return '/';
33 | #endif
34 | }
35 |
36 | static void to_hex(int x, char* str, const int str_len) {
37 | static const char TOHEX[16] = {
38 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
39 | };
40 |
41 | str += str_len - 1;
42 | for (int i = 0; i < str_len; ++i) {
43 | *str-- = TOHEX[x & 15];
44 | x = x >> 4;
45 | }
46 | }
47 |
48 | static void create_file(const char* file_name) {
49 | static const char FILE_DATA[32] = {
50 | 0, 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,
51 | 26, 27, 28, 29, 30, 31
52 | };
53 |
54 | FILE *f = fopen(file_name, "wb");
55 | if (!f) {
56 | fprintf(stderr, "*** Unable to create file \"%s\"\n", file_name);
57 | exit(1);
58 | }
59 | fwrite(FILE_DATA, 1, sizeof(FILE_DATA), f);
60 | fclose(f);
61 | }
62 |
63 | static void delete_file(const char* file_name) {
64 | remove(file_name);
65 | }
66 |
67 | int main(int argc, const char** argv) {
68 | if (argc != 2) {
69 | printf("Usage: %s root-folder\n", argv[0]);
70 | exit(1);
71 | }
72 |
73 | printf("Benchmark: Create/delete %d files...\n", NUM_FILES);
74 | fflush(stdout);
75 |
76 | // Create a path string.
77 | int hex_len = num_hex_chars(NUM_FILES - 1);
78 | size_t root_path_len = strlen(argv[1]);
79 | size_t path_len = root_path_len + 1 + hex_len;
80 | char* file_name = (char*)malloc(path_len + 1);
81 | if (!file_name) {
82 | fprintf(stderr, "*** Out of memory!\n");
83 | exit(1);
84 | }
85 | strncpy(file_name, argv[1], root_path_len);
86 | file_name[root_path_len] = path_separator();
87 | file_name[path_len] = 0;
88 |
89 | double best_time = 1e9;
90 | const double start_t = get_time();
91 | while (get_time() - start_t < BENCHMARK_TIME) {
92 | const double t0 = get_time();
93 |
94 | for (int file_no = 0; file_no < NUM_FILES; ++file_no) {
95 | // Construct the file name for this file.
96 | to_hex(file_no, &file_name[root_path_len + 1], hex_len);
97 |
98 | // Create the file.
99 | create_file(file_name);
100 | }
101 |
102 | for (int file_no = 0; file_no < NUM_FILES; ++file_no) {
103 | // Construct the file name for this file.
104 | to_hex(file_no, &file_name[root_path_len + 1], hex_len);
105 |
106 | // Delete the file.
107 | delete_file(file_name);
108 | }
109 |
110 | double dt = get_time() - t0;
111 | if (dt < best_time) {
112 | best_time = dt;
113 | }
114 | }
115 |
116 | printf("%f us / file\n", (best_time / (double)NUM_FILES) * 1000000.0);
117 | fflush(stdout);
118 |
119 | free((void*)file_name);
120 |
121 | return 0;
122 | }
123 |
124 |
--------------------------------------------------------------------------------
/src/create_processes.c:
--------------------------------------------------------------------------------
1 | // This is free and unencumbered software released into the public domain.
2 | // For more information, see UNLICENSE.
3 |
4 | #include "common/time.h"
5 |
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 | static const double BENCHMARK_TIME = 5.0;
13 | static const int NUM_PROCESSES = 100;
14 |
15 | int main() {
16 | printf("Benchmark: Create/teardown of %d processes...\n", NUM_PROCESSES);
17 | fflush(stdout);
18 |
19 | double best_time = 1e9;
20 | const double start_t = get_time();
21 | while (get_time() - start_t < BENCHMARK_TIME) {
22 | pid_t processes[NUM_PROCESSES];
23 | const double t0 = get_time();
24 |
25 | // Create all the processes.
26 | for (int i = 0; i < NUM_PROCESSES; ++i) {
27 | pid_t pid = fork();
28 | if (pid == 0) {
29 | exit(0);
30 | } else if (pid > 0) {
31 | processes[i] = pid;
32 | } else {
33 | fprintf(stderr, "*** Unable to create process no. %d\n", i);
34 | exit(1);
35 | }
36 | }
37 |
38 | // Wait for all child processes to terminate.
39 | for (int i = 0; i < NUM_PROCESSES; ++i) {
40 | waitpid(processes[i], (int*)0, 0);
41 | }
42 |
43 | double dt = get_time() - t0;
44 | if (dt < best_time) {
45 | best_time = dt;
46 | }
47 | }
48 |
49 | printf("%f us / process\n", (best_time / (double)NUM_PROCESSES) * 1000000.0);
50 | fflush(stdout);
51 |
52 | return 0;
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/src/create_threads.c:
--------------------------------------------------------------------------------
1 | // This is free and unencumbered software released into the public domain.
2 | // For more information, see UNLICENSE.
3 |
4 | #include "common/time.h"
5 |
6 | #include
7 |
8 | #if defined(_WIN32)
9 | #define WIN32_LEAN_AND_MEAN
10 | #include
11 | #include
12 | #else
13 | #include
14 | #endif
15 |
16 | #if defined(_WIN32)
17 |
18 | // WIN32 thread implementation.
19 | typedef HANDLE thread_t;
20 |
21 | static unsigned WINAPI thread_fun(void* arg) {
22 | // We do nothing here...
23 | (void)arg;
24 | return 0u;
25 | }
26 |
27 | static thread_t create_thread() {
28 | return (HANDLE)_beginthreadex((void*)0, 0, thread_fun, (void*)0, 0, (unsigned*)0);
29 | }
30 |
31 | static void join_thread(thread_t thread) {
32 | if (WaitForSingleObject(thread, INFINITE) != WAIT_FAILED) {
33 | CloseHandle(thread);
34 | }
35 | }
36 |
37 | #else
38 |
39 | // POSIX thread implementation.
40 | typedef pthread_t thread_t;
41 |
42 | static void* thread_fun(void* arg) {
43 | // We do nothing here...
44 | (void)arg;
45 | return (void*)0;
46 | }
47 |
48 | static thread_t create_thread() {
49 | thread_t result;
50 | pthread_create(&result, (const pthread_attr_t*)0, thread_fun, (void*)0);
51 | return result;
52 | }
53 |
54 | static void join_thread(thread_t thread) {
55 | pthread_join(thread, (void**)0);
56 | }
57 |
58 | #endif // WIN32
59 |
60 | #define NUM_THREADS 100
61 |
62 | static const double BENCHMARK_TIME = 5.0;
63 |
64 | int main() {
65 | printf("Benchmark: Create/teardown of %d threads...\n", NUM_THREADS);
66 | fflush(stdout);
67 |
68 | double best_time = 1e9;
69 | const double start_t = get_time();
70 | while (get_time() - start_t < BENCHMARK_TIME) {
71 | thread_t threads[NUM_THREADS];
72 | const double t0 = get_time();
73 |
74 | // Create all the child threads.
75 | for (int i = 0; i < NUM_THREADS; ++i) {
76 | threads[i] = create_thread();
77 | }
78 |
79 | // Wait for all the child threads to finish.
80 | for (int i = 0; i < NUM_THREADS; ++i) {
81 | join_thread(threads[i]);
82 | }
83 |
84 | double dt = get_time() - t0;
85 | if (dt < best_time) {
86 | best_time = dt;
87 | }
88 | }
89 |
90 | printf("%f us / thread\n", (best_time / (double)NUM_THREADS) * 1000000.0);
91 | fflush(stdout);
92 |
93 | return 0;
94 | }
95 |
96 |
--------------------------------------------------------------------------------
/src/launch_programs.c:
--------------------------------------------------------------------------------
1 | // This is free and unencumbered software released into the public domain.
2 | // For more information, see UNLICENSE.
3 |
4 | #include "common/time.h"
5 |
6 | #include
7 | #include
8 |
9 | #if defined(_WIN32)
10 |
11 | // Windows implementation.
12 | #define WIN32_LEAN_AND_MEAN
13 | #include
14 | #include
15 |
16 | typedef struct {
17 | HANDLE hProcess;
18 | HANDLE hThread;
19 | } process_t;
20 |
21 | static process_t create_process(const char* arg0, int process_no) {
22 | // The program name is the first argument to the parent process.
23 | const char* prg_name = arg0;
24 |
25 | // Construct a valid command line.
26 | // TODO(m): We could do this outside of the benchmark loop.
27 | size_t prg_name_len = strlen(prg_name);
28 | char* cmd_line = (char*)malloc(prg_name_len + 5);
29 | if (!cmd_line) {
30 | fprintf(stderr, "*** Out of memory (process #%d)\n", process_no);
31 | exit(1);
32 | }
33 | strncpy(&cmd_line[1], prg_name, prg_name_len);
34 | cmd_line[0] = '"';
35 | cmd_line[prg_name_len + 1] = '"';
36 | cmd_line[prg_name_len + 2] = ' ';
37 | cmd_line[prg_name_len + 3] = '-';
38 | cmd_line[prg_name_len + 4] = 0;
39 |
40 | // Start the child process.
41 | STARTUPINFO startup_info;
42 | memset(&startup_info, 0, sizeof(STARTUPINFO));
43 | startup_info.cb = sizeof(STARTUPINFO);
44 | PROCESS_INFORMATION process_info;
45 | if (!CreateProcess(prg_name, cmd_line, NULL, NULL, TRUE, 0, NULL, NULL, &startup_info, &process_info)) {
46 | fprintf(stderr, "*** Unable to create process no. %d\n", process_no);
47 | exit(1);
48 | }
49 |
50 | free((void*)cmd_line);
51 | return (process_t){ process_info.hProcess, process_info.hThread };
52 | }
53 |
54 | static void wait_process(process_t pid) {
55 | WaitForSingleObject(pid.hProcess, INFINITE);
56 | CloseHandle(pid.hProcess);
57 | CloseHandle(pid.hThread);
58 | }
59 |
60 | #else
61 |
62 | // Unix implementation.
63 | #include
64 | #include
65 | #include
66 |
67 | typedef pid_t process_t;
68 |
69 | static process_t create_process(const char* arg0, int process_no) {
70 | pid_t pid = fork();
71 | if (pid == 0) {
72 | execlp(arg0, arg0, "-", (char*)0);
73 | } else if (pid < 0) {
74 | fprintf(stderr, "*** Unable to create process no. %d\n", process_no);
75 | exit(1);
76 | }
77 | return (process_t)pid;
78 | }
79 |
80 | static void wait_process(process_t pid) {
81 | waitpid(pid, (int*)0, 0);
82 | }
83 |
84 | #endif
85 |
86 | #define NUM_PROGRAMS 100
87 |
88 | static const double BENCHMARK_TIME = 5.0;
89 |
90 | int main(int argc, char** argv) {
91 | // When called as a child-process, the first argument is defined.
92 | if (argc > 1) {
93 | exit(0);
94 | }
95 |
96 | printf("Benchmark: Launch %d programs...\n", NUM_PROGRAMS);
97 | fflush(stdout);
98 |
99 | double best_time = 1e9;
100 | const double start_t = get_time();
101 | while (get_time() - start_t < BENCHMARK_TIME) {
102 | process_t processes[NUM_PROGRAMS];
103 | const double t0 = get_time();
104 |
105 | // Create all the processes.
106 | for (int i = 0; i < NUM_PROGRAMS; ++i) {
107 | processes[i] = create_process(argv[0], i);
108 | }
109 |
110 | // Wait for all child processes to terminate.
111 | for (int i = 0; i < NUM_PROGRAMS; ++i) {
112 | wait_process(processes[i]);
113 | }
114 |
115 | double dt = get_time() - t0;
116 | if (dt < best_time) {
117 | best_time = dt;
118 | }
119 | }
120 |
121 | printf("%f us / program\n", (best_time / (double)NUM_PROGRAMS) * 1000000.0);
122 | fflush(stdout);
123 |
124 | return 0;
125 | }
126 |
127 |
--------------------------------------------------------------------------------
/src/mem_alloc.c:
--------------------------------------------------------------------------------
1 | // This is free and unencumbered software released into the public domain.
2 | // For more information, see UNLICENSE.
3 |
4 | #include "common/time.h"
5 |
6 | #include
7 | #include
8 |
9 | static const double BENCHMARK_TIME = 5.0;
10 | #define NUM_ALLOCS 1000000
11 |
12 | static void* s_addresses[NUM_ALLOCS];
13 |
14 | int main(int argc, const char** argv) {
15 | (void)argc;
16 | (void)argv;
17 | printf("Benchmark: Allocate/free %d memory chunks (4-128 bytes)...\n", NUM_ALLOCS);
18 | fflush(stdout);
19 |
20 | double best_time = 1e9;
21 | const double start_t = get_time();
22 | while (get_time() - start_t < BENCHMARK_TIME) {
23 | const double t0 = get_time();
24 |
25 | for (int i = 0; i < NUM_ALLOCS; ++i) {
26 | const size_t memory_size = ((i % 32) + 1) * 4;
27 | s_addresses[i] = malloc(memory_size);
28 | ((char*)s_addresses[i])[0] = 1;
29 | }
30 |
31 | for (int i = 0; i < NUM_ALLOCS; ++i) {
32 | free(s_addresses[i]);
33 | }
34 |
35 | double dt = get_time() - t0;
36 | if (dt < best_time) {
37 | best_time = dt;
38 | }
39 | }
40 |
41 | printf("%f ns / alloc\n", (best_time / (double)NUM_ALLOCS) * 1000000000.0);
42 | fflush(stdout);
43 |
44 | return 0;
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/src/meson.build:
--------------------------------------------------------------------------------
1 | # This is free and unencumbered software released into the public domain.
2 | # For more information, see UNLICENSE.
3 |
4 | project('osbench', ['c'])
5 |
6 | # All includes are relative to the root directory.
7 | root_inc = include_directories('.')
8 |
9 | # Common functions for all benchmarks.
10 | subdir('common')
11 |
12 | # We need a threading library.
13 | thread_dep = dependency('threads', required: true)
14 |
15 | # We need the math library on some systems.
16 | cc = meson.get_compiler('c')
17 | m_dep = cc.find_library('m', required : false)
18 |
19 | # Threading benchmark.
20 | create_threads_exe = executable(
21 | 'create_threads',
22 | ['create_threads.c'],
23 | include_directories: [root_inc],
24 | dependencies: [
25 | common_dep,
26 | thread_dep
27 | ]
28 | )
29 |
30 | # Process creation benchmark.
31 | if target_machine.system() != 'windows'
32 | create_processes_exe = executable(
33 | 'create_processes',
34 | ['create_processes.c'],
35 | include_directories: [root_inc],
36 | dependencies: [
37 | common_dep
38 | ]
39 | )
40 | endif
41 |
42 | # Launch programs benchmark.
43 | launch_programs_exe = executable(
44 | 'launch_programs',
45 | ['launch_programs.c'],
46 | include_directories: [root_inc],
47 | dependencies: [
48 | common_dep
49 | ]
50 | )
51 |
52 | # File creation benchmark.
53 | create_files_exe = executable(
54 | 'create_files',
55 | ['create_files.c'],
56 | include_directories: [root_inc],
57 | dependencies: [
58 | common_dep,
59 | m_dep
60 | ]
61 | )
62 |
63 | # Memory allocation benchmark.
64 | mem_alloc_exe = executable(
65 | 'mem_alloc',
66 | ['mem_alloc.c'],
67 | include_directories: [root_inc],
68 | dependencies: [
69 | common_dep
70 | ]
71 | )
72 |
73 |
--------------------------------------------------------------------------------