├── 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 | --------------------------------------------------------------------------------