Deepening II, Performance and memory

learning objective: understanding how the GeoDMS keeps large models fast, and which performance and memory levers you control as a modeller
introduction
The GeoDMS is built for large models: national grids with millions of cells, long chains of calculation steps, and many scenario variants. A lot of the performance work is done for you by the calculation engine, but some of it is in your hands as a modeller. This deepening first shows what you get for free, then walks through the three levers you control: value types, domains and source tiling, and strategic decoupling. It closes with practical symptoms and remedies, and the multithreading settings in the GeoDMS GUI.
This page is supplementary material; it assumes you have completed Module 2C, GeoDMS own data formats and Module 5, Basic analyses with grid data.
what you get for free
Lazy evaluation. The GeoDMS treats your model as a dependency tree: every data item declares, through its expression, which source data and other items it is calculated from. Calculations are demand driven: only the items you actually request (view in the GUI, export, or update from the command line) are calculated, together with the suppliers they depend on. Items that nothing asks for are never computed. You never specify a calculation order; the engine derives it from the dependencies. See Calculation management.
Reuse within a session. Once calculated, results are kept and reused; nothing is computed twice within a session. Results are invalidated only when the expressions they (directly or indirectly) depend on change, or when their source data changes. After such a change, only the invalidated part of the tree is recalculated. See Update Mechanism.
Multi-threading. The GeoDMS performs multiple calculation steps simultaneously, using multiple cores. This is enabled by default; the settings are described below. See Multi Tasking.
Tiled processing. By default, the GeoDMS splits data into segments (tiles) of 256x256 cells for two-dimensional grid data, and 65536 rows for one-dimensional data. Most operators can process data tile by tile, in parallel, and can pipeline tiles from one operation to the next. This means a calculation over a large grid does not need to hold the full grid in memory at once. See GeoDMS Default Tiling.
On top of this, the engine rewrites calculation rules symbolically to remove redundant steps and optimizes the order of the calculation steps. In short: you describe what to calculate, the engine decides how.
what you control: value types
Every values unit has a value type, and the value type determines how many bytes each element occupies. Because grid attributes have one element per cell, this choice directly scales the memory footprint of your model; see also explicit configuration of value types.
Let us make that tangible. A 100 m grid covering the Dutch RD extent (x from 0 to 280 km, y from 300 to 625 km) has 2,800 columns by 3,250 rows, so 9.1 million cells. At 10 m resolution the same extent has 28,000 by 32,500 cells, so 910 million cells. One attribute on those grids costs, per value type:
| value type | bytes per cell | 100 m grid (9.1 million cells) | 10 m grid (910 million cells) |
|---|---|---|---|
uint8 | 1 | ~9 MB | ~0.9 GB |
uint16 / int16 | 2 | ~18 MB | ~1.8 GB |
uint32 / int32 / float32 | 4 | ~36 MB | ~3.6 GB |
uint64 / int64 / float64 | 8 | ~73 MB | ~7.3 GB |
Two rules of thumb follow directly from this table:
- Use the smallest integer type that fits. A land use map with 40 classes fits comfortably in
uint8(usable values 0 to 254, with 255 reserved as the missing value). Storing it asuint32costs four times the memory for no benefit. For yes/no masks there is even abooltype of 1 bit per cell, anduint2anduint4for very small class sets. - Prefer
float32overfloat64unless you need the precision.float32gives about 7 significant digits, which is usually plenty for distances, densities and suitability scores, at half the memory offloat64.
// a land use map with 40 classes: uint8 is enough
attribute<uint8> land_use_class (grid_100m);
// 4 bytes per cell instead of 8: float32 halves the footprint of float64
attribute<float32> distance_to_station (grid_100m) := ...;
The value type also matters for intermediate results: a long chain of float64 grid calculations keeps multiple 8-byte-per-cell arrays alive at the same time.
what you control: domains and source tiling
Choose your domain deliberately. The domain unit determines the number of elements of every attribute on it. Work at the resolution that answers your question: if a result is needed at 500 m, aggregate early (for example with mean over a relation to the coarser grid, as you did in Module 5) instead of dragging the fine grid through the whole model. The same goes for vector domains: a subset selection early in the chain means every subsequent step processes fewer rows.
Match the tiling of your source files. The GeoDMS reads grid files in its internal 256x256 tiles. If a large GeoTiff is stored as long strips instead of square blocks, filling each internal tile requires reading many strips, and the file ends up being read many times over. The GeoDMS warns about this in the eventlog with a tilesize mismatch message. The GeoDMS Default Tiling page shows the remedies: retile the source file with gdal_translate, or (from GeoDMS 20.1) leave the grid domain unbound so it adopts the file’s native tiling. The difference is not subtle: a documented measurement on that page shows a raster_merge over 14 elevation tiles going from 4 minutes 48 seconds to 4 seconds after retiling.
Only read and compute what is consumed. The property LazyCalculated = "True" on a storage unit ensures that its data is only calculated when actually requested. This is particularly useful for a source unit with several derived attributes: requesting one derivation does not trigger reading or computing the others, and the source is only read for the tiles those attributes need.
unit<spoint> elevation
: StorageName = "%ProjDir%/Data/elevation.tif"
, StorageType = "gdal.grid"
, StorageReadOnly = "True"
, LazyCalculated = "True"
{
attribute<float32> GridData;
// grid_500m_rel relates each fine cell to its 500 m cell,
// see the GeoDMS Default Tiling page for the full pattern
attribute<float32> mean_500m (grid_500m) := mean(GridData, grid_500m_rel);
attribute<float32> modus_500m (grid_500m) := modus(GridData, grid_500m_rel);
}
Requesting only mean_500m will not compute modus_500m.
what you control: strategic decoupling
Within a session, the engine never computes the same result twice. Between sessions, however, calculated results are gone: restart the GeoDMS and your two-hour base data integration runs again. The answer is strategic decoupling: explicitly write stable intermediate results to disk, and let subsequent runs read those files instead of recalculating.
Decoupling uses the regular storage facilities you learned in Module 2: a write-container that stores the results, and a read-side that reads the same files as a data source. Typical formats are the GeoDMS native formats (MMD or FSS, see Module 2C, GeoDMS own data formats) for fast reads, GeoTiff for grid data, and CSV or parquet for tabular exchange with other tools.
// write once, whenever the base data changes:
unit<uint32> store_base := Preprocessing/Result
, StorageName = "%LocalDataProjDir%/base_data.mmd"
{
attribute<rdc> geometry := Preprocessing/Result/geometry;
attribute<float32> pop_density := Preprocessing/Result/pop_density;
}
// read in every subsequent run (MMD auto-declares the attributes):
unit<uint32> base_data
: StorageName = "%LocalDataProjDir%/base_data.mmd"
, StorageReadOnly = "True"
{
}
Two things to keep in mind:
- There is no automatic invalidation. When source data or expressions upstream of a decoupled result change, re-running the write-container is a conscious step in your workflow. Make that step explicit, for instance one write-container per data layer, updated whenever that layer’s inputs change.
- Decouple only stable intermediates: source data integration and base layers that change rarely, not the parts of the model you are actively developing.
The payoff is iteration speed: while you develop or calibrate one part of a model, the parts you are not touching are read from disk. Decoupled files can also be shared with colleagues and shipped to users who only run the downstream part of a model.
practical symptoms and remedies
| symptom | what is happening | remedy |
|---|---|---|
| The first request of an item takes long, requesting it again is instant | Normal: results are calculated on first demand and reused within the session | None needed |
| After restarting the GeoDMS, everything calculates again | Calculated results are not kept between sessions | Decouple stable intermediates to disk |
| A small edit in the configuration triggers a large recalculation | The edit invalidated all items that directly or indirectly depend on it | Decouple the stable upstream part, so the invalidation stops at the files |
| The eventlog shows a tilesize mismatch warning | The tiling of a source file diverges from the internal 256x256 tiling, causing repeated reads | Retile the source or let the grid domain adopt the file’s native tiling, see GeoDMS Default Tiling |
| The machine becomes unresponsive during large calculations | Memory pressure | Use smaller value types, work on a coarser domain, or lower the memory flushing threshold (see below) |
Reading progress. While a calculation runs, the eventlog shows progress messages on reading, calculating and writing data; its filter lets you select the Calculation Progress category. The statusbar at the bottom of the main window shows the number of items still to be calculated and the current memory usage. And after some longer calculation steps have run, the menu option View > Calculation times opens a Calculation time overview listing the slowest calculation steps of your session: a quick way to find out where the time actually goes.
multithreading settings in the GUI
In the GeoDMS GUI, open Settings > Local machine Options. The Parallel Processing block contains four options, all enabled by default:
| option | what it does |
|---|---|
| 0: Suspend view update to favor gui | Keeps the GUI responsive by suspending further calculation steps whenever user events are queued, resuming when idle |
| 1: Data-segment production as separate tasks | Splits operations into separate tasks per data segment (tile) |
| 2: Multiple operations simultaneously | Performs multiple operations at the same time |
| 3: Pipelined operations | Delays data-segment production to the task that requests those segments, so tiles flow from one operation to the next |
The advice is to keep the default settings in most cases. If your machine encounters memory issues or non-reproducible errors, try disabling option 2, see Multi Tasking. Each option can also be set or cleared for a single run with the command line options /S0 to /S3 (set) and /C0 to /C3 (clear).
The same dialog contains the slider Treshold for memory flushing wait procedure: the percentage of total memory in use at which the GeoDMS starts flushing memory to keep your machine responsive. A higher value calculates faster but makes the machine less responsive on large datasets; the advised range is 80 to 90 percent.
try it yourself!
No new data is needed for this deepening; use the configurations from the earlier modules.
- Open your Module 5 configuration and double-click a calculated grid attribute (for example a
potentialresult). Watch the eventlog messages and the item counter in the statusbar while it calculates. Now request the same item again: it comes back instantly from memory. - After a few of these calculations, open View > Calculation times. Which steps of your session were the most expensive?
- Compute the memory footprint of your own study area: columns x rows x bytes per cell. How much would you save by switching one
float64grid attribute tofloat32? Check the value types of the attributes in your Module 5 configuration and see whether any of them can be narrowed. - Open Settings > Local machine Options and locate the four Parallel Processing options and the memory flushing threshold.
- Revisit your Module 2C exercise with decoupling in mind: which containers of your Module 5 or Module 7 configuration are stable enough to write to MMD once and read back in every run?
Go to previous deepening: Deepening I, How to make a network