Skip to content

Commit

Permalink
Merge pull request #69 from pypr/improve-profile
Browse files Browse the repository at this point in the history
Improve profile
  • Loading branch information
prabhuramachandran committed Dec 17, 2020
2 parents 9d82e26 + b152908 commit 1d3fe06
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 11 deletions.
4 changes: 4 additions & 0 deletions compyle/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
from .parallel import (
Elementwise, Reduction, Scan, elementwise
)
from .profile import (
get_profile_info, named_profile, profile, profile_ctx, print_profile,
profile_kernel
)
from .translator import (
CConverter, CStructHelper, OpenCLConverter, detect_type, ocl_detect_type,
py2c
Expand Down
3 changes: 2 additions & 1 deletion compyle/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,8 @@ def cumsum(ary, backend=None, out=None):
cumsum_scan(ary=ary, out=out)
return out
elif backend == 'cython':
output = np.cumsum(ary, out=out)
_out = out.dev if out is not None else out
output = np.cumsum(ary.dev, out=_out)
return wrap_array(output, backend)


Expand Down
40 changes: 30 additions & 10 deletions compyle/profile.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
""" Utils for profiling kernels
"""

import time
from contextlib import contextmanager
from collections import defaultdict
import time
from .config import get_config


Expand Down Expand Up @@ -31,22 +31,38 @@ def profile_ctx(name):
_record_profile(name, end - start)


def profile(method):
def profile(method=None, name=None):
"""Decorator for profiling a function. Can be used as follows::
@profile
def f():
pass
If used on a class method, it will use self.name as the
name for recording the profile. If 'name' attribute is not available, it
will use the method name
If explicitly passed a name, with @profile(name='some name'), it will use
the given name. Otherwise, if the function is a class method, and the class
has a `self.name` attribute, it will use that. Otherwise, it will use the
method's qualified name to record the profile.
"""
def wrapper(*args, **kwargs):
self = args[0] if len(args) else None
with profile_ctx(getattr(self, "name", method.__name__)):
return method(*args, **kwargs)
return wrapper
def make_wrapper(method):
def wrapper(*args, **kwargs):
self = args[0] if len(args) else None
if name is None:
if hasattr(self, method.__name__) and hasattr(self, 'name'):
p_name = self.name
else:
p_name = getattr(method, '__qualname__', method.__name__)
else:
p_name = name
with profile_ctx(p_name):
return method(*args, **kwargs)
wrapper.__doc__ = method.__doc__
return wrapper
if method is None:
return make_wrapper
else:
return make_wrapper(method)


def get_profile_info():
Expand All @@ -58,8 +74,11 @@ def print_profile():
global _profile_info
profile_data = sorted(_profile_info.items(), key=lambda x: x[1]['time'],
reverse=True)
hr = '-'*70
print(hr)
if len(_profile_info) == 0:
print("No profiling information available")
print(hr)
return
print("Profiling info:")
print("{:<40} {:<10} {:<10}".format('Function', 'N calls', 'Time'))
Expand All @@ -71,6 +90,7 @@ def print_profile():
data['time']))
tot_time += data['time']
print("Total profiled time: %g secs" % tot_time)
print(hr)


def profile_kernel(kernel, name, backend=None):
Expand Down
11 changes: 11 additions & 0 deletions compyle/tests/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,3 +349,14 @@ def test_cumsum(backend):
a.pull()
b.pull()
assert np.all(b.data == np.cumsum(a.data))

# Test cumsum with an out argument
# Given
out = array.zeros(100, dtype=int, backend=backend)

# When
b = array.cumsum(a, out=out)

# Then
out.pull()
assert np.all(out.data == np.cumsum(a.data))
41 changes: 41 additions & 0 deletions compyle/tests/test_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,25 @@ def axpb():
return a * x + b


class A:
@profile
def f(self):
pass


class B:
def __init__(self):
self.name = 'my_name'

@profile
def f(self):
pass

@profile(name='explicit_name')
def named(self):
pass


@profile
def profiled_axpb():
axpb()
Expand Down Expand Up @@ -48,6 +67,28 @@ def test_profile():
assert profile_info['profiled_axpb']['calls'] == 100


def test_profile_method():
# Given
a = A()
b = B()

# When
for i in range(5):
a.f()
b.f()
b.named()

# Then
profile_info = get_profile_info()
assert profile_info['A.f']['calls'] == 5

# For b.f(), b.name is my_name.
assert profile_info['my_name']['calls'] == 5

# profile was given an explicit name for b.named()
assert profile_info['explicit_name']['calls'] == 5


def test_named_profile():
importorskip('pyopencl')
get_config().profile = True
Expand Down

0 comments on commit 1d3fe06

Please sign in to comment.