From 34e4777916cc670695ba50636c15b9414911dc89 Mon Sep 17 00:00:00 2001 From: "Philipp A." Date: Tue, 30 Jun 2026 12:37:54 +0200 Subject: [PATCH 1/4] ci: fix benchmarks --- benchmarks/benchmarks/preprocessing_counts.py | 72 ++++++++++++------- benchmarks/benchmarks/preprocessing_log.py | 31 +++++--- benchmarks/benchmarks/tools.py | 43 +++++------ 3 files changed, 91 insertions(+), 55 deletions(-) diff --git a/benchmarks/benchmarks/preprocessing_counts.py b/benchmarks/benchmarks/preprocessing_counts.py index 5c5114d902..789cf229d9 100644 --- a/benchmarks/benchmarks/preprocessing_counts.py +++ b/benchmarks/benchmarks/preprocessing_counts.py @@ -7,6 +7,7 @@ from inspect import signature from itertools import product +from pathlib import Path from typing import TYPE_CHECKING import anndata as ad @@ -24,12 +25,12 @@ from ._utils import Dataset, KeyCount -def cache_adata(dataset: Dataset, layer: KeyCount) -> None: +def make_adata(dataset: Dataset, layer: KeyCount) -> ad.AnnData: """Without this caching, asv was running several processes which meant the data was repeatedly downloaded.""" adata, batch_key = get_count_dataset(dataset, layer=layer) assert "lop1p" not in adata.uns adata.uns["batch_key"] = batch_key - adata.write_h5ad(f"{dataset}_{layer}.h5ad") + return adata class PreprocessingCountsSuite: # noqa: D101 @@ -39,12 +40,19 @@ class PreprocessingCountsSuite: # noqa: D101 ) param_names = ("dataset", "layer") - def setup_cache(self) -> None: - for dataset, layer in product(*self.params): - cache_adata(dataset, layer) + def setup_cache(self) -> dict[tuple[Dataset, KeyCount], ad.AnnData]: + return { + (dataset, layer): make_adata(dataset, layer) + for dataset, layer in product(*self.params) + } - def setup(self, dataset, layer) -> None: - self.adata = ad.read_h5ad(f"{dataset}_{layer}.h5ad") + def setup( + self, + cache: dict[tuple[Dataset, KeyCount], ad.AnnData], + dataset: Dataset, + layer: KeyCount, + ) -> None: + self.adata = cache[dataset, layer].copy() def time_filter_cells(self, *_) -> None: sc.pp.filter_cells(self.adata, min_genes=100) @@ -75,23 +83,24 @@ def peakmem_scrublet(self, *_) -> None: class PreprocessingCountsRngSuite: # noqa: D101 - params: tuple[list[Dataset], list[str], list[str]] = ( + params: tuple[list[Dataset], list[str]] = ( ["pbmc68k_reduced", "pbmc3k"], ["rng", "random_state"], ) param_names = ("dataset", "layer") - def setup_cache(self) -> None: - for dataset in self.params[0]: - cache_adata(dataset, "counts") + def setup_cache(self) -> dict[Dataset, ad.AnnData]: + return {dataset: make_adata(dataset, "counts") for dataset in self.params[0]} - def setup(self, dataset, rng_arg) -> None: + def setup( + self, cache: dict[Dataset, ad.AnnData], dataset: Dataset, rng_arg: str + ) -> None: if ( rng_arg == "rng" and "rng" not in signature(sc.pp.downsample_counts).parameters ): raise NotImplementedError - self.adata = ad.read_h5ad(f"{dataset}_counts.h5ad") + self.adata = cache[dataset].copy() self.rng_kw: Any = {rng_arg: 0} self.total = self.adata.X.sum() / 10 @@ -117,12 +126,19 @@ class FastSuite: ) param_names = ("dataset", "layer") - def setup_cache(self) -> None: - for dataset, layer in product(*self.params): - cache_adata(dataset, layer) + def setup_cache(self) -> dict[tuple[Dataset, KeyCount], ad.AnnData]: + return { + (dataset, layer): make_adata(dataset, layer) + for dataset, layer in product(*self.params) + } - def setup(self, dataset, layer) -> None: - self.adata = ad.read_h5ad(f"{dataset}_{layer}.h5ad") + def setup( + self, + cache: dict[tuple[Dataset, KeyCount], ad.AnnData], + dataset: Dataset, + layer: KeyCount, + ) -> None: + self.adata = cache[dataset, layer].copy() def time_calculate_qc_metrics(self, *_) -> None: sc.pp.calculate_qc_metrics( @@ -159,19 +175,27 @@ class Agg: # noqa: D101 ) param_names = ("agg_name", "use_csc", "use_dask") - def setup_cache(self) -> None: + def setup_cache(self) -> Path: """Without this caching, asv was running several processes which meant the data was repeatedly downloaded.""" adata, _ = get_dataset("lung93k") adata.layers["counts_csc"] = adata.layers["counts"].tocsc() - adata.write_zarr("lung93k.zarr") - - def setup(self, agg_name: AggType, use_csc: bool, use_dask: bool) -> None: # noqa: FBT001 + path = Path("lung93k.zarr").resolve() + adata.write_zarr(path) + return path + + def setup( + self, + path: Path, + agg_name: AggType, + use_csc: bool, # noqa: FBT001 + use_dask: bool, # noqa: FBT001 + ) -> None: counts_src_key = "counts_csc" if use_csc else "counts" if use_dask: if agg_name == "median": # Skip this one: https://asv.readthedocs.io/en/stable/writing_benchmarks.html#setup-and-teardown-functions raise NotImplementedError() - z = zarr.open("lung93k.zarr") + z = zarr.open(path) self.adata = ad.AnnData( obs=ad.io.read_elem(z["obs"]), var=ad.io.read_elem(z["var"]), @@ -183,7 +207,7 @@ def setup(self, agg_name: AggType, use_csc: bool, use_dask: bool) -> None: # no X=ad.experimental.read_elem_lazy(z["X"]), ) else: - self.adata = ad.read_zarr("lung93k.zarr") + self.adata = ad.read_zarr(path) if counts_src_key != "counts": self.adata.layers["counts"] = self.adata.layers[counts_src_key] del self.adata.layers[counts_src_key] diff --git a/benchmarks/benchmarks/preprocessing_log.py b/benchmarks/benchmarks/preprocessing_log.py index 350bb66883..78e8aff449 100644 --- a/benchmarks/benchmarks/preprocessing_log.py +++ b/benchmarks/benchmarks/preprocessing_log.py @@ -6,6 +6,7 @@ from __future__ import annotations from itertools import product +from pathlib import Path from typing import TYPE_CHECKING import anndata as ad @@ -36,14 +37,20 @@ class PreprocessingSuite: # noqa: D101 params = params param_names = param_names - def setup_cache(self) -> None: + def setup_cache(self) -> dict[tuple[Dataset, KeyX], ad.AnnData]: """Without this caching, asv was running several processes which meant the data was repeatedly downloaded.""" - for dataset, layer in product(*self.params): - adata, _ = get_dataset(dataset, layer=layer) - adata.write_h5ad(f"{dataset}_{layer}.h5ad") + return { + (dataset, layer): get_dataset(dataset, layer=layer)[0] + for dataset, layer in product(*self.params) + } - def setup(self, dataset, layer) -> None: - self.adata = ad.read_h5ad(f"{dataset}_{layer}.h5ad") + def setup( + self, + cache: dict[tuple[Dataset, KeyX], ad.AnnData], + dataset: Dataset, + layer: KeyX, + ) -> None: + self.adata = cache[dataset, layer].copy() def time_pca(self, *_) -> None: sc.pp.pca(self.adata, svd_solver="arpack") @@ -71,16 +78,18 @@ class HVGSuite: # noqa: D101 params = (["seurat_v3", "cell_ranger", "seurat"], [True, False]) param_names = ("flavor", "use_dask") - def setup_cache(self) -> None: + def setup_cache(self) -> tuple[ad.AnnData, Path]: """Without this caching, asv was running several processes which meant the data was repeatedly downloaded.""" adata, _ = get_dataset("lung93k") - adata.write_zarr("lung93k.zarr") obs = np.arange(adata.shape[0]) np.random.default_rng().shuffle(obs) - adata[obs].write_zarr("lung93k_shuffled.zarr") + path = Path("lung93k_shuffled.zarr").resolve() + adata[obs].write_zarr(path) + return adata, path def setup( self, + cache: tuple[ad.AnnData, Path], flavor: Literal["seurat_v3", "cell_ranger", "seurat"], use_dask: bool, # noqa: FBT001 ) -> None: @@ -88,7 +97,7 @@ def setup( if flavor != "seurat_v3": # This benchmark only really makes sense for seurat v3 as that has been optimized. raise NotImplementedError() - z = zarr.open("lung93k_shuffled.zarr") + z = zarr.open(cache[1]) self.adata = ad.AnnData( obs=ad.io.read_elem(z["obs"]), var=ad.io.read_elem(z["var"]), @@ -102,7 +111,7 @@ def setup( self.adata.obs["PatientNumber"].isin(["1", "2", "3"]) ].copy() else: - self.adata = ad.read_zarr("lung93k.zarr") + self.adata = cache[0].copy() sc.pp.filter_genes(self.adata, min_cells=3) self.flavor = flavor diff --git a/benchmarks/benchmarks/tools.py b/benchmarks/benchmarks/tools.py index 1d7e66e18b..4928ae32c4 100644 --- a/benchmarks/benchmarks/tools.py +++ b/benchmarks/benchmarks/tools.py @@ -5,50 +5,53 @@ from __future__ import annotations -import anndata as ad +from typing import TYPE_CHECKING import scanpy as sc from ._utils import pbmc3k, pbmc68k_reduced, to_off_axis +if TYPE_CHECKING: + import anndata as ad + class ToolsSuite: # noqa: D101 - def setup_cache(self) -> None: + def setup_cache(self) -> ad.AnnData: adata = pbmc68k_reduced() assert "X_pca" in adata.obsm - adata.write_h5ad("adata.h5ad") + return adata - def setup(self) -> None: - self.adata = ad.read_h5ad("adata.h5ad") + def setup(self, adata: ad.AnnData) -> None: + self.adata = adata.copy() - def time_umap(self) -> None: + def time_umap(self, *_) -> None: sc.tl.umap(self.adata, rng=None) - def peakmem_umap(self) -> None: + def peakmem_umap(self, *_) -> None: sc.tl.umap(self.adata, rng=None) - def time_diffmap(self) -> None: + def time_diffmap(self, *_) -> None: sc.tl.diffmap(self.adata) - def peakmem_diffmap(self) -> None: + def peakmem_diffmap(self, *_) -> None: sc.tl.diffmap(self.adata) - def time_leiden(self) -> None: + def time_leiden(self, *_) -> None: sc.tl.leiden(self.adata, flavor="igraph") - def peakmem_leiden(self) -> None: + def peakmem_leiden(self, *_) -> None: sc.tl.leiden(self.adata, flavor="igraph") - def time_rank_genes_groups(self) -> None: + def time_rank_genes_groups(self, *_) -> None: sc.tl.rank_genes_groups(self.adata, "bulk_labels", method="wilcoxon") - def peakmem_rank_genes_groups(self) -> None: + def peakmem_rank_genes_groups(self, *_) -> None: sc.tl.rank_genes_groups(self.adata, "bulk_labels", method="wilcoxon") - def time_combat(self) -> None: + def time_combat(self, *_) -> None: sc.pp.combat(self.adata, key="bulk_labels") - def peakmem_combat(self) -> None: + def peakmem_combat(self, *_) -> None: sc.pp.combat(self.adata, key="bulk_labels") @@ -63,14 +66,14 @@ class ScoreGenesSuite: params: tuple[str, ...] = ("pbmc3k", "pbmc3k-off-axis") param_names = ("layout",) - def setup_cache(self) -> None: + def setup_cache(self) -> dict[str, ad.AnnData]: adata = pbmc3k() - adata.write_h5ad("pbmc3k.h5ad") + adata_orig = adata.copy() adata.X = to_off_axis(adata.X) - adata.write_h5ad("pbmc3k-off-axis.h5ad") + return {"pbmc3k": adata_orig, "pbmc3k-off-axis": adata} - def setup(self, layout: str) -> None: - self.adata = ad.read_h5ad(f"{layout}.h5ad") + def setup(self, cache: dict[str, ad.AnnData], layout: str) -> None: + self.adata = cache[layout].copy() self.gene_list = self.adata.var_names[:100].tolist() # warm up the numba JIT (score_genes -> _sparse_nanmean) so compilation # is excluded from the timing From 5ddbf61e1169b09720cf08411f13e03c6733b7ad Mon Sep 17 00:00:00 2001 From: "Philipp A." Date: Tue, 30 Jun 2026 13:47:27 +0200 Subject: [PATCH 2/4] revert most of it --- benchmarks/benchmarks/preprocessing_counts.py | 72 +++++++------------ benchmarks/benchmarks/preprocessing_log.py | 31 +++----- benchmarks/benchmarks/tools.py | 26 +++---- 3 files changed, 48 insertions(+), 81 deletions(-) diff --git a/benchmarks/benchmarks/preprocessing_counts.py b/benchmarks/benchmarks/preprocessing_counts.py index 789cf229d9..5c5114d902 100644 --- a/benchmarks/benchmarks/preprocessing_counts.py +++ b/benchmarks/benchmarks/preprocessing_counts.py @@ -7,7 +7,6 @@ from inspect import signature from itertools import product -from pathlib import Path from typing import TYPE_CHECKING import anndata as ad @@ -25,12 +24,12 @@ from ._utils import Dataset, KeyCount -def make_adata(dataset: Dataset, layer: KeyCount) -> ad.AnnData: +def cache_adata(dataset: Dataset, layer: KeyCount) -> None: """Without this caching, asv was running several processes which meant the data was repeatedly downloaded.""" adata, batch_key = get_count_dataset(dataset, layer=layer) assert "lop1p" not in adata.uns adata.uns["batch_key"] = batch_key - return adata + adata.write_h5ad(f"{dataset}_{layer}.h5ad") class PreprocessingCountsSuite: # noqa: D101 @@ -40,19 +39,12 @@ class PreprocessingCountsSuite: # noqa: D101 ) param_names = ("dataset", "layer") - def setup_cache(self) -> dict[tuple[Dataset, KeyCount], ad.AnnData]: - return { - (dataset, layer): make_adata(dataset, layer) - for dataset, layer in product(*self.params) - } + def setup_cache(self) -> None: + for dataset, layer in product(*self.params): + cache_adata(dataset, layer) - def setup( - self, - cache: dict[tuple[Dataset, KeyCount], ad.AnnData], - dataset: Dataset, - layer: KeyCount, - ) -> None: - self.adata = cache[dataset, layer].copy() + def setup(self, dataset, layer) -> None: + self.adata = ad.read_h5ad(f"{dataset}_{layer}.h5ad") def time_filter_cells(self, *_) -> None: sc.pp.filter_cells(self.adata, min_genes=100) @@ -83,24 +75,23 @@ def peakmem_scrublet(self, *_) -> None: class PreprocessingCountsRngSuite: # noqa: D101 - params: tuple[list[Dataset], list[str]] = ( + params: tuple[list[Dataset], list[str], list[str]] = ( ["pbmc68k_reduced", "pbmc3k"], ["rng", "random_state"], ) param_names = ("dataset", "layer") - def setup_cache(self) -> dict[Dataset, ad.AnnData]: - return {dataset: make_adata(dataset, "counts") for dataset in self.params[0]} + def setup_cache(self) -> None: + for dataset in self.params[0]: + cache_adata(dataset, "counts") - def setup( - self, cache: dict[Dataset, ad.AnnData], dataset: Dataset, rng_arg: str - ) -> None: + def setup(self, dataset, rng_arg) -> None: if ( rng_arg == "rng" and "rng" not in signature(sc.pp.downsample_counts).parameters ): raise NotImplementedError - self.adata = cache[dataset].copy() + self.adata = ad.read_h5ad(f"{dataset}_counts.h5ad") self.rng_kw: Any = {rng_arg: 0} self.total = self.adata.X.sum() / 10 @@ -126,19 +117,12 @@ class FastSuite: ) param_names = ("dataset", "layer") - def setup_cache(self) -> dict[tuple[Dataset, KeyCount], ad.AnnData]: - return { - (dataset, layer): make_adata(dataset, layer) - for dataset, layer in product(*self.params) - } + def setup_cache(self) -> None: + for dataset, layer in product(*self.params): + cache_adata(dataset, layer) - def setup( - self, - cache: dict[tuple[Dataset, KeyCount], ad.AnnData], - dataset: Dataset, - layer: KeyCount, - ) -> None: - self.adata = cache[dataset, layer].copy() + def setup(self, dataset, layer) -> None: + self.adata = ad.read_h5ad(f"{dataset}_{layer}.h5ad") def time_calculate_qc_metrics(self, *_) -> None: sc.pp.calculate_qc_metrics( @@ -175,27 +159,19 @@ class Agg: # noqa: D101 ) param_names = ("agg_name", "use_csc", "use_dask") - def setup_cache(self) -> Path: + def setup_cache(self) -> None: """Without this caching, asv was running several processes which meant the data was repeatedly downloaded.""" adata, _ = get_dataset("lung93k") adata.layers["counts_csc"] = adata.layers["counts"].tocsc() - path = Path("lung93k.zarr").resolve() - adata.write_zarr(path) - return path - - def setup( - self, - path: Path, - agg_name: AggType, - use_csc: bool, # noqa: FBT001 - use_dask: bool, # noqa: FBT001 - ) -> None: + adata.write_zarr("lung93k.zarr") + + def setup(self, agg_name: AggType, use_csc: bool, use_dask: bool) -> None: # noqa: FBT001 counts_src_key = "counts_csc" if use_csc else "counts" if use_dask: if agg_name == "median": # Skip this one: https://asv.readthedocs.io/en/stable/writing_benchmarks.html#setup-and-teardown-functions raise NotImplementedError() - z = zarr.open(path) + z = zarr.open("lung93k.zarr") self.adata = ad.AnnData( obs=ad.io.read_elem(z["obs"]), var=ad.io.read_elem(z["var"]), @@ -207,7 +183,7 @@ def setup( X=ad.experimental.read_elem_lazy(z["X"]), ) else: - self.adata = ad.read_zarr(path) + self.adata = ad.read_zarr("lung93k.zarr") if counts_src_key != "counts": self.adata.layers["counts"] = self.adata.layers[counts_src_key] del self.adata.layers[counts_src_key] diff --git a/benchmarks/benchmarks/preprocessing_log.py b/benchmarks/benchmarks/preprocessing_log.py index 78e8aff449..350bb66883 100644 --- a/benchmarks/benchmarks/preprocessing_log.py +++ b/benchmarks/benchmarks/preprocessing_log.py @@ -6,7 +6,6 @@ from __future__ import annotations from itertools import product -from pathlib import Path from typing import TYPE_CHECKING import anndata as ad @@ -37,20 +36,14 @@ class PreprocessingSuite: # noqa: D101 params = params param_names = param_names - def setup_cache(self) -> dict[tuple[Dataset, KeyX], ad.AnnData]: + def setup_cache(self) -> None: """Without this caching, asv was running several processes which meant the data was repeatedly downloaded.""" - return { - (dataset, layer): get_dataset(dataset, layer=layer)[0] - for dataset, layer in product(*self.params) - } + for dataset, layer in product(*self.params): + adata, _ = get_dataset(dataset, layer=layer) + adata.write_h5ad(f"{dataset}_{layer}.h5ad") - def setup( - self, - cache: dict[tuple[Dataset, KeyX], ad.AnnData], - dataset: Dataset, - layer: KeyX, - ) -> None: - self.adata = cache[dataset, layer].copy() + def setup(self, dataset, layer) -> None: + self.adata = ad.read_h5ad(f"{dataset}_{layer}.h5ad") def time_pca(self, *_) -> None: sc.pp.pca(self.adata, svd_solver="arpack") @@ -78,18 +71,16 @@ class HVGSuite: # noqa: D101 params = (["seurat_v3", "cell_ranger", "seurat"], [True, False]) param_names = ("flavor", "use_dask") - def setup_cache(self) -> tuple[ad.AnnData, Path]: + def setup_cache(self) -> None: """Without this caching, asv was running several processes which meant the data was repeatedly downloaded.""" adata, _ = get_dataset("lung93k") + adata.write_zarr("lung93k.zarr") obs = np.arange(adata.shape[0]) np.random.default_rng().shuffle(obs) - path = Path("lung93k_shuffled.zarr").resolve() - adata[obs].write_zarr(path) - return adata, path + adata[obs].write_zarr("lung93k_shuffled.zarr") def setup( self, - cache: tuple[ad.AnnData, Path], flavor: Literal["seurat_v3", "cell_ranger", "seurat"], use_dask: bool, # noqa: FBT001 ) -> None: @@ -97,7 +88,7 @@ def setup( if flavor != "seurat_v3": # This benchmark only really makes sense for seurat v3 as that has been optimized. raise NotImplementedError() - z = zarr.open(cache[1]) + z = zarr.open("lung93k_shuffled.zarr") self.adata = ad.AnnData( obs=ad.io.read_elem(z["obs"]), var=ad.io.read_elem(z["var"]), @@ -111,7 +102,7 @@ def setup( self.adata.obs["PatientNumber"].isin(["1", "2", "3"]) ].copy() else: - self.adata = cache[0].copy() + self.adata = ad.read_zarr("lung93k.zarr") sc.pp.filter_genes(self.adata, min_cells=3) self.flavor = flavor diff --git a/benchmarks/benchmarks/tools.py b/benchmarks/benchmarks/tools.py index 4928ae32c4..9061300e5a 100644 --- a/benchmarks/benchmarks/tools.py +++ b/benchmarks/benchmarks/tools.py @@ -5,24 +5,24 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from pathlib import Path + +import anndata as ad import scanpy as sc from ._utils import pbmc3k, pbmc68k_reduced, to_off_axis -if TYPE_CHECKING: - import anndata as ad - class ToolsSuite: # noqa: D101 - def setup_cache(self) -> ad.AnnData: + def setup_cache(self) -> Path: adata = pbmc68k_reduced() assert "X_pca" in adata.obsm - return adata + adata.write_h5ad(path := Path("adata.h5ad")) + return path - def setup(self, adata: ad.AnnData) -> None: - self.adata = adata.copy() + def setup(self, path: Path) -> None: + self.adata = ad.read_h5ad(path) def time_umap(self, *_) -> None: sc.tl.umap(self.adata, rng=None) @@ -66,14 +66,14 @@ class ScoreGenesSuite: params: tuple[str, ...] = ("pbmc3k", "pbmc3k-off-axis") param_names = ("layout",) - def setup_cache(self) -> dict[str, ad.AnnData]: + def setup_cache(self) -> None: adata = pbmc3k() - adata_orig = adata.copy() + adata.write_h5ad("pbmc3k.h5ad") adata.X = to_off_axis(adata.X) - return {"pbmc3k": adata_orig, "pbmc3k-off-axis": adata} + adata.write_h5ad("pbmc3k-off-axis.h5ad") - def setup(self, cache: dict[str, ad.AnnData], layout: str) -> None: - self.adata = cache[layout].copy() + def setup(self, layout: str) -> None: + self.adata = ad.read_h5ad(f"{layout}.h5ad") self.gene_list = self.adata.var_names[:100].tolist() # warm up the numba JIT (score_genes -> _sparse_nanmean) so compilation # is excluded from the timing From e6f092ca3c6a282f957af69ce7bd55d85d08e459 Mon Sep 17 00:00:00 2001 From: "Philipp A." Date: Tue, 30 Jun 2026 13:48:39 +0200 Subject: [PATCH 3/4] move comment --- benchmarks/benchmarks/preprocessing_counts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/benchmarks/preprocessing_counts.py b/benchmarks/benchmarks/preprocessing_counts.py index 5c5114d902..99a7ef0b53 100644 --- a/benchmarks/benchmarks/preprocessing_counts.py +++ b/benchmarks/benchmarks/preprocessing_counts.py @@ -25,7 +25,6 @@ def cache_adata(dataset: Dataset, layer: KeyCount) -> None: - """Without this caching, asv was running several processes which meant the data was repeatedly downloaded.""" adata, batch_key = get_count_dataset(dataset, layer=layer) assert "lop1p" not in adata.uns adata.uns["batch_key"] = batch_key @@ -40,6 +39,7 @@ class PreprocessingCountsSuite: # noqa: D101 param_names = ("dataset", "layer") def setup_cache(self) -> None: + """Without this caching, asv was running several processes which meant the data was repeatedly downloaded.""" for dataset, layer in product(*self.params): cache_adata(dataset, layer) From 5ae983e42af0dada572325e17b17c23eee3b50e4 Mon Sep 17 00:00:00 2001 From: "Philipp A." Date: Tue, 30 Jun 2026 13:50:33 +0200 Subject: [PATCH 4/4] comment --- benchmarks/benchmarks/tools.py | 1 + 1 file changed, 1 insertion(+) diff --git a/benchmarks/benchmarks/tools.py b/benchmarks/benchmarks/tools.py index 9061300e5a..41563293bb 100644 --- a/benchmarks/benchmarks/tools.py +++ b/benchmarks/benchmarks/tools.py @@ -19,6 +19,7 @@ def setup_cache(self) -> Path: adata = pbmc68k_reduced() assert "X_pca" in adata.obsm adata.write_h5ad(path := Path("adata.h5ad")) + # we need to have a parameter, else asv doesn’t run `setup_cache` before `setup` return path def setup(self, path: Path) -> None: