Skip to content

Lidar odometry: bounded memory for long recordings - #555

Merged
JanuszBedkowski merged 2 commits into
MapsHD:mainfrom
adamdbrw:lio-long-capture-bounded-memory
Oct 7, 2026
Merged

JanuszBedkowski merged 2 commits into
MapsHD:mainfrom
adamdbrw:lio-long-capture-bounded-memory

Conversation

@adamdbrw

@adamdbrw adamdbrw commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Process long recordings without running out of memory

Now you can run lidar odometry on long recordings — an hour of walking, or a slow vehicle — on an ordinary
workstation. Before, such a run could need more than 60 GB of RAM and be killed part-way.

Now you can keep the map's working buffer on disk instead of in RAM, with one line in the parameter file.

The results are exactly the same as before: same trajectory, same scans, same poses, byte for byte.

How

  • Raw scans are now loaded when they are needed and freed when they are done, instead of all at once at the start.
    This is on by default and already lowers memory use (2.2 GB → 1.3 GB on our test).

  • For very long recordings, set a scratch folder and the map buffer lives there (→ 0.75 GB on the same test):

    [performance]
    points_global_spill_directory = "/path/to/fast/disk"

Both settings are documented in doc/virtual_memory.md. If a step fails, for example when the disk is full, the apps
now report the error instead of saving a partial result.

lidar_odometry_step_1 loads every raw point-cloud file before step 1 starts,
and step 2's map buffer (points_global) only shrinks at a sliding-window reset.
A long recording from a slow-moving platform (~50 min, ~29,000 frames) needs
more than 60 GB of RAM and the run is killed part-way.

Two parameters in the [performance] section of LidarOdometryParams, saved with
every run and documented in doc/virtual_memory.md:

- lazy_load_raw_clouds (default true): each raw file is loaded when step 1
  reaches its time range and freed once step 1 has passed it. Files are loaded
  and sorted exactly as before, so step 1 sees the same points in the same
  order. The raw data viewer sets it to false and keeps every file.
- points_global_spill_directory (default "", in memory): when set, step 2's
  map buffer is kept in a temporary file there, owned by the compute_step_2
  call, and streamed back in order at a reset with the ray-cast decision of
  its whole size. An I/O error ends step 2 with an error (no abort) and the
  files are removed on every exit; the final close is checked.
  Lazy loading reads each file twice and refuses a file that changed in
  between (size, modification time before and after the read, point count,
  time range).

The step 1 GUI, its console mode and the precision-forestry app now act on a
failed step 1 or step 2: the console reports "no results saved" and exits 1,
and the GUI neither marks the step done nor saves a partial result.

Also: the two 101^3 normal-vector histograms are cleared by resetting only the
bins written in the previous iteration; the raw clouds are released after a
successful step 1 (release_raw_clouds: run_lidar_odometry, the step 1 GUI and
console, the precision-forestry app), since nothing later reads them; both
parameters are exposed in the Python bindings. Heap trimming is glibc-only;
file seeks are 64-bit (_fseeki64 / fseeko, range-checked where off_t is
narrower).

Every trajectory, scan and pose file is byte-identical to the unpatched build
on a recording window, with the defaults, lazy loading off, the spill directory
set, and resets forced by a 20 m window; only metadata differs. (When the
per-frame time limit real_time_threshold_seconds is reached, the iteration
count depends on machine speed with or without this change.) Peak memory
there: 2.2 GB unpatched, 1.3 GB with the defaults, 0.75 GB with the spill
directory set.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@adamdbrw adamdbrw changed the title Lidar odometry: bounded memory for long recordings, results unchanged Lidar odometry: bounded memory for long recordings Oct 5, 2026
@michalpelka
michalpelka self-requested a review October 5, 2026 14:38

@michalpelka michalpelka left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some nitting,

spill_fail("seek failed");
}

class PointsGlobalSpill

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to move PointsGlobalSpill to separe cpp/header with own documentation - currently it looks band-aid like.

}

private:
std::string path() const

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I exepct troubles on Windows - stick to std::filestystem instead of string airthmetics.

// when its range overlaps the chunk and frees it once the chunk start has passed its last point. A file is loaded by
// the same call and sorted by the same comparator as the eager load, so every chunk receives the same points in the
// same order. The loader lives in LidarOdometryParams::raw_cloud_loader, so each pipeline owns its own.
struct RawCloudLoader

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to ceate cpp/header for that

<< (int)p.lidarid << "\n";
}
}
write_calibration_validation(wdp, data, params);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

private:
std::string path() const
{
return dir_ + "/points_global_" + tag_ + "_" + std::to_string(gen_) + ".bin";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expect troubles on Windows, use std::filesystem instead of this string sewing.

spill_fail("seek failed");
}

class PointsGlobalSpill

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to ceate cpp/header for that

…oader

Move both out of lidar_odometry_utils_optimizers.cpp/lidar_odometry.cpp
into their own header/source pairs, and build PointsGlobalSpill's spill
file path with std::filesystem instead of string concatenation to avoid
Windows path-separator issues. Logic is unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G9Cmw8PaVx6XGFJhAh7G1f
@JanuszBedkowski
JanuszBedkowski merged commit 2744d75 into MapsHD:main Oct 7, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants