From ade1b7063a766fe019ec9b5005b37199099697b8 Mon Sep 17 00:00:00 2001 From: Berk Geveci Date: Thu, 16 Apr 2026 13:25:28 -0400 Subject: [PATCH 1/3] perf: avoid unnecessary array copies in reader Replace flatten() with reshape(-1) to avoid copies where the data is only read. In _load_variable, use a single copy followed by in-place fill value replacement instead of flatten + np.where which created three copies. --- src/e3sm_quickview/plugins/eam_reader.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/e3sm_quickview/plugins/eam_reader.py b/src/e3sm_quickview/plugins/eam_reader.py index 5636679..e756d53 100644 --- a/src/e3sm_quickview/plugins/eam_reader.py +++ b/src/e3sm_quickview/plugins/eam_reader.py @@ -422,8 +422,8 @@ def _load_variable(self, vardata, varmeta): slice_tuple.append(self._slices.get(dim, 0)) # Get data with proper slicing - data = vardata[varmeta.name][tuple(slice_tuple)].data.flatten() - data = np.where(data == varmeta.fillval, np.nan, data) + data = vardata[varmeta.name][tuple(slice_tuple)].data.reshape(-1).copy() + data[data == varmeta.fillval] = np.nan return data except Exception as e: print_error(f"Error loading variable {varmeta.name}: {e}") @@ -460,8 +460,8 @@ def _build_geometry(self, meshdata): londim = mvars[np.where(np.char.find(mvars, "corner_lon") > -1)][0] # Build coordinates - lat = meshdata[latdim][:].data.flatten() - lon = meshdata[londim][:].data.flatten() + lat = meshdata[latdim][:].data.reshape(-1) + lon = meshdata[londim][:].data.reshape(-1) if self._ForceFloatPoints: points_type = np.float32 @@ -561,7 +561,7 @@ def _populate_variable_metadata(self): # Clear old timestamps before adding new ones self._timeSteps.clear() if "time" in vardata.variables: - timesteps = vardata["time"][:].data.flatten() + timesteps = vardata["time"][:].data.reshape(-1) self._timeSteps.extend(timesteps) def SetDataFileName(self, fname): From f472043dc55934ecbb2bc9f36be96f138ade7271 Mon Sep 17 00:00:00 2001 From: Berk Geveci Date: Thu, 16 Apr 2026 13:52:19 -0400 Subject: [PATCH 2/3] perf: skip reloading variables unaffected by slice changes Track which dimensions changed in SetSlicing and only reload variables whose dimensions intersect the changed set. Variables unaffected by the slice change are kept from the previous output. For example, changing lev now skips all 2D (time, ncol) variables. With 200 2D + 20 3D variables loaded, lev changes are ~2.5x faster. --- src/e3sm_quickview/plugins/eam_reader.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/e3sm_quickview/plugins/eam_reader.py b/src/e3sm_quickview/plugins/eam_reader.py index e756d53..f9eebe5 100644 --- a/src/e3sm_quickview/plugins/eam_reader.py +++ b/src/e3sm_quickview/plugins/eam_reader.py @@ -255,6 +255,7 @@ def __init__(self): self._timeSteps = [] # Dictionary to store dimension slices self._slices = {} + self._changed_dims = set() # vtkDataArraySelection to allow users choice for fields # to fetch from the netCDF data set @@ -627,6 +628,8 @@ def SetSlicing(self, slice_str): f"{dim_display}={slice_val} (valid range: 0-{dim_size - 1})" ) else: + if self._slices.get(dim) != slice_val: + self._changed_dims.add(dim) self._slices[dim] = slice_val else: print_error( @@ -759,13 +762,20 @@ def RequestData(self, request, inInfo, outInfo): for i in range(last_num_arrays): to_remove.add(output_mesh.CellData.GetArrayName(i)) + changed_dims = self._changed_dims for name, varmeta in self._variables.items(): if self._variable_selection.ArrayIsEnabled(name): if output_mesh.CellData.HasArray(name): to_remove.remove(name) + if changed_dims and not changed_dims.intersection( + varmeta.dimensions + ): + continue data = self._load_variable(vardata, varmeta) output_mesh.CellData.append(data, name) + self._changed_dims = set() + area_var_name = "area" if self._areavar and not output_mesh.CellData.HasArray(area_var_name): data = self._get_cached_area(vardata) From 53ba7416162e2b3ea06c9c1fa2053ab54f2ae1a7 Mon Sep 17 00:00:00 2001 From: Berk Geveci Date: Thu, 16 Apr 2026 15:08:16 -0400 Subject: [PATCH 3/3] perf: skip fill value scan and copy for variables without fill values Only copy and scan for fill values when the variable actually has a _FillValue attribute. Most variables default to NaN, making the comparison a no-op that still scans the entire array. Variables without fill values now return a zero-copy view from reshape. --- src/e3sm_quickview/plugins/eam_reader.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/e3sm_quickview/plugins/eam_reader.py b/src/e3sm_quickview/plugins/eam_reader.py index f9eebe5..3ed5b5c 100644 --- a/src/e3sm_quickview/plugins/eam_reader.py +++ b/src/e3sm_quickview/plugins/eam_reader.py @@ -423,8 +423,10 @@ def _load_variable(self, vardata, varmeta): slice_tuple.append(self._slices.get(dim, 0)) # Get data with proper slicing - data = vardata[varmeta.name][tuple(slice_tuple)].data.reshape(-1).copy() - data[data == varmeta.fillval] = np.nan + data = vardata[varmeta.name][tuple(slice_tuple)].data.reshape(-1) + if not np.isnan(varmeta.fillval): + data = data.copy() + data[data == varmeta.fillval] = np.nan return data except Exception as e: print_error(f"Error loading variable {varmeta.name}: {e}")