From 457c0728b4feaf1f579a6fe493b1feaa56fba6a6 Mon Sep 17 00:00:00 2001 From: Yingjie Wang Date: Thu, 27 Aug 2026 03:53:45 -0400 Subject: [PATCH] Catalog: parallelize all-sky tile prefetch --- Makefile | 7 +- assets/2mass/processed/all_sky/README.md | 39 ++++- src/catalog.c | 198 ++++++++++++++++++++--- src/catalog.h | 27 +++- src/frame.c | 30 ++-- src/frame.h | 4 +- src/main.c | 35 +++- tests/data/minkowski_rotation_2s.csv | 7 + tests/test_catalog_prefetch.c | 71 ++++++++ tests/test_frame.c | 8 +- 10 files changed, 371 insertions(+), 55 deletions(-) create mode 100644 tests/data/minkowski_rotation_2s.csv create mode 100644 tests/test_catalog_prefetch.c diff --git a/Makefile b/Makefile index e38a24f..fc7dc5e 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ TEST_TARGET := build/test_geodesic FRAME_TEST_TARGET := build/test_frame SCHWARZSCHILD_TEST_TARGET := build/test_schwarzschild OBSERVER_TRACK_TEST_TARGET := build/test_observer_track +CATALOG_PREFETCH_TEST_TARGET := build/test_catalog_prefetch .PHONY: all clean run test minkowski schwarzschild @@ -59,11 +60,15 @@ $(SCHWARZSCHILD_TEST_TARGET): tests/test_schwarzschild.c $(COMMON_SOURCES) src/s $(OBSERVER_TRACK_TEST_TARGET): tests/test_observer_track.c $(COMMON_SOURCES) | build $(CC) $(CPPFLAGS) $(CFLAGS) $(OPENMP_FLAGS) -Isrc $^ $(LDLIBS) -o $@ -test: $(TEST_TARGET) $(FRAME_TEST_TARGET) $(SCHWARZSCHILD_TEST_TARGET) $(OBSERVER_TRACK_TEST_TARGET) +$(CATALOG_PREFETCH_TEST_TARGET): tests/test_catalog_prefetch.c $(CORE_MINKOWSKI_SOURCES) | build + $(CC) $(CPPFLAGS) $(CFLAGS) $(OPENMP_FLAGS) -Isrc $^ $(LDLIBS) -o $@ + +test: $(TEST_TARGET) $(FRAME_TEST_TARGET) $(SCHWARZSCHILD_TEST_TARGET) $(OBSERVER_TRACK_TEST_TARGET) $(CATALOG_PREFETCH_TEST_TARGET) ./$(TEST_TARGET) ./$(FRAME_TEST_TARGET) ./$(SCHWARZSCHILD_TEST_TARGET) ./$(OBSERVER_TRACK_TEST_TARGET) + ./$(CATALOG_PREFETCH_TEST_TARGET) clean: rm -rf build diff --git a/assets/2mass/processed/all_sky/README.md b/assets/2mass/processed/all_sky/README.md index 4894990..85c6ee5 100644 --- a/assets/2mass/processed/all_sky/README.md +++ b/assets/2mass/processed/all_sky/README.md @@ -46,7 +46,44 @@ make ENABLE_PNG=1 SPACETIME=minkowski all ``` The renderer lazily loads only tiles touched by source triangles in the frame. -It prefetches them serially, so OpenMP splat workers read immutable tile data. +It first collects and de-duplicates the frame's touched tile IDs on the main +thread. Newly needed CSVs are then read by a bounded OpenMP loader group +(`--catalog-load-workers N`, default `4`); each worker owns distinct tile +files. The main thread commits their results to the cache only after that group +finishes, so OpenMP splat workers always read immutable tile data and never +race catalog I/O. A tile already loaded or previously found absent is not read +again during the same renderer run. The cache deliberately remains live for +the entire movie: final numerical-BBH renders commonly sweep across the full +sky, so tile eviction would trade the normal case for repeated filesystem I/O. + +Each frame reports the number of requested/newly loaded/unavailable tiles, +newly loaded stars, and CSV-load wall time. The loader count is deliberately +independent of the splat worker count: start at the default and benchmark on +the target storage before raising it. + +Warm-cache reference measurement, 2026-08-27, on the available partial +all-sky directory (18 CSV tiles, 12,917 stars, 128x72, 2-degree field): + +| Loader workers | CSV load time | +| ---: | ---: | +| 1 | 5 ms | +| 2 | 3 ms | +| 4 | 2 ms | +| 8 | 3 ms | + +This confirms the bounded-loader path and motivates the default of four; it is +not a cold-cache throughput claim for the final full catalog. Reproduce it +with the rotating observer fixture: + +```sh +./build/minkowski_sky --all-sky-catalog assets/2mass/processed/all_sky \ + --observer-track tests/data/minkowski_rotation_2s.csv \ + --frames-dir output/imgs/catalog_prefetch_bench/w4 --frames-prefix frame \ + --start-time 0 --duration 0 --fps 1 --width 128 --height 72 \ + --coarse-cell-pixels 8 --fov-deg 2 --exposure 1e13 \ + --catalog-load-workers 4 +``` + Missing tiles are skipped until the downloader has produced them. Fully contained tiles skip per-star source-triangle containment; partial tiles retain that exact check. For 2MASS apparent-solid-angle amplitudes, start with diff --git a/src/catalog.c b/src/catalog.c index baec797..96c3242 100644 --- a/src/catalog.c +++ b/src/catalog.c @@ -6,6 +6,8 @@ #include #include +#include + #define PI 3.14159265358979323846 enum { @@ -108,26 +110,40 @@ static size_t tile_index(int ra_index, int dec_index) return (size_t)dec_index * CATALOG_ALL_SKY_RA_TILES + ra_index; } +static int load_tile_file(const StarCatalog *catalog, int ra_index, + int dec_index, Star **stars, size_t *count) +{ + char path[PATH_MAX]; + int written; + if (catalog == NULL || stars == NULL || count == NULL) + return -1; + *stars = NULL; + *count = 0; + written = snprintf(path, sizeof path, "%s/tile_ra%03d_dec%03d.csv", + catalog->all_sky_root, ra_index, dec_index); + if (written < 0 || (size_t)written >= sizeof path) + return -1; + StarCatalog temporary = {0}; + if (catalog_load_csv(&temporary, path)) + return -1; + *stars = temporary.stars; + *count = temporary.count; + return 0; +} + static int load_tile(StarCatalog *catalog, int ra_index, int dec_index) { CatalogTile *tile = &catalog->tiles[tile_index(ra_index, dec_index)]; - char path[PATH_MAX]; - int written; + Star *stars; + size_t count; if (tile->state != 0) return tile->state == 1 ? 0 : -1; - written = snprintf(path, sizeof path, "%s/tile_ra%03d_dec%03d.csv", - catalog->all_sky_root, ra_index, dec_index); - if (written < 0 || (size_t)written >= sizeof path) { + if (load_tile_file(catalog, ra_index, dec_index, &stars, &count)) { tile->state = -1; return -1; } - StarCatalog temporary = {0}; - if (catalog_load_csv(&temporary, path)) { - tile->state = -1; - return -1; - } - tile->stars = temporary.stars; - tile->count = temporary.count; + tile->stars = stars; + tile->count = count; tile->state = 1; catalog->count += tile->count; return 0; @@ -255,15 +271,15 @@ static int tile_is_fully_contained(const double direction[3][3], int ra_index, return 1; } -int catalog_visit_source_triangle(StarCatalog *catalog, - const double direction[3][3], - int load_missing, CatalogTileVisitor visitor, - void *context) +typedef int (*CatalogTileIndexVisitor)(int ra_index, int dec_index, + void *context); + +static int visit_source_triangle_tile_indices( + const double direction[3][3], CatalogTileIndexVisitor visitor, + void *context) { - if (catalog == NULL || direction == NULL || visitor == NULL) + if (direction == NULL || visitor == NULL) return -1; - if (catalog->kind == STAR_CATALOG_MEMORY) - return visitor(catalog->stars, catalog->count, 0, context); double longitude[3], latitude[3], unwrapped[3]; for (int i = 0; i < 3; ++i) { lon_lat_from_direction(direction[i], &longitude[i], &latitude[i]); @@ -311,17 +327,149 @@ int catalog_visit_source_triangle(StarCatalog *catalog, for (int raw_ra = all_ra ? 0 : ra_first; raw_ra <= (all_ra ? 359 : ra_last); ++raw_ra) { const int ra = (raw_ra % 360 + 360) % 360; - CatalogTile *tile = &catalog->tiles[tile_index(ra, dec)]; - if (load_missing && load_tile(catalog, ra, dec)) - continue; /* Downloader has not finished this tile yet. */ - if (tile->state != 1 || - visitor(tile->stars, tile->count, - tile_is_fully_contained(direction, ra, dec), context)) + if (visitor(ra, dec, context)) return -1; } return 0; } +typedef struct { + StarCatalog *catalog; + const double (*direction)[3]; + int load_missing; + CatalogTileVisitor visitor; + void *context; +} CatalogVisitContext; + +static int visit_catalog_tile(int ra_index, int dec_index, void *opaque) +{ + CatalogVisitContext *context = opaque; + CatalogTile *tile = + &context->catalog->tiles[tile_index(ra_index, dec_index)]; + if (context->load_missing && load_tile(context->catalog, ra_index, dec_index)) + return 0; /* Downloader has not finished this tile yet. */ + if (tile->state != 1) + return 0; + return context->visitor( + tile->stars, tile->count, + tile_is_fully_contained(context->direction, ra_index, dec_index), + context->context); +} + +typedef struct { + unsigned char *requested; +} CatalogMarkContext; + +static int mark_catalog_tile(int ra_index, int dec_index, void *opaque) +{ + CatalogMarkContext *context = opaque; + context->requested[tile_index(ra_index, dec_index)] = 1; + return 0; +} + +int catalog_mark_source_triangle_tiles( + const double direction[3][3], + unsigned char requested[CATALOG_ALL_SKY_TILE_COUNT]) +{ + CatalogMarkContext context = {.requested = requested}; + if (requested == NULL) + return -1; + return visit_source_triangle_tile_indices(direction, mark_catalog_tile, + &context); +} + +typedef struct { + size_t tile_id; + Star *stars; + size_t count; + int loaded; +} CatalogPendingTile; + +int catalog_prefetch_marked_tiles( + StarCatalog *catalog, + const unsigned char requested[CATALOG_ALL_SKY_TILE_COUNT], + int worker_count, CatalogPrefetchStats *stats) +{ + CatalogPendingTile *pending = NULL; + size_t pending_count = 0; + double load_start; + int result = -1; + if (stats != NULL) + *stats = (CatalogPrefetchStats){0}; + if (catalog == NULL || requested == NULL || worker_count <= 0) + return -1; + if (catalog->kind != STAR_CATALOG_ALL_SKY) + return 0; + for (size_t tile_id = 0; tile_id < CATALOG_ALL_SKY_TILE_COUNT; ++tile_id) { + if (!requested[tile_id]) + continue; + if (stats != NULL) + ++stats->requested_tiles; + if (catalog->tiles[tile_id].state == 0) + ++pending_count; + } + if (pending_count == 0) + return 0; + pending = calloc(pending_count, sizeof *pending); + if (pending == NULL) + return -1; + size_t pending_index = 0; + for (size_t tile_id = 0; tile_id < CATALOG_ALL_SKY_TILE_COUNT; ++tile_id) + if (requested[tile_id] && catalog->tiles[tile_id].state == 0) + pending[pending_index++].tile_id = tile_id; + if (worker_count > (int)pending_count) + worker_count = (int)pending_count; + load_start = omp_get_wtime(); +#pragma omp parallel for num_threads(worker_count) schedule(static) + for (size_t i = 0; i < pending_count; ++i) { + const int ra_index = + (int)(pending[i].tile_id % CATALOG_ALL_SKY_RA_TILES); + const int dec_index = + (int)(pending[i].tile_id / CATALOG_ALL_SKY_RA_TILES); + pending[i].loaded = !load_tile_file( + catalog, ra_index, dec_index, &pending[i].stars, &pending[i].count); + } + if (stats != NULL) + stats->load_seconds = omp_get_wtime() - load_start; + /* Only this serial commit mutates the shared catalog cache. */ + for (size_t i = 0; i < pending_count; ++i) { + CatalogTile *tile = &catalog->tiles[pending[i].tile_id]; + if (pending[i].loaded) { + tile->stars = pending[i].stars; + tile->count = pending[i].count; + tile->state = 1; + catalog->count += tile->count; + if (stats != NULL) { + ++stats->newly_loaded_tiles; + stats->newly_loaded_stars += tile->count; + } + } else { + tile->state = -1; + if (stats != NULL) + ++stats->unavailable_tiles; + } + } + result = 0; + free(pending); + return result; +} + +int catalog_visit_source_triangle(StarCatalog *catalog, + const double direction[3][3], + int load_missing, CatalogTileVisitor visitor, + void *context) +{ + if (catalog == NULL || direction == NULL || visitor == NULL) + return -1; + if (catalog->kind == STAR_CATALOG_MEMORY) + return visitor(catalog->stars, catalog->count, 0, context); + CatalogVisitContext visit_context = { + .catalog = catalog, .direction = direction, .load_missing = load_missing, + .visitor = visitor, .context = context}; + return visit_source_triangle_tile_indices(direction, visit_catalog_tile, + &visit_context); +} + void catalog_destroy(StarCatalog *catalog) { if (catalog->tiles != NULL) diff --git a/src/catalog.h b/src/catalog.h index 9535b70..2c99019 100644 --- a/src/catalog.h +++ b/src/catalog.h @@ -9,7 +9,12 @@ typedef struct { double amplitude; } Star; -enum { CATALOG_ALL_SKY_RA_TILES = 360, CATALOG_ALL_SKY_DEC_TILES = 180 }; +enum { + CATALOG_ALL_SKY_RA_TILES = 360, + CATALOG_ALL_SKY_DEC_TILES = 180, + CATALOG_ALL_SKY_TILE_COUNT = + CATALOG_ALL_SKY_RA_TILES * CATALOG_ALL_SKY_DEC_TILES +}; typedef struct { Star *stars; @@ -33,6 +38,14 @@ typedef struct { typedef int (*CatalogTileVisitor)(const Star *stars, size_t count, int fully_contained, void *context); +typedef struct { + size_t requested_tiles; + size_t newly_loaded_tiles; + size_t unavailable_tiles; + size_t newly_loaded_stars; + double load_seconds; +} CatalogPrefetchStats; + /* * Synthetic lensing fixture: stars lie on the union of 10-degree longitude * and latitude lines, sampled every 2 degrees. The eight longitude/hemisphere @@ -44,6 +57,18 @@ int catalog_load_csv(StarCatalog *catalog, const char *path); /* The directory contains tile_raRRR_decDDD.csv plus optional .done markers. * Tiles are loaded only after a source triangle intersects them. */ int catalog_load_all_sky(StarCatalog *catalog, const char *directory); +/* Add every one-degree tile which can intersect a source triangle to the + * caller-owned CATALOG_ALL_SKY_TILE_COUNT-byte request bitmap. */ +int catalog_mark_source_triangle_tiles( + const double direction[3][3], + unsigned char requested[CATALOG_ALL_SKY_TILE_COUNT]); +/* Load each requested, previously unseen tile once. File reads use at most + * worker_count OpenMP workers, but cache mutation happens serially afterward, + * leaving the cache immutable for later splatting. */ +int catalog_prefetch_marked_tiles( + StarCatalog *catalog, + const unsigned char requested[CATALOG_ALL_SKY_TILE_COUNT], + int worker_count, CatalogPrefetchStats *stats); /* Visit only 1-degree all-sky tiles that can intersect this source triangle. * Call with load_missing=1 before parallel rendering, then 0 inside workers. */ int catalog_visit_source_triangle(StarCatalog *catalog, diff --git a/src/frame.c b/src/frame.c index 276f44d..d5611de 100644 --- a/src/frame.c +++ b/src/frame.c @@ -259,19 +259,13 @@ static size_t splat_catalog_triangles(const FrameLensMesh *mesh, return images; } -static int prefetch_catalog_tile(const Star *stars, size_t count, - int fully_contained, void *context) { - (void)stars; - (void)count; - (void)fully_contained; - (void)context; - return 0; -} - static void prefetch_catalog_for_mesh(const FrameLensMesh *mesh, - StarCatalog *catalog) { + StarCatalog *catalog, + int worker_count, + CatalogPrefetchStats *stats) { if (catalog->kind != STAR_CATALOG_ALL_SKY) return; + unsigned char requested[CATALOG_ALL_SKY_TILE_COUNT] = {0}; for (size_t t = 0; t < mesh->triangle_count; ++t) { const LensVertex *vertex[3]; if (!usable_triangle(mesh, &mesh->triangles[t], vertex)) @@ -280,22 +274,24 @@ static void prefetch_catalog_for_mesh(const FrameLensMesh *mesh, {vertex[0]->n_infinity[0], vertex[0]->n_infinity[1], vertex[0]->n_infinity[2]}, {vertex[1]->n_infinity[0], vertex[1]->n_infinity[1], vertex[1]->n_infinity[2]}, {vertex[2]->n_infinity[0], vertex[2]->n_infinity[1], vertex[2]->n_infinity[2]}}; - (void)catalog_visit_source_triangle(catalog, direction, 1, - prefetch_catalog_tile, NULL); + (void)catalog_mark_source_triangle_tiles(direction, requested); } + (void)catalog_prefetch_marked_tiles(catalog, requested, worker_count, stats); } size_t frame_splat_catalog(const FrameLensMesh *mesh, StarCatalog *catalog, double *hdr, int width, int height, double exposure, - const PointSpreadFunction *psf) { + const PointSpreadFunction *psf, + int catalog_load_workers, + CatalogPrefetchStats *prefetch_stats) { if (mesh == NULL || catalog == NULL || hdr == NULL || exposure <= 0.0 || - psf == NULL || width <= 0 || height <= 0) + psf == NULL || width <= 0 || height <= 0 || catalog_load_workers <= 0) return 0; - /* Tile I/O is deliberately serial and complete before OpenMP workers start. - * The parallel splat pass then reads an immutable tile cache. */ - prefetch_catalog_for_mesh(mesh, catalog); + /* A bounded parallel read phase completes before splatting. Its serial cache + * commit leaves immutable tile data for the OpenMP splat workers. */ + prefetch_catalog_for_mesh(mesh, catalog, catalog_load_workers, prefetch_stats); const size_t pixel_count = (size_t)width * height * 3; if (pixel_count > SIZE_MAX / sizeof(double) || diff --git a/src/frame.h b/src/frame.h index 2a71dc3..e1f5f7d 100644 --- a/src/frame.h +++ b/src/frame.h @@ -38,7 +38,9 @@ int frame_lens_mesh_trace(FrameLensMesh *mesh, const SpacetimeSource *spacetime, size_t frame_splat_catalog(const FrameLensMesh *mesh, StarCatalog *catalog, double *hdr, int width, int height, double exposure, - const PointSpreadFunction *psf); + const PointSpreadFunction *psf, + int catalog_load_workers, + CatalogPrefetchStats *prefetch_stats); void frame_draw_mesh(const FrameLensMesh *mesh, double *hdr, int width, int height, double gray, double opacity); void frame_lens_mesh_destroy(FrameLensMesh *mesh); diff --git a/src/main.c b/src/main.c index 78266e8..0caa9d0 100644 --- a/src/main.c +++ b/src/main.c @@ -31,6 +31,7 @@ typedef struct { double movie_start_time, movie_duration, movie_fps; double slab_duration; double minkowski_proper_acceleration; + int catalog_load_workers; } Settings; static int parse_int(const char *text, int *value) { @@ -108,7 +109,8 @@ static int parse_args(int argc, char **argv, Settings *s, .movie_duration = 2.0, .movie_fps = 30.0, .slab_duration = 64.0, - .minkowski_proper_acceleration = 1.52}; + .minkowski_proper_acceleration = 1.52, + .catalog_load_workers = 4}; *write_path = NULL; for (int i = 1; i < argc; ++i) { if (!strcmp(argv[i], "--catalog") && i + 1 < argc) @@ -158,6 +160,8 @@ static int parse_args(int argc, char **argv, Settings *s, } else if (!strcmp(argv[i], "--proper-acceleration") && i + 1 < argc && !parse_nonnegative(argv[++i], &s->minkowski_proper_acceleration)) { + } else if (!strcmp(argv[i], "--catalog-load-workers") && i + 1 < argc && + !parse_int(argv[++i], &s->catalog_load_workers)) { } else if (!strcmp(argv[i], "--write-minkowski-accel-track") && i + 1 < argc) s->write_minkowski_accel_track_path = argv[++i]; @@ -204,14 +208,24 @@ static int render_observer_frame(const Settings *s, StarCatalog *catalog, free(hdr); return -1; } - size_t images = frame_splat_catalog(&mesh, catalog, hdr, s->width, s->height, - s->exposure, &s->psf); + CatalogPrefetchStats prefetch = {0}; + size_t images = frame_splat_catalog( + &mesh, catalog, hdr, s->width, s->height, s->exposure, &s->psf, + s->catalog_load_workers, &prefetch); if (s->draw_mesh) frame_draw_mesh(&mesh, hdr, s->width, s->height, 0.5, 0.5); int result = write_tonemapped_image(output_path, hdr, s->width, s->height); fprintf(stderr, "Rendered %zu images from %zu catalog stars to %s (%s)\n", images, catalog->count, output_path, result == 0 ? "ok" : "write failed"); + if (catalog->kind == STAR_CATALOG_ALL_SKY) + fprintf(stderr, + "Catalog prefetch: %zu requested, %zu newly loaded (%zu stars), " + "%zu unavailable in %.3f s; %d loader workers\n", + prefetch.requested_tiles, prefetch.newly_loaded_tiles, + prefetch.newly_loaded_stars, prefetch.unavailable_tiles, + prefetch.load_seconds, + s->catalog_load_workers); frame_lens_mesh_destroy(&mesh); free(hdr); return result; @@ -289,14 +303,24 @@ static int render_movie(const Settings *s, StarCatalog *catalog, free(hdr); goto done; } - const size_t images = frame_splat_catalog(&movie.frames[i].mesh, catalog, hdr, - s->width, s->height, s->exposure, &s->psf); + CatalogPrefetchStats prefetch = {0}; + const size_t images = frame_splat_catalog( + &movie.frames[i].mesh, catalog, hdr, s->width, s->height, s->exposure, + &s->psf, s->catalog_load_workers, &prefetch); if (s->draw_mesh) frame_draw_mesh(&movie.frames[i].mesh, hdr, s->width, s->height, 0.5, 0.5); const int write_result = write_tonemapped_image(output_path, hdr, s->width, s->height); free(hdr); fprintf(stderr, "Rendered %zu images from %zu catalog stars to %s (%s)\n", images, catalog->count, output_path, write_result == 0 ? "ok" : "write failed"); + if (catalog->kind == STAR_CATALOG_ALL_SKY) + fprintf(stderr, + "Catalog prefetch: %zu requested, %zu newly loaded (%zu stars), " + "%zu unavailable in %.3f s; %d loader workers\n", + prefetch.requested_tiles, prefetch.newly_loaded_tiles, + prefetch.newly_loaded_stars, prefetch.unavailable_tiles, + prefetch.load_seconds, + s->catalog_load_workers); if (write_result) goto done; } @@ -331,6 +355,7 @@ int main(int argc, char **argv) { "[--exposure E] [--observer-inward-speed V] " "[--psf-fwhm-pixels N] [--psf-moffat-beta N] " "[--coarse-cell-pixels N] [--draw-mesh] [--write-catalog PATH] " + "[--catalog-load-workers N] " "[--observer-track PATH --frames-dir DIR --frames-prefix NAME " "--start-time T --duration T --fps N] " "[--proper-acceleration A --write-minkowski-accel-track PATH]\n", diff --git a/tests/data/minkowski_rotation_2s.csv b/tests/data/minkowski_rotation_2s.csv new file mode 100644 index 0000000..b86f315 --- /dev/null +++ b/tests/data/minkowski_rotation_2s.csv @@ -0,0 +1,7 @@ +# An inertial observer at the origin whose spatial camera triad scans RA 0, 20, +# and 40 degrees at Dec -75 degrees. Samples coincide with a 1 fps movie so +# this fixture avoids interpolation-induced non-orthonormal intermediate triads. +t,tau,x,y,z,e0t,e0x,e0y,e0z,e1t,e1x,e1y,e1z,e2t,e2x,e2y,e2z,e3t,e3x,e3y,e3z +0,0,0,0,0,1,0,0,0,0,0.25881904510252074,-0.9659258262890683,0,0,0.9659258262890683,0.25881904510252074,0,0,0,0,1 +1,1,0,0,0,1,0,0,0,0,0.24321034680169396,-0.9659258262890683,0.08852132690137686,0,0.9076733711903687,0.25881904510252074,0.33036608954935215,0,-0.3420201433256687,0,0.9396926207859084 +2,2,0,0,0,1,0,0,0,0,0.19826689127414615,-0.9659258262890683,0.1663656753428019,0,0.739942111693848,0.25881904510252074,0.6208851530148457,0,-0.6427876096865394,0,0.766044443118978 diff --git a/tests/test_catalog_prefetch.c b/tests/test_catalog_prefetch.c new file mode 100644 index 0000000..4087f16 --- /dev/null +++ b/tests/test_catalog_prefetch.c @@ -0,0 +1,71 @@ +#define _POSIX_C_SOURCE 200809L + +#include "catalog.h" + +#include +#include +#include +#include + +static int write_tile(const char *directory, int ra_index, int dec_index, + double longitude) { + char path[4096]; + const int written = snprintf(path, sizeof path, "%s/tile_ra%03d_dec%03d.csv", + directory, ra_index, dec_index); + if (written < 0 || written >= (int)sizeof path) + return -1; + FILE *file = fopen(path, "w"); + if (file == NULL) + return -1; + const int result = + fputs("longitude_deg,latitude_deg,temperature_K,amplitude\n", file) == EOF || + fprintf(file, "%.1f,0,7000,1\n", longitude) < 0 || + fclose(file) != 0; + return result ? -1 : 0; +} + +int main(void) { + char directory[] = "/tmp/catalog_prefetch_XXXXXX"; + char path[4096]; + StarCatalog catalog = {0}; + unsigned char requested[CATALOG_ALL_SKY_TILE_COUNT] = {0}; + CatalogPrefetchStats first = {0}, second = {0}; + int result = 1; + if (mkdtemp(directory) == NULL || write_tile(directory, 0, 90, 0.0) || + write_tile(directory, 1, 90, 1.0) || + catalog_load_all_sky(&catalog, directory)) + goto done; + requested[90 * CATALOG_ALL_SKY_RA_TILES] = 1; + requested[90 * CATALOG_ALL_SKY_RA_TILES + 1] = 1; + requested[90 * CATALOG_ALL_SKY_RA_TILES + 2] = 1; /* Missing on purpose. */ + if (catalog_prefetch_marked_tiles(&catalog, requested, 4, &first) || + first.requested_tiles != 3 || first.newly_loaded_tiles != 2 || + first.unavailable_tiles != 1 || first.newly_loaded_stars != 2 || + catalog.count != 2 || + catalog.tiles[90 * CATALOG_ALL_SKY_RA_TILES].state != 1 || + catalog.tiles[90 * CATALOG_ALL_SKY_RA_TILES + 1].state != 1 || + catalog.tiles[90 * CATALOG_ALL_SKY_RA_TILES + 2].state != -1) { + fputs("parallel catalog prefetch regression failed\n", stderr); + goto done; + } + Star *first_tile = catalog.tiles[90 * CATALOG_ALL_SKY_RA_TILES].stars; + if (catalog_prefetch_marked_tiles(&catalog, requested, 2, &second) || + second.requested_tiles != 3 || second.newly_loaded_tiles != 0 || + second.unavailable_tiles != 0 || second.newly_loaded_stars != 0 || + catalog.count != 2 || + catalog.tiles[90 * CATALOG_ALL_SKY_RA_TILES].stars != first_tile) { + fputs("catalog prefetch cache reuse regression failed\n", stderr); + goto done; + } + result = 0; +done: + catalog_destroy(&catalog); + for (int ra = 0; ra < 3; ++ra) { + const int written = snprintf(path, sizeof path, "%s/tile_ra%03d_dec090.csv", + directory, ra); + if (written > 0 && written < (int)sizeof path) + unlink(path); + } + rmdir(directory); + return result; +} diff --git a/tests/test_frame.c b/tests/test_frame.c index 1ed453c..a194de4 100644 --- a/tests/test_frame.c +++ b/tests/test_frame.c @@ -27,7 +27,7 @@ int main(void) { goto done; const size_t images = frame_splat_catalog(&mesh, &catalog, hdr, width, height, test_exposure, - &psf); + &psf, 1, NULL); if (images != 1 || hdr[3 * (50 * width + 50)] <= 0.0) { fputs("flat-space inverse lens-map regression failed\n", stderr); goto done; @@ -44,10 +44,10 @@ int main(void) { omp_set_dynamic(0); omp_set_num_threads(1); const size_t serial_images = frame_splat_catalog( - &mesh, &catalog, serial_hdr, width, height, test_exposure, &psf); + &mesh, &catalog, serial_hdr, width, height, test_exposure, &psf, 1, NULL); omp_set_num_threads(4); const size_t parallel_images = frame_splat_catalog( - &mesh, &catalog, parallel_hdr, width, height, test_exposure, &psf); + &mesh, &catalog, parallel_hdr, width, height, test_exposure, &psf, 1, NULL); omp_set_num_threads(original_threads); for (int value = 0; value < width * height * 3; ++value) if (fabs(serial_hdr[value] - parallel_hdr[value]) > @@ -102,7 +102,7 @@ int main(void) { if (frame_lens_mesh_build_coarse(&fine_mesh, width, height, 1, 0.1) || frame_lens_mesh_trace(&fine_mesh, &spacetime, &observer, &trace) || frame_splat_catalog(&fine_mesh, &fine_catalog, hdr, width, height, - test_exposure, &psf) != 1) { + test_exposure, &psf, 1, NULL) != 1) { fputs("fine source-triangle containment regression failed\n", stderr); frame_lens_mesh_destroy(&fine_mesh); goto done;