Skip to content

Instantly share code, notes, and snippets.

@kripken
Last active January 15, 2025 22:18
Show Gist options
  • Save kripken/3c98fa795d2d119a222d63eaa3c43360 to your computer and use it in GitHub Desktop.
Save kripken/3c98fa795d2d119a222d63eaa3c43360 to your computer and use it in GitHub Desktop.
Parallel Benchmarks
// 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