Files
darling-lol/tools/riot-platform-memory-bench.c
T
lucannez64andClaude Opus 5.5 4370f375cd Cut Darling overhead in League fights and add passive match capture
Game process (ARAM Hexgate: 84 -> 127 FPS average, slowest stretches ~110):
- Local pthread mutexes, condition variables and Mach semaphores are on by
  default in the game process only, with /tmp/rclol-no-local-* opt-outs.
  The main thread's darlingserver waits fell from 1.36 to 0.10 ms per frame.
- Local semaphores: the count and the slot generation change in one
  compare-exchange, a destroy leaves a negative marker, the table is reset
  after fork, and names the table did not create go to the server.
- Metal: drop identical re-binds (keyed on the Indium object, depth-stencil
  retained), pass blit resources by reference, and stop resetting vertex
  descriptors twice.
- Core isolation of the main thread is opt-in (/tmp/rclol-core-isolation);
  it lowered FPS in its only match.
- Optimized libc/libc++/Foundation overlays, AVX2 string primitives, real CPU
  topology and their installers and offline checks.

Tools and docs:
- tools/riot-match-capture: passive /proc sampler, user-space IP profiler and
  reports, plus a comparison against the game's netstats.
- tools/riot-lsem-model and tools/riot-metal-bind-model: host models that
  extract the shipped code and check it.
- tools/install-riot-metal-pair.sh installs Metal and Indium together.
- Status, setup, testing and troubleshooting docs updated with the measured
  matches and the switch table; manifest file counts match the patches.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-14 16:48:34 +02:00

232 lines
6.9 KiB
C

/*
* Host-side benchmark of the libplatform primitives against the host's glibc.
*
* The `current` copy is the code the installed libsystem_platform.dylib was
* built from (memmove.c/bzero.c/memset_pattern.c at -O2 -fno-builtin, the rest
* at -O0) and the `new` copy is what tools/build-riot-platform-strings.py
* produces (-O2 for every string source, plus the vector paths). Both are the
* same sources as the guest library; glibc is measured in the same process for
* scale, and is not what the guest runs.
*
* Sizes 1..256, then 512, 1 KiB, 4 KiB, 16 KiB, 64 KiB and 1 MiB; the source
* and destination advance by 32 bytes per iteration so every alignment mod 64
* is exercised. Every call is a direct call through a volatile function
* pointer and every result feeds a volatile sink, so nothing is folded away.
* This is a throughput benchmark, not an FPS claim.
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
uint64_t platform_test_capabilities;
#define DECLARE_VARIANT(prefix) \
extern void *prefix##_platform_memmove(void *, const void *, size_t); \
extern void *prefix##_platform_memset(void *, int, size_t); \
extern int prefix##_platform_memcmp(const void *, const void *, size_t); \
extern void *prefix##_platform_memchr(const void *, int, size_t); \
extern size_t prefix##_platform_strlen(const char *); \
extern int prefix##_platform_strcmp(const char *, const char *);
DECLARE_VARIANT(current)
DECLARE_VARIANT(new)
static volatile size_t sink;
static volatile size_t g_size;
static volatile unsigned g_iterations;
static double now_ns(void)
{
struct timespec time;
clock_gettime(CLOCK_MONOTONIC, &time);
return (double)time.tv_sec * 1e9 + (double)time.tv_nsec;
}
static unsigned iterations_for(size_t size)
{
size_t iterations = 4000000 / (size + 16);
if (iterations < 100)
iterations = 100;
if (iterations > 200000)
iterations = 200000;
return (unsigned)iterations;
}
static char *source;
static char *destination;
static size_t capacity = 4u << 20;
enum operation { MOVE, FILL, COMPARE, SEARCH, LENGTH, STRINGCOMPARE };
static const char *operation_names[] = { "memcpy", "memset", "memcmp", "memchr", "strlen", "strcmp" };
typedef void (*operation_function)(int which);
static void run_current(int which)
{
size_t size = g_size;
unsigned iterations = g_iterations;
unsigned index;
for (index = 0; index < iterations; index++) {
size_t offset = (index * 32) & 4095;
size_t i = (index & 1) ? offset : 0;
switch (which) {
case MOVE: sink = (size_t)current_platform_memmove(destination + i, source + i, size); break;
case FILL: sink = (size_t)current_platform_memset(destination + i, 0x5a, size); break;
case COMPARE: sink = (size_t)current_platform_memcmp(source + i, destination + i, size); break;
case SEARCH: sink = (size_t)current_platform_memchr(source + i, 0xa5, size); break;
case LENGTH: sink = current_platform_strlen(source + i); break;
default: sink = (size_t)current_platform_strcmp(source + i, destination + i); break;
}
}
}
static void run_new(int which)
{
size_t size = g_size;
unsigned iterations = g_iterations;
unsigned index;
for (index = 0; index < iterations; index++) {
size_t offset = (index * 32) & 4095;
size_t i = (index & 1) ? offset : 0;
switch (which) {
case MOVE: sink = (size_t)new_platform_memmove(destination + i, source + i, size); break;
case FILL: sink = (size_t)new_platform_memset(destination + i, 0x5a, size); break;
case COMPARE: sink = (size_t)new_platform_memcmp(source + i, destination + i, size); break;
case SEARCH: sink = (size_t)new_platform_memchr(source + i, 0xa5, size); break;
case LENGTH: sink = new_platform_strlen(source + i); break;
default: sink = (size_t)new_platform_strcmp(source + i, destination + i); break;
}
}
}
static void run_glibc(int which)
{
size_t size = g_size;
unsigned iterations = g_iterations;
unsigned index;
for (index = 0; index < iterations; index++) {
size_t offset = (index * 32) & 4095;
size_t i = (index & 1) ? offset : 0;
switch (which) {
case MOVE: sink = (size_t)memcpy(destination + i, source + i, size); break;
case FILL: sink = (size_t)memset(destination + i, 0x5a, size); break;
case COMPARE: sink = (size_t)memcmp(source + i, destination + i, size); break;
case SEARCH: sink = (size_t)memchr(source + i, 0xa5, size); break;
case LENGTH: sink = strlen(source + i); break;
default: sink = (size_t)strcmp(source + i, destination + i); break;
}
}
}
static double measure(operation_function function, int which)
{
double best = 1e30;
int round;
for (round = 0; round < 3; round++) {
double start, elapsed;
start = now_ns();
function(which);
elapsed = now_ns() - start;
if (elapsed < best)
best = elapsed;
}
return best / (double)g_iterations;
}
static void prepare_data(int which, size_t size)
{
size_t boundary;
switch (which) {
case COMPARE:
/* a difference at the end, so a compare scans the whole range */
memcpy(destination, source, capacity + 8192);
if (size != 0)
destination[size - 1] = (unsigned char)(source[size - 1] ^ 0x20);
break;
case SEARCH:
/* 0xa5 does not appear in the source */
memset(source, 'a', capacity + 8192);
break;
case LENGTH:
case STRINGCOMPARE:
/* the strings end every size+1 bytes, so the offset sweep stays valid */
memset(source, 'a', capacity + 8192);
memset(destination, 'a', capacity + 8192);
for (boundary = size + 1; boundary < capacity; boundary += size + 1) {
source[boundary] = 0;
destination[boundary] = 0;
}
break;
default:
break;
}
}
static void measure_size(size_t size)
{
int which;
g_size = size;
g_iterations = iterations_for(size);
memset(source, 'a', capacity + 8192);
memset(destination, 'a', capacity + 8192);
for (which = 0; which < 6; which++) {
double current, updated, glibc;
prepare_data(which, size);
current = measure(run_current, which);
prepare_data(which, size);
updated = measure(run_new, which);
prepare_data(which, size);
glibc = measure(run_glibc, which);
printf("BENCH size=%zu op=%s current_ns=%.2f new_ns=%.2f glibc_ns=%.2f current_vs_glibc=%.2f new_vs_glibc=%.2f\n",
size, operation_names[which], current, updated, glibc,
current / glibc, updated / glibc);
}
}
int main(int argc, char **argv)
{
size_t size;
int single = 0;
if (argc > 1 && strcmp(argv[1], "--no-avx2") == 0) {
platform_test_capabilities = 0;
single = 1;
} else {
platform_test_capabilities = 0x20000000 | 0x08000000;
}
(void)single;
source = malloc(capacity + 8192);
destination = malloc(capacity + 8192);
if (source == NULL || destination == NULL)
return 1;
memset(source, 'a', capacity + 8192);
memset(destination, 'a', capacity + 8192);
for (size = 1; size <= 256; size++)
measure_size(size);
measure_size(512);
measure_size(1024);
measure_size(4096);
measure_size(16384);
measure_size(65536);
measure_size(1048576);
return 0;
}