Skip to content

Commit 8814108

Browse files
committed
ci: split runtime benchmark into 3 parallel size chunks
1 parent 5c4e5b4 commit 8814108

2 files changed

Lines changed: 127 additions & 24 deletions

File tree

.github/workflows/benchmark.yml

Lines changed: 93 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ jobs:
6969
retention-days: 1
7070

7171
# ══════════════════════════════════════════════════════════════════════
72-
# 2. Run each benchmark in parallel
72+
# 2a. Non-runtime benchmarks in parallel
7373
# ══════════════════════════════════════════════════════════════════════
7474
benchmark:
7575
needs: build
@@ -78,7 +78,7 @@ jobs:
7878
strategy:
7979
fail-fast: false
8080
matrix:
81-
suite: [correctness, runtime, incremental, epsilon]
81+
suite: [correctness, incremental, epsilon]
8282
steps:
8383
- uses: actions/checkout@v4
8484

@@ -116,11 +116,67 @@ jobs:
116116
path: louvain/results/*.csv
117117
retention-days: 30
118118

119+
# ══════════════════════════════════════════════════════════════════════
120+
# 2b. Runtime benchmark split by graph size
121+
# ══════════════════════════════════════════════════════════════════════
122+
runtime:
123+
needs: build
124+
runs-on: ubuntu-latest
125+
timeout-minutes: 180
126+
strategy:
127+
fail-fast: false
128+
matrix:
129+
include:
130+
- chunk: small
131+
sizes: "1000,5000"
132+
- chunk: medium
133+
sizes: "10000,50000"
134+
- chunk: large
135+
sizes: "100000,250000"
136+
steps:
137+
- uses: actions/checkout@v4
138+
139+
- name: Set up Python
140+
uses: actions/setup-python@v5
141+
with:
142+
python-version: '3.11'
143+
144+
- name: Install Python dependencies
145+
working-directory: louvain
146+
run: |
147+
python -m pip install --upgrade pip
148+
pip install -r ../requirements-common.txt -r requirements.txt
149+
150+
- name: Download build artifacts
151+
uses: actions/download-artifact@v4
152+
with:
153+
name: louvain-build
154+
path: louvain/build/
155+
156+
- name: Make binaries executable
157+
run: chmod +x louvain/build/*
158+
159+
- name: Run runtime benchmark (${{ matrix.chunk }})
160+
working-directory: louvain
161+
run: |
162+
mkdir -p results
163+
python scripts/benchmark.py --only runtime \
164+
--sizes ${{ matrix.sizes }} \
165+
--output-suffix _${{ matrix.chunk }}
166+
167+
- name: Upload runtime ${{ matrix.chunk }} results
168+
uses: actions/upload-artifact@v4
169+
if: always()
170+
with:
171+
name: results-runtime-${{ matrix.chunk }}
172+
path: louvain/results/*.csv
173+
retention-days: 30
174+
119175
# ══════════════════════════════════════════════════════════════════════
120176
# 3. Merge results, generate plots, and commit
121177
# ══════════════════════════════════════════════════════════════════════
122178
visualize:
123-
needs: benchmark
179+
needs: [benchmark, runtime]
124180
if: always()
125181
runs-on: ubuntu-latest
126182
timeout-minutes: 30
@@ -146,10 +202,24 @@ jobs:
146202
path: louvain/results/
147203
continue-on-error: true
148204

149-
- name: Download runtime results
205+
- name: Download runtime (small) results
150206
uses: actions/download-artifact@v4
151207
with:
152-
name: results-runtime
208+
name: results-runtime-small
209+
path: louvain/results/
210+
continue-on-error: true
211+
212+
- name: Download runtime (medium) results
213+
uses: actions/download-artifact@v4
214+
with:
215+
name: results-runtime-medium
216+
path: louvain/results/
217+
continue-on-error: true
218+
219+
- name: Download runtime (large) results
220+
uses: actions/download-artifact@v4
221+
with:
222+
name: results-runtime-large
153223
path: louvain/results/
154224
continue-on-error: true
155225

@@ -167,6 +237,24 @@ jobs:
167237
path: louvain/results/
168238
continue-on-error: true
169239

240+
- name: Merge runtime CSV chunks
241+
working-directory: louvain
242+
run: |
243+
python -c "
244+
import pandas as pd, glob, os
245+
parts = sorted(glob.glob('results/runtime_*.csv'))
246+
if parts:
247+
df = pd.concat([pd.read_csv(p) for p in parts], ignore_index=True)
248+
df.to_csv('results/runtime.csv', index=False)
249+
print(f'Merged {len(parts)} runtime chunks into runtime.csv ({len(df)} rows)')
250+
for p in parts:
251+
os.remove(p)
252+
elif os.path.exists('results/runtime.csv'):
253+
print('runtime.csv already present, skipping merge')
254+
else:
255+
print('WARNING: no runtime CSVs found')
256+
"
257+
170258
- name: Generate visualizations
171259
working-directory: louvain
172260
run: python scripts/visualize.py

louvain/scripts/benchmark.py

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ def run_benchmark_correctness(n_trials=100):
290290

291291
# ── runtime scalability ─────────────────────────────────────────────────
292292

293-
def run_benchmark_runtime(n_trials=10, sizes=None):
293+
def run_benchmark_runtime(n_trials=10, sizes=None, output_suffix=''):
294294
"""Run runtime scalability benchmark.
295295
296296
Benchmark fairness notes:
@@ -458,8 +458,9 @@ def run_benchmark_runtime(n_trials=10, sizes=None):
458458
print(', '.join(summary_parts))
459459

460460
df = pd.DataFrame(results)
461-
df.to_csv('results/runtime.csv', index=False)
462-
print(f"\nSaved to results/runtime.csv")
461+
out_name = f'results/runtime{output_suffix}.csv'
462+
df.to_csv(out_name, index=False)
463+
print(f"\nSaved to {out_name}")
463464
return df
464465

465466

@@ -752,34 +753,48 @@ def run_benchmark_epsilon(n_trials=10, sizes=None):
752753

753754
# ── main ─────────────────────────────────────────────────────────────────
754755

755-
BENCHMARKS = {
756-
'correctness': lambda q: run_benchmark_correctness(n_trials=5 if q else 100),
757-
'runtime': lambda q: run_benchmark_runtime(
758-
n_trials=3 if q else 10,
759-
sizes=[1000, 5000, 25000] if q else None),
760-
'incremental': lambda q: run_benchmark_incremental(
761-
n_trials=3 if q else 10,
762-
sizes=[100, 500] if q else None),
763-
'epsilon': lambda q: run_benchmark_epsilon(
764-
n_trials=3 if q else 10,
765-
sizes=[1000, 5000, 10000] if q else None),
766-
}
756+
BENCHMARKS = ['correctness', 'runtime', 'incremental', 'epsilon']
767757

768758
if __name__ == '__main__':
769759
parser = argparse.ArgumentParser(description='Louvain benchmark suite')
770760
parser.add_argument('--quick', action='store_true',
771761
help='Run a fast smoke-test (fewer graphs, trials, sizes)')
772-
parser.add_argument('--only', choices=list(BENCHMARKS.keys()),
762+
parser.add_argument('--only', choices=BENCHMARKS,
773763
help='Run only the specified benchmark')
764+
parser.add_argument('--sizes', type=str, default=None,
765+
help='Comma-separated list of graph sizes (for runtime/incremental/epsilon)')
766+
parser.add_argument('--output-suffix', type=str, default='',
767+
help='Suffix appended to output CSV filename (e.g. "_part0")')
774768
args = parser.parse_args()
775769

776770
os.makedirs('results', exist_ok=True)
777771

778772
if args.quick:
779773
print('*** QUICK MODE — reduced trials & sizes ***\n')
780774

775+
sizes_override = [int(s) for s in args.sizes.split(',')] if args.sizes else None
776+
suffix = args.output_suffix
777+
778+
def _run(name):
779+
q = args.quick
780+
if name == 'correctness':
781+
run_benchmark_correctness(n_trials=5 if q else 100)
782+
elif name == 'runtime':
783+
run_benchmark_runtime(
784+
n_trials=3 if q else 10,
785+
sizes=sizes_override or ([1000, 5000, 25000] if q else None),
786+
output_suffix=suffix)
787+
elif name == 'incremental':
788+
run_benchmark_incremental(
789+
n_trials=3 if q else 10,
790+
sizes=sizes_override or ([100, 500] if q else None))
791+
elif name == 'epsilon':
792+
run_benchmark_epsilon(
793+
n_trials=3 if q else 10,
794+
sizes=sizes_override or ([1000, 5000, 10000] if q else None))
795+
781796
if args.only:
782-
BENCHMARKS[args.only](args.quick)
797+
_run(args.only)
783798
else:
784-
for name, fn in BENCHMARKS.items():
785-
fn(args.quick)
799+
for name in BENCHMARKS:
800+
_run(name)

0 commit comments

Comments
 (0)