Feat: Add dummy PSF support workload diagnostics

Add an opt-in DUMMY_PSF_SUPPORT_STATS mode to the diagnostic-only dummy
consumer. It records per-chunk cache-limited 16px support references,
touched tiles, 256-reference tasks, maximum tile list and first-pass
counting time, then reports percentiles. No HDR is produced and no
references are materialized or uploaded.
This commit is contained in:
wyj committed 2026-09-14 19:15:53 -04:00
1 parent eec5dd215a
commit 4bf2de1b40
2 files changed
+128 -6

No files matched your search

+127 -5
View File
@@ -7,13 +7,16 @@
#include <stdlib.h>
#include <string.h>
enum { DUMMY_SELECTOR_TILE = 32 };
enum { DUMMY_SELECTOR_TILE = 32, DUMMY_SUPPORT_TILE = 16,
DUMMY_TASK_EVENTS = 256 };
typedef struct {
size_t worker, events, triangles, occupied_tiles;
double event_bbox_diagonal, triangle_bbox_diagonal;
double maximum_triangle_jump;
int adaptive_tile16;
size_t support_refs, support_tiles, support_tasks, maximum_tile_list;
double support_pass_seconds;
} DummyChunkSample;
struct DummyPsfSink {
@@ -21,6 +24,8 @@ struct DummyPsfSink {
size_t count, capacity, event_capacity;
size_t total_events;
int width, height, tiles_x, tiles_y;
int support_tiles_x, support_tiles_y, support_stats;
double max_radius_pixels;
int failed;
omp_lock_t lock;
};
@@ -28,6 +33,9 @@ struct DummyPsfSink {
struct DummyPsfChunk {
DummyPsfSink *sink;
uint32_t *tile_generation;
PsfCachedEvent *support_events;
uint32_t *support_counts, *support_touched;
size_t support_touched_count;
uint32_t generation;
size_t worker, events, triangles, occupied_tiles;
size_t last_triangle;
@@ -66,8 +74,9 @@ static int append_sample(DummyPsfSink *sink, DummyChunkSample sample) {
}
int dummy_psf_sink_create(DummyPsfSink **out, int width, int height,
size_t event_capacity) {
if (out == NULL || width <= 0 || height <= 0 || event_capacity == 0)
size_t event_capacity, double max_radius_pixels) {
if (out == NULL || width <= 0 || height <= 0 || event_capacity == 0 ||
!isfinite(max_radius_pixels) || max_radius_pixels < 0)
return -1;
DummyPsfSink *sink = calloc(1, sizeof *sink);
if (sink == NULL)
@@ -77,6 +86,14 @@ int dummy_psf_sink_create(DummyPsfSink **out, int width, int height,
sink->tiles_x = (width + DUMMY_SELECTOR_TILE - 1) / DUMMY_SELECTOR_TILE;
sink->tiles_y = (height + DUMMY_SELECTOR_TILE - 1) / DUMMY_SELECTOR_TILE;
sink->event_capacity = event_capacity;
sink->support_tiles_x = (width + DUMMY_SUPPORT_TILE - 1) / DUMMY_SUPPORT_TILE;
sink->support_tiles_y = (height + DUMMY_SUPPORT_TILE - 1) / DUMMY_SUPPORT_TILE;
sink->max_radius_pixels = max_radius_pixels;
const char *support = getenv("DUMMY_PSF_SUPPORT_STATS");
if (support && strcmp(support, "0") && strcmp(support, "1")) {
free(sink); return -1;
}
sink->support_stats = support && !strcmp(support, "1");
omp_init_lock(&sink->lock);
*out = sink;
return 0;
@@ -89,13 +106,24 @@ int dummy_psf_chunk_create(DummyPsfChunk **out, DummyPsfSink *sink,
DummyPsfChunk *chunk = calloc(1, sizeof *chunk);
if (chunk == NULL)
return -1;
chunk->sink = sink;
const size_t tile_count = (size_t)sink->tiles_x * sink->tiles_y;
chunk->tile_generation = calloc(tile_count, sizeof *chunk->tile_generation);
if (chunk->tile_generation == NULL) {
free(chunk);
return -1;
}
chunk->sink = sink;
if (sink->support_stats) {
const size_t count = (size_t)sink->support_tiles_x * sink->support_tiles_y;
chunk->support_events = malloc(sink->event_capacity * sizeof *chunk->support_events);
chunk->support_counts = calloc(count, sizeof *chunk->support_counts);
chunk->support_touched = malloc(count * sizeof *chunk->support_touched);
if (!chunk->support_events || !chunk->support_counts ||
!chunk->support_touched) {
dummy_psf_chunk_destroy(chunk);
return -1;
}
}
chunk->worker = worker_id;
chunk->generation = 1;
*out = chunk;
@@ -127,6 +155,40 @@ int dummy_psf_chunk_flush(DummyPsfChunk *chunk) {
const double triangle_diagonal =
hypot(chunk->triangle_max_x - chunk->triangle_min_x,
chunk->triangle_max_y - chunk->triangle_min_y);
size_t support_refs = 0, support_tasks = 0, maximum_tile_list = 0;
double support_pass_seconds = 0.0;
if (chunk->sink->support_stats) {
const double start = omp_get_wtime();
for (size_t i = 0; i < chunk->support_touched_count; ++i)
chunk->support_counts[chunk->support_touched[i]] = 0;
chunk->support_touched_count = 0;
const int tiles_x = chunk->sink->support_tiles_x;
for (size_t i = 0; i < chunk->events; ++i) {
const PsfCachedEvent *event = &chunk->support_events[i];
const int support = (int)fmin(ceil(event->support_radius),
chunk->sink->max_radius_pixels);
const int bx = (int)floor(event->x), by = (int)floor(event->y);
const int x0 = fmax(0, bx - support);
const int x1 = fmin(chunk->sink->width - 1, bx + support);
const int y0 = fmax(0, by - support);
const int y1 = fmin(chunk->sink->height - 1, by + support);
if (x0 > x1 || y0 > y1) continue;
for (int y = y0 / DUMMY_SUPPORT_TILE; y <= y1 / DUMMY_SUPPORT_TILE; ++y)
for (int x = x0 / DUMMY_SUPPORT_TILE; x <= x1 / DUMMY_SUPPORT_TILE; ++x) {
const size_t tile = (size_t)y * tiles_x + x;
if (!chunk->support_counts[tile])
chunk->support_touched[chunk->support_touched_count++] = tile;
++chunk->support_counts[tile];
++support_refs;
}
}
for (size_t i = 0; i < chunk->support_touched_count; ++i) {
const size_t count = chunk->support_counts[chunk->support_touched[i]];
if (count > maximum_tile_list) maximum_tile_list = count;
support_tasks += (count + DUMMY_TASK_EVENTS - 1) / DUMMY_TASK_EVENTS;
}
support_pass_seconds = omp_get_wtime() - start;
}
const DummyChunkSample sample = {
.worker = chunk->worker,
.events = chunk->events,
@@ -136,7 +198,12 @@ int dummy_psf_chunk_flush(DummyPsfChunk *chunk) {
.triangle_bbox_diagonal = triangle_diagonal,
.maximum_triangle_jump = chunk->maximum_triangle_jump,
.adaptive_tile16 = chunk->events >= 8192 && chunk->occupied_tiles != 0 &&
chunk->events / chunk->occupied_tiles >= 32};
chunk->events / chunk->occupied_tiles >= 32,
.support_refs = support_refs,
.support_tiles = chunk->support_touched_count,
.support_tasks = support_tasks,
.maximum_tile_list = maximum_tile_list,
.support_pass_seconds = support_pass_seconds};
const int result = append_sample(chunk->sink, sample);
reset_chunk(chunk);
return result;
@@ -150,6 +217,8 @@ int dummy_psf_chunk_emit(DummyPsfChunk *chunk, const PsfCachedEvent *event,
if (chunk->events == chunk->sink->event_capacity &&
dummy_psf_chunk_flush(chunk))
return -1;
if (chunk->sink->support_stats)
chunk->support_events[chunk->events] = *event;
if (!chunk->have_event) {
chunk->event_min_x = chunk->event_max_x = event->x;
@@ -204,6 +273,9 @@ void dummy_psf_chunk_destroy(DummyPsfChunk *chunk) {
return;
(void)dummy_psf_chunk_flush(chunk);
free(chunk->tile_generation);
free(chunk->support_events);
free(chunk->support_counts);
free(chunk->support_touched);
free(chunk);
}
@@ -284,6 +356,51 @@ static void report_group(const DummyPsfSink *sink, int full_only,
free(events);
}
static void report_support_group(const DummyPsfSink *sink, int full_only,
const char *label) {
size_t count = 0, total_refs = 0, total_tasks = 0;
double total_pass = 0.0;
for (size_t i = 0; i < sink->count; ++i)
if (!full_only || sink->samples[i].events == sink->event_capacity)
++count;
if (!count || count > SIZE_MAX / (5 * sizeof(double))) return;
double *values = malloc(5 * count * sizeof *values);
if (!values) { fputs("Dummy PSF support report allocation failed\n", stderr); return; }
const char *names[] = {"support_16px_references", "support_16px_tiles",
"support_16px_tasks", "maximum_tile_list",
"support_first_pass_ms"};
size_t next = 0;
for (size_t i = 0; i < sink->count; ++i) {
const DummyChunkSample *sample = &sink->samples[i];
if (full_only && sample->events != sink->event_capacity) continue;
values[next] = sample->support_refs;
values[count + next] = sample->support_tiles;
values[2 * count + next] = sample->support_tasks;
values[3 * count + next] = sample->maximum_tile_list;
values[4 * count + next] = 1000.0 * sample->support_pass_seconds;
total_refs += sample->support_refs;
total_tasks += sample->support_tasks;
total_pass += sample->support_pass_seconds;
++next;
}
for (size_t field = 0; field < 5; ++field) {
double *series = values + field * count;
qsort(series, count, sizeof *series, compare_double);
fprintf(stderr,
"Dummy PSF %s %s: min=%.3f p10=%.3f p25=%.3f p50=%.3f "
"p75=%.3f p90=%.3f p99=%.3f max=%.3f\n",
label, names[field], series[0],
percentile(series, count, 0.10), percentile(series, count, 0.25),
percentile(series, count, 0.50), percentile(series, count, 0.75),
percentile(series, count, 0.90), percentile(series, count, 0.99),
series[count - 1]);
}
fprintf(stderr,
"Dummy PSF %s support totals: references=%zu tasks=%zu summed_first_pass=%.3f s\n",
label, total_refs, total_tasks, total_pass);
free(values);
}
void dummy_psf_sink_report(const DummyPsfSink *sink) {
if (sink == NULL)
return;
@@ -298,6 +415,11 @@ void dummy_psf_sink_report(const DummyPsfSink *sink) {
sink->event_capacity, DUMMY_SELECTOR_TILE);
report_group(sink, 0, "all");
report_group(sink, 1, "full");
if (sink->support_stats) {
fputs("Dummy PSF support diagnostic: 16px rectangular cache-limited first pass only; no references were materialized or uploaded.\n", stderr);
report_support_group(sink, 0, "all");
report_support_group(sink, 1, "full");
}
}
void dummy_psf_sink_destroy(DummyPsfSink *sink) {
+1 -1
View File
@@ -11,7 +11,7 @@ typedef struct DummyPsfChunk DummyPsfChunk;
/* Diagnostic-only consumer for the production PSF event stream. It preserves
* per-worker chunk boundaries but never accumulates or writes an HDR image. */
int dummy_psf_sink_create(DummyPsfSink **out, int width, int height,
size_t event_capacity);
size_t event_capacity, double max_radius_pixels);
int dummy_psf_chunk_create(DummyPsfChunk **out, DummyPsfSink *sink,
size_t worker_id);
int dummy_psf_chunk_emit(DummyPsfChunk *chunk, const PsfCachedEvent *event,