Last active
January 15, 2025 22:18
-
-
Save kripken/3c98fa795d2d119a222d63eaa3c43360 to your computer and use it in GitHub Desktop.
Parallel Benchmarks
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Compile with e.g. | |
// | |
// ./emcc a.cpp -pthread -sPROXY_TO_PTHREAD -O3 -s INITIAL_MEMORY=1GB | |
// | |
#include <atomic> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <emscripten.h> | |
#include <emscripten/threading.h> | |
#define MAX_WORKERS 1024 | |
int WORKERS = 0; | |
double start; | |
pthread_t thread[MAX_WORKERS] = {}; | |
std::atomic<int> running = 0; | |
void do_math(int worker) { | |
// Do lots of pure math. | |
#define N 20 * 1024 | |
#define SHARE (N / WORKERS) | |
size_t x = 0; | |
for (int i = 0; i < SHARE; i++) { | |
for (int j = 0; j < SHARE; j++) { | |
x += 37; | |
x ^= (x + 2*x*x - 1024); | |
x *= x % 17; | |
x ^= (2*x + x*x*x - 99999); | |
} | |
} | |
printf("thread exiting with x %zu\n", x); | |
} | |
void do_mallocs(int worker) { | |
// Do lots of mallocs. | |
#define TOTAL 50000000 | |
#define AT_ONCE 1000 | |
#define BASE_SIZE 1000 | |
void* allocations[AT_ONCE]; | |
for (int i = 0; i < AT_ONCE; i++) { | |
allocations[i] = NULL; | |
} | |
for (int i = 0; i < (TOTAL / WORKERS); i++) { | |
int rem = i & (AT_ONCE - 1); | |
void*& allocation = allocations[rem]; | |
if (allocation) { | |
free(allocation); | |
} | |
allocation = malloc(BASE_SIZE + rem); | |
char* data = (char*)allocation; | |
*data = i; | |
} | |
int total = 0; | |
for (int i = 0; i < AT_ONCE; i++) { | |
void* allocation = allocations[i]; | |
char* data = (char*)allocation; | |
total += *data; | |
free(allocation); | |
} | |
printf("thread exiting with total %d\n", total); | |
} | |
void do_files(int worker) { | |
// Do lots of file writes. | |
#define ITERS (50 * 1024) | |
#define BUFFER (10 * 1024 * 1024) | |
#define CHUNK ( 1024) | |
char* buffer = (char*)malloc(BUFFER); | |
for (int i = 0; i < BUFFER; i++) { | |
buffer[i] = i * i; | |
} | |
// Use a different filename per thread, so in principle the operations can be | |
// done in parallel. | |
char filename[] = "aX.txt"; | |
filename[1] = '0' + worker; | |
int i; | |
for (i = 0; i < (ITERS / WORKERS); i++) { | |
size_t size = CHUNK; | |
size_t offset = (i*i) % (BUFFER - CHUNK); | |
if (!(i & 1)) { | |
// Write. | |
FILE* f = fopen(filename, "wb"); | |
if (!f) { | |
puts("bad file"); | |
abort(); | |
} | |
size_t written = fwrite(buffer + offset, 1, size, f); | |
if (written != size) { | |
puts("bad write"); | |
abort(); | |
} | |
fclose(f); | |
} else { | |
// Read. | |
FILE* f = fopen(filename, "rb"); | |
if (!f) { | |
puts("bad file"); | |
abort(); | |
} | |
size_t read = fread(buffer + offset, 1, size, f); | |
if (read != size) { | |
puts("bad read"); | |
abort(); | |
} | |
fclose(f); | |
} | |
} | |
free(buffer); | |
printf("thread exiting after %d iters\n", i); | |
} | |
void do_work(int worker) { | |
// Pick which of the 3 benchmarks. | |
//do_math(worker); | |
//do_mallocs(worker); | |
do_files(worker); | |
} | |
void *thread_main(void *arg) { | |
int worker = (int)arg; | |
puts("thread started"); | |
running++; | |
do_work(worker); | |
running--; | |
if (running == 0) { | |
double end = emscripten_date_now(); | |
printf("total time %.2f ms\n", end - start); | |
emscripten_force_exit(0); | |
} | |
pthread_exit(0); | |
} | |
void create_thread(int i) { | |
pthread_attr_t attr; | |
pthread_attr_init(&attr); | |
pthread_attr_setstacksize(&attr, 4*1024); | |
int rc = pthread_create(&thread[i], &attr, thread_main, (void*)i); | |
if (rc != 0 || thread[i] == 0) { | |
printf("Failed to create thread!\n"); | |
} | |
pthread_attr_destroy(&attr); | |
} | |
extern "C" | |
EMSCRIPTEN_KEEPALIVE void work(void* arg) { | |
WORKERS = (int)arg; | |
printf("workers: %d\n", WORKERS); | |
if (WORKERS == 0) { | |
// Run the code on the main thread. Set WORKERS to 1, as all the tasks can | |
// assume a single place will do all the work. | |
WORKERS = 1; | |
start = emscripten_date_now(); | |
do_work(0); | |
double end = emscripten_date_now(); | |
printf("total time %.2f ms\n", end - start); | |
return; | |
} | |
if (WORKERS > MAX_WORKERS) { | |
puts("bad workers"); | |
abort(); | |
} | |
for (int i = 0; i < WORKERS; ++i) { | |
create_thread(i); | |
} | |
start = emscripten_date_now(); | |
emscripten_exit_with_live_runtime(); | |
} | |
int main(int argc, char **argv) { | |
work((void*)atoi(argv[1])); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment