diff --git a/.gitignore b/.gitignore index b6e4761..f300f72 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,7 @@ dmypy.json # Pyre type checker .pyre/ + +# asv +results/ +html/ diff --git a/README.md b/README.md index ac3240c..5291894 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,18 @@ ## nx-parallel -nx-parallel is a NetworkX backend plugin that uses joblib and multiprocessing for parallelization. This project aims to provide parallelized implementations of various NetworkX functions to improve performance. +nx-parallel is a NetworkX backend that uses joblib for parallelization. This project aims to provide parallelized implementations of various NetworkX functions to improve performance. ## Features nx-parallel provides parallelized implementations for the following NetworkX functions: -``` -├── centrality -│ ├── betweenness_centrality -│ ├── closeness_vitality -├── tournament -│ ├── is_reachable -├── efficiency_measures -│ ├── local_efficiency -``` +- [betweeness_centrality](https://github.com/networkx/nx-parallel/blob/main/nx_parallel/algorithms/centrality/betweenness.py#L17) +- [local_efficiency](https://github.com/networkx/nx-parallel/blob/main/nx_parallel/algorithms/efficiency_measures.py#L12) +- [number_of_isolates](https://github.com/networkx/nx-parallel/blob/main/nx_parallel/algorithms/isolate.py#L9) +- [all_pairs_bellman_ford_path](https://github.com/networkx/nx-parallel/blob/main/nx_parallel/algorithms/shortest_paths/weighted.py#L9) +- [is_reachable](https://github.com/networkx/nx-parallel/blob/main/nx_parallel/algorithms/tournament.py#L11) +- [tournament_is_strongly_connected](https://github.com/networkx/nx-parallel/blob/main/nx_parallel/algorithms/tournament.py#L103) +- [closeness_vitality](https://github.com/networkx/nx-parallel/blob/main/nx_parallel/algorithms/vitality.py#L9) ![alt text](timing/heatmap_all_functions.png) diff --git a/benchmarks/README.md b/benchmarks/README.md new file mode 100644 index 0000000..4097852 --- /dev/null +++ b/benchmarks/README.md @@ -0,0 +1,22 @@ +## Preview benchmarks locally + +1. clone this repo +2. `cd benchmarks` +3. If you are working on a different branch then update the `branches` in the `asv.conf.json` file. +4. `asv run` will run the benchmarks on the last commit + - or use `asv continuous base_commit_hash test_commit_hash` to run the benchmark to compare two commits + - or `asv run -b -k ` to run a particular benchmark. + - if you are running benchmarks for the first time, you will be asked to enter your machine information after this command. +5. `asv publish` will create a `html` folder with the results +6. `asv preview` will host the results locally at http://127.0.0.1:8080/ + +
+ +## Structure of benchmarks + +* Each `bench_` file corresponds to a folder/file in the [networkx/algorithms](https://github.com/networkx/networkx/tree/main/networkx/algorithms) directory in NetworkX +* Each class inside a `bench_` file corresponds to every file in a folder(one class if it’s a file) in networkx/algorithms +* The class name corresponds to the file name and the `x` in `bench_x` corresponds to the folder name(class name and `x` are the same if it’s a file in networkx/algorithms) +* Each `time_` function corresponds to each function in the file. +* For other folders in [networkx/networkx](https://github.com/networkx/networkx/tree/main/networkx) like `generators`, `classes`, `linalg`, `utils` etc. we can have different `bench_` files for each of them having different classes corresponding to different files in each of these folders. +* For example: `bench_centrality.py` corresponds to `networkx/algorithms/centrality` folder in NetworkX and the `Betweenness` class inside it corresponds to the `betweenness.py` file in `networkx/algorithms/centrality` folder in NetworkX. And the `time_betweenness_centrality` function corresponds to the `betweenness_centrality` function. diff --git a/benchmarks/asv.conf.json b/benchmarks/asv.conf.json new file mode 100644 index 0000000..2cc72a7 --- /dev/null +++ b/benchmarks/asv.conf.json @@ -0,0 +1,20 @@ +{ + "version": 1, + "project": "nx-parallel", + "project_url": "https://github.com/networkx/nx-parallel", + "repo": "..", + "branches": ["main"], + "build_command": [ + "python -m pip install build", + "python -m build --wheel -o {build_cache_dir} {build_dir}" + ], + "dvcs": "git", + "environment_type": "virtualenv", + "show_commit_url": "https://github.com/networkx/nx-parallel/commit/", + "matrix": {"networkx": ["3.2"], "nx-parallel": []}, + "benchmark_dir": "benchmarks", + "env_dir": "env", + "results_dir": "results", + "html_dir": "html", + "build_cache_size": 8 +} diff --git a/benchmarks/benchmarks/__init__.py b/benchmarks/benchmarks/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/benchmarks/benchmarks/__init__.py @@ -0,0 +1 @@ + diff --git a/benchmarks/benchmarks/bench_centrality.py b/benchmarks/benchmarks/bench_centrality.py new file mode 100644 index 0000000..885dd12 --- /dev/null +++ b/benchmarks/benchmarks/bench_centrality.py @@ -0,0 +1,11 @@ +from .common import * +import networkx as nx + + +class Betweenness(Benchmark): + params = [(backends), (num_nodes), (edge_prob)] + param_names = ["backend", "num_nodes", "edge_prob"] + + def time_betweenness_centrality(self, backend, num_nodes, edge_prob): + G = get_cached_gnp_random_graph(num_nodes, edge_prob) + _ = nx.betweenness_centrality(G, backend=backend) diff --git a/benchmarks/benchmarks/bench_efficiency_measures.py b/benchmarks/benchmarks/bench_efficiency_measures.py new file mode 100644 index 0000000..aa0659f --- /dev/null +++ b/benchmarks/benchmarks/bench_efficiency_measures.py @@ -0,0 +1,11 @@ +from .common import * +import networkx as nx + + +class EfficiencyMeasures(Benchmark): + params = [(backends), (num_nodes), (edge_prob)] + param_names = ["backend", "num_nodes", "edge_prob"] + + def time_local_efficiency(self, backend, num_nodes, edge_prob): + G = get_cached_gnp_random_graph(num_nodes, edge_prob) + _ = nx.local_efficiency(G, backend=backend) diff --git a/benchmarks/benchmarks/bench_isolate.py b/benchmarks/benchmarks/bench_isolate.py new file mode 100644 index 0000000..a011847 --- /dev/null +++ b/benchmarks/benchmarks/bench_isolate.py @@ -0,0 +1,11 @@ +from .common import * +import networkx as nx + + +class Isolate(Benchmark): + params = [(backends), (num_nodes), (edge_prob)] + param_names = ["backend", "num_nodes", "edge_prob"] + + def time_number_of_isolates(self, backend, num_nodes, edge_prob): + G = get_cached_gnp_random_graph(num_nodes, edge_prob) + _ = nx.number_of_isolates(G, backend=backend) diff --git a/benchmarks/benchmarks/bench_shortest_paths.py b/benchmarks/benchmarks/bench_shortest_paths.py new file mode 100644 index 0000000..dead06b --- /dev/null +++ b/benchmarks/benchmarks/bench_shortest_paths.py @@ -0,0 +1,11 @@ +from .common import * +import networkx as nx + + +class Weighted(Benchmark): + params = [(backends), (num_nodes), (edge_prob)] + param_names = ["backend", "num_nodes", "edge_prob"] + + def time_all_pairs_bellman_ford_path(self, backend, num_nodes, edge_prob): + G = get_cached_gnp_random_graph(num_nodes, edge_prob, is_weighted=True) + _ = dict(nx.all_pairs_bellman_ford_path(G, backend=backend)) diff --git a/benchmarks/benchmarks/bench_tournament.py b/benchmarks/benchmarks/bench_tournament.py new file mode 100644 index 0000000..1337b76 --- /dev/null +++ b/benchmarks/benchmarks/bench_tournament.py @@ -0,0 +1,11 @@ +from .common import * +import networkx as nx + + +class Tournament(Benchmark): + params = [(backends), (num_nodes)] + param_names = ["backend", "num_nodes"] + + def time_is_reachable(self, backend, num_nodes): + G = nx.tournament.random_tournament(num_nodes, seed=42) + _ = nx.tournament.is_reachable(G, 1, num_nodes, backend=backend) diff --git a/benchmarks/benchmarks/bench_vitality.py b/benchmarks/benchmarks/bench_vitality.py new file mode 100644 index 0000000..57ff402 --- /dev/null +++ b/benchmarks/benchmarks/bench_vitality.py @@ -0,0 +1,11 @@ +from .common import * +import networkx as nx + + +class Vitality(Benchmark): + params = [(backends), (num_nodes), (edge_prob)] + param_names = ["backend", "num_nodes", "edge_prob"] + + def time_closeness_vitality(self, backend, num_nodes, edge_prob): + G = get_cached_gnp_random_graph(num_nodes, edge_prob) + _ = nx.closeness_vitality(G, backend=backend) diff --git a/benchmarks/benchmarks/common.py b/benchmarks/benchmarks/common.py new file mode 100644 index 0000000..0d84f0b --- /dev/null +++ b/benchmarks/benchmarks/common.py @@ -0,0 +1,33 @@ +from functools import lru_cache +from pathlib import Path +import random + +import networkx as nx + +__all__ = [ + "backends", + "num_nodes", + "edge_prob", + "get_cached_gnp_random_graph", + "Benchmark", +] + +CACHE_ROOT = Path(__file__).resolve().parent.parent / "env" / "nxp_benchdata" + +backends = ["parallel", None] +num_nodes = [50, 100, 200, 400, 800] +edge_prob = [0.8, 0.6, 0.4, 0.2] + + +@lru_cache(typed=True) +def get_cached_gnp_random_graph(num_nodes, edge_prob, is_weighted=False): + G = nx.fast_gnp_random_graph(num_nodes, edge_prob, seed=42, directed=False) + if is_weighted: + random.seed(42) + for (u, v) in G.edges(): + G.edges[u, v]["weight"] = random.random() + return G + + +class Benchmark: + pass diff --git a/pyproject.toml b/pyproject.toml index 5f09069..61e12ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ developer = [ 'pytest', ] -[project.entry-points."networkx.plugins"] +[project.entry-points."networkx.backends"] parallel = "nx_parallel.interface:Dispatcher" [tool.setuptools]