Skip to content

Commit 4de292f

Browse files
committed
add benchmark doc
1 parent 5e4e7aa commit 4de292f

File tree

3 files changed

+137
-2
lines changed

3 files changed

+137
-2
lines changed

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,11 @@ tests:
5353
tests_nccl:
5454
mpiexec -n $(NUM_PROCESSES) pytest tests_nccl/ --with-mpi
5555

56+
# sphinx-build does not work well with NCCL
5657
doc:
5758
cd docs && rm -rf source/api/generated && rm -rf source/gallery &&\
5859
rm -rf source/tutorials && rm -rf build &&\
59-
cd .. && sphinx-build -b html docs/source docs/build
60+
cd .. && NCCL_PYLOPS_MPI=0 sphinx-build -b html docs/source docs/build
6061

6162
doc_cupy:
6263
cp tutorials_cupy/* tutorials/

pylops_mpi/utils/benchmark.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def _nccl_sync():
2626

2727
def _parse_output_tree(markers: List[str]):
2828
"""This function parses the list of strings gathered during the benchmark call and output them
29-
as one properly formatted string. The format of output string follows the hierachy of function calls
29+
as one properly formatted string. The format of output string follows the hierarchy of function calls
3030
i.e., the nested funtion calls are indented.
3131
3232
Parameters

tutorials/benchmark_example.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
r"""
2+
Benchmark Utility in PyLops-MPI
3+
=========================
4+
This tutorial demonstrates how to use the bencmark utility of PyLops-MPI. It contains various
5+
function calling pattern that may come up during the benchmarking.
6+
"""
7+
import numpy as np
8+
from mpi4py import MPI
9+
from pylops_mpi import DistributedArray, Partition
10+
11+
np.random.seed(42)
12+
rank = MPI.COMM_WORLD.Get_rank()
13+
14+
par = {'global_shape': (500, 501),
15+
'partition': Partition.SCATTER, 'dtype': np.float64,
16+
'axis': 1}
17+
18+
###############################################################################
19+
# Let's start by import the utility
20+
from pylops_mpi.utils.benchmark import benchmark, mark
21+
22+
###############################################################################
23+
# :py:func:`pylops_mpi.utils.benchmark` is a decorator used to decorate any
24+
# function to measure its execution time from start to finish
25+
# :py:func:`pylops_mpi.utils.mark` is a function used inside the benchmark-decorated
26+
# function to provide fine-grain time measurements. Let's start with a simple example
27+
28+
29+
@benchmark
30+
def inner_func(par):
31+
dist_arr = DistributedArray(global_shape=par['global_shape'],
32+
partition=par['partition'],
33+
dtype=par['dtype'], axis=par['axis'])
34+
# may perform computation here
35+
dist_arr.dot(dist_arr)
36+
37+
38+
###############################################################################
39+
# When we call :py:func:`inner_func`, we will see the result
40+
# of the benchmark print to standard output. If we want to customize the
41+
# function name in the printout, we can pass the parameter to the :py:func:`benchmark`
42+
# i.e., :py:func:`@benchmark(description="printout_name")`
43+
44+
inner_func(par)
45+
46+
###############################################################################
47+
# We may want to get the finer time measurement by timing the execution time from arbitary lines
48+
# of code. :py:func:`pylops_mpi.utils.mark` provides such utitlity
49+
50+
51+
@benchmark
52+
def inner_func_with_mark(par):
53+
mark("Begin array constructor")
54+
dist_arr = DistributedArray(global_shape=par['global_shape'],
55+
partition=par['partition'],
56+
dtype=par['dtype'], axis=par['axis'])
57+
mark("Begin dot")
58+
dist_arr.dot(dist_arr)
59+
mark("Finish dot")
60+
61+
62+
###############################################################################
63+
# Now when we run, we get the detail time measurement. Noted that there is a tag
64+
# [decorator] to the function name to distinguish between the start-to-end time measuredment of
65+
# top-level function and those that comes from :py:func:`pylops_mpi.utils.mark`
66+
inner_func_with_mark(par)
67+
68+
###############################################################################
69+
# This utility also supports the nested functions. Let's define the outer function
70+
# that internally calls decorated :py:func:`inner_func_with_mark`
71+
72+
73+
@benchmark
74+
def outer_func_with_mark(par):
75+
mark("Outer func start")
76+
inner_func_with_mark(par)
77+
dist_arr = DistributedArray(global_shape=par['global_shape'],
78+
partition=par['partition'],
79+
dtype=par['dtype'], axis=par['axis'])
80+
dist_arr + dist_arr
81+
mark("Outer func ends")
82+
83+
84+
###############################################################################
85+
# If we run :py:func:`outer_func_with_mark`, we get the time measurement nicely
86+
# printout with the nested indentation to specify that nested calls.
87+
outer_func_with_mark(par)
88+
89+
90+
###############################################################################
91+
# In some cases, we may want to write benchmark output to a text file.
92+
# :py:func:`pylops_mpi.utils.benchmark` also takes the py:class:`logging.Logger`
93+
# in its argument. Let's first import the logging package and construct our logger
94+
95+
import sys
96+
import logging
97+
save_file = True
98+
file_path = "benchmark.log"
99+
100+
###############################################################################
101+
# Here we define a simple :py:func:`make_logger()`. We set the :py:func:`logger.propagate = False`
102+
# isolate the logging of our benchmark from that of the rest of the code
103+
104+
105+
def make_logger(save_file=False, file_path=''):
106+
logger = logging.getLogger(__name__)
107+
logging.basicConfig(filename=file_path if save_file else None, filemode='w', level=logging.INFO, force=True)
108+
logger.propagate = False
109+
if save_file:
110+
handler = logging.FileHandler(file_path, mode='w')
111+
else:
112+
handler = logging.StreamHandler(sys.stdout)
113+
logger.addHandler(handler)
114+
return logger
115+
116+
117+
logger = make_logger(save_file, file_path)
118+
119+
120+
###############################################################################
121+
# Then we can pass the logger to the :py:func:`pylops_mpi.utils.benchmark`
122+
123+
@benchmark(logger=logger)
124+
def inner_func_with_logger(par):
125+
dist_arr = DistributedArray(global_shape=par['global_shape'],
126+
partition=par['partition'],
127+
dtype=par['dtype'], axis=par['axis'])
128+
# may perform computation here
129+
dist_arr.dot(dist_arr)
130+
131+
132+
###############################################################################
133+
# Run this function and observe that the file `benchmark.log` is written.
134+
inner_func_with_logger(par)

0 commit comments

Comments
 (0)