MoProfiler¶
Badge¶
其他¶
概览¶
MoProfiler 是一个综合性能分析工具,支持内存分析、时间分析、秒表打点等功能。 可以有效量化 Python 代码的性能,从而找到性能瓶颈所在,使性能优化可以有的放矢。
使用说明¶
如开始的介绍,本工具集主要由三个子工具 时间分析器 , 内存分析器 , 秒表工具 组成, 下面将对其使用方式进行逐一介绍
三个子工具分别提供了一个 装饰器
,其中秒表工具额外提供了一个 Mixin
类,
一般使用中会存在如下装饰场景:
- 独立函数
- 类方法
- 实例方法
- 静态方法
- 生成器方法
为方便使用,三个工具分别提供了一个 超级装饰器
,即同时支持上述几类场景,
且同时支持 有参装饰
与 无参装饰
,当不传参时装饰器后不需增加 ()
其中秒表工具,当被装饰对象为 类方法
或 实例方法
时,可通过让类继承
StopwatchMixin
类,来获得功能增强
时间分析器¶
该分析器可以统计出指定函数或方法中每行代码的执行时间消耗,从而找到瓶颈所在,用例如下:
from moprofiler import TimeProfiler
class QucikSort(object):
"""
快速排序
"""
def __init__(self, arr):
self.arr = arr
def sort(self, left=None, right=None):
"""排序"""
left = 0 if not isinstance(left, (int, float)) else left
right = len(self.arr) - 1 if not isinstance(right, (int, float)) else right
if left < right:
partition_index = self.partition(left, right)
self.sort(left, partition_index - 1)
self.sort(partition_index + 1, right)
@TimeProfiler(print_res=False)
def partition(self, left, right):
"""分区"""
pivot = left
index = pivot + 1
i = index
while i <= right:
if self.arr[i] < self.arr[pivot]:
self.swap(i, index)
index += 1
i += 1
self.swap(pivot, index - 1)
return index - 1
def swap(self, i, j):
"""交换"""
self.arr[i], self.arr[j] = self.arr[j], self.arr[i]
unsort_list = [3, 12, 12, 11, 15, 9, 12, 4, 15, 4, 2, 15, 7, 10, 12, 2, 3, 1, 14, 5, 7]
qs = QucikSort(unsort_list)
qs.sort()
# 从 1.1.0 版本开始支持的结果打印方式
qs.partition.print_stats()
print('结果: {}'.format(qs.arr))
执行结果如下:
Timer unit: 1e-06 s
Total time: 0.000344 s
File: tests/test_50_time_profiler_to_method.py
Function: partition at line 28
Line # Hits Time Per Hit % Time Line Contents
==============================================================
28 @TimeProfiler(print_res=False)
29 def partition(self, left, right):
30 """分区"""
31 15 17.0 1.1 4.9 pivot = left
32 15 10.0 0.7 2.9 index = pivot + 1
33 15 7.0 0.5 2.0 i = index
34 93 63.0 0.7 18.3 while i <= right:
35 78 58.0 0.7 16.9 if self.arr[i] < self.arr[pivot]:
36 33 74.0 2.2 21.5 self.swap(i, index)
37 33 34.0 1.0 9.9 index += 1
38 78 47.0 0.6 13.7 i += 1
39 15 26.0 1.7 7.6 self.swap(pivot, index - 1)
40 15 8.0 0.5 2.3 return index - 1
结果:[1, 2, 2, 3, 3, 4, 4, 5, 7, 7, 9, 10, 11, 12, 12, 12, 12, 14, 15, 15, 15]
注意
当被装饰函数&方法被多次调用时,会复用该函数&方法对应的单例分析器,
所得的统计结果在上次的基础上累加后用于打印。若确实不关心累计结果,
仅需要使用全新的分析器进行分析可在装饰时使用 force_new_profiler
关键字参数实现,
具体参考其父类中的定义 ProfilerClassDecorator
内存分析器¶
该分析器可以统计出指定函数或方法中每行代码的执行内存消耗,从而找到瓶颈所在,用例如下:
from moprofiler import MemoryProfiler
class MemoryWaste(object):
"""
浪费内存
"""
@MemoryProfiler(print_res=False)
def list_waste(self):
"""列表"""
a = [1] * (10 ** 5)
b = [2] * (2 * 10 ** 5)
del b
return a
@classmethod
@MemoryProfiler
def dict_waste(cls, a):
"""字典"""
ret = {}
for i in a:
ret[i] = i
return ret
mw = MemoryWaste()
x = mw.list_waste()
mw.dict_waste(x)
mw.list_waste.print_stats()
执行结果如下:
Filename: tests/test_01_memory_profiler_to_method.py
Line # Mem usage Increment Line Contents
================================================
23 40.9 MiB 40.9 MiB @classmethod
24 @MemoryProfiler
25 def dict_waste(cls, a):
26 """字典"""
27 40.9 MiB 0.0 MiB ret = {}
28 40.9 MiB 0.0 MiB for i in a:
29 40.9 MiB 0.0 MiB ret[i] = i
30 40.9 MiB 0.0 MiB return ret
Filename: tests/test_01_memory_profiler_to_method.py
Line # Mem usage Increment Line Contents
================================================
15 38.6 MiB 38.6 MiB @MemoryProfiler(print_res=False)
16 def list_waste(self):
17 """列表"""
18 39.4 MiB 0.8 MiB a = [1] * (10 ** 5)
19 40.9 MiB 1.5 MiB b = [2] * (2 * 10 ** 5)
20 40.9 MiB 0.0 MiB del b
21 40.9 MiB 0.0 MiB return a
注意
当被装饰函数&方法被多次调用时,会复用该函数&方法对应的单例分析器,
所得的统计结果在上次的基础上累加后用于打印。若确实不关心累计结果,
仅需要使用全新的分析器进行分析可在装饰时使用 force_new_profiler
关键字参数实现,
具体参考其父类中的定义 ProfilerClassDecorator
秒表工具¶
该秒表工具可以监控指定函数或方法的执行用时,当被装饰的方法继承了 StopwatchMixin
后,可以通过调用 dotting()
方法来进行日志打点,从而记录某个代码切片的用时。
由于打点多少可由开发者自行控制,故该工具与前述 时间分析器 的优势是,可用于生产环境。
import logging
import time
from moprofiler import StopwatchMixin, stopwatch
logging.basicConfig(
level=logging.DEBUG,
format='[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s')
LOG = logging.getLogger(__name__)
class zzz(StopwatchMixin):
"""测试方法装饰"""
@staticmethod
@stopwatch
def orz_staticmethod():
"""静态方法"""
for _i in range(2):
time.sleep(0.25)
@stopwatch
def orz_instancemethod(self, x):
"""实例方法"""
for _i in range(x):
self.stopwatch.dotting()
time.sleep(0.1)
self.stopwatch.dotting()
@classmethod
@stopwatch(
fmt='[性能] {name}, 参数列表: {args}, 耗时: {time_use:.8f}s, {foo}',
logger=LOG,
name='hakula',
foo='matata')
def orz_classmethod(cls, x):
"""类方法"""
for _i in range(x):
cls.stopwatch.dotting('定制打点输出{idx},当前 {time_diff:.8f}s,累计: {time_total:.8f}s')
time.sleep(0.1)
cls.stopwatch.dotting()
@stopwatch(print_mem=True)
def orz_instancemethod_generator(self, x):
"""实例方法生成器"""
for _i in range(x):
mute = True if _i == 2 else False
self.stopwatch.dotting(mute=mute, memory=True)
time.sleep(0.1)
yield _i
self.stopwatch.dotting(memory=True)
z = zzz()
z.orz_staticmethod()
z.orz_instancemethod(5)
z.orz_classmethod(5)
_tmp = [i for i in z.orz_instancemethod_generator(5)]
assert _tmp == [i for i in range(5)]
执行结果如下:
[2019-01-08 19:13:26,019] INFO [moprofiler.stopwatch:120] [性能] orz_staticmethod, 耗时: 0.5062s
[2019-01-08 19:13:26,021] INFO [moprofiler.stopwatch:214] [性能] 当前耗时(1): 0.0002s, 累计耗时: 0.0002s
[2019-01-08 19:13:26,127] INFO [moprofiler.stopwatch:214] [性能] 当前耗时(2): 0.1054s, 累计耗时: 0.1056s
[2019-01-08 19:13:26,229] INFO [moprofiler.stopwatch:214] [性能] 当前耗时(3): 0.1021s, 累计耗时: 0.2078s
[2019-01-08 19:13:26,333] INFO [moprofiler.stopwatch:214] [性能] 当前耗时(4): 0.1045s, 累计耗时: 0.3123s
[2019-01-08 19:13:26,438] INFO [moprofiler.stopwatch:214] [性能] 当前耗时(5): 0.1046s, 累计耗时: 0.4168s
[2019-01-08 19:13:26,542] INFO [moprofiler.stopwatch:214] [性能] 当前耗时(6): 0.1045s, 累计耗时: 0.5213s
[2019-01-08 19:13:26,543] INFO [moprofiler.stopwatch:120] [性能] orz_instancemethod, 耗时: 0.5218s
[2019-01-08 19:13:26,544] INFO [test_02_stopwatch_mixin:214] 定制打点输出1,当前 0.00021791s,累计: 0.00021791s
[2019-01-08 19:13:26,647] INFO [test_02_stopwatch_mixin:214] 定制打点输出2,当前 0.10304499s,累计: 0.10326290s
[2019-01-08 19:13:26,751] INFO [test_02_stopwatch_mixin:214] 定制打点输出3,当前 0.10447907s,累计: 0.20774198s
[2019-01-08 19:13:26,856] INFO [test_02_stopwatch_mixin:214] 定制打点输出4,当前 0.10449409s,累计: 0.31223607s
[2019-01-08 19:13:26,961] INFO [test_02_stopwatch_mixin:214] 定制打点输出5,当前 0.10524797s,累计: 0.41748405s
[2019-01-08 19:13:27,065] INFO [test_02_stopwatch_mixin:214] [性能] 当前耗时(6): 0.1044s, 累计耗时: 0.5219s
[2019-01-08 19:13:27,066] INFO [test_02_stopwatch_mixin:120] [性能] hakula, 参数列表: (<class 'test_02_stopwatch_mixin.zzz'>, 5), 耗时: 0.52235889s, matata
[2019-01-08 19:13:27,069] INFO [moprofiler.stopwatch:214] [性能] 当前耗时(1): 0.0019s, 累计耗时: 0.0019s, 当前变化: 1M, 累计变化: 1M
[2019-01-08 19:13:27,175] INFO [moprofiler.stopwatch:214] [性能] 当前耗时(2): 0.1060s, 累计耗时: 0.1079s, 当前变化: 2M, 累计变化: 3M
[2019-01-08 19:13:27,384] INFO [moprofiler.stopwatch:214] [性能] 当前耗时(4): 0.1041s, 累计耗时: 0.3167s, 当前变化: 2M, 累计变化: 6M
[2019-01-08 19:13:27,490] INFO [moprofiler.stopwatch:214] [性能] 当前耗时(5): 0.1068s, 累计耗时: 0.4235s, 当前变化: 1M, 累计变化: 7M
[2019-01-08 19:13:27,594] INFO [moprofiler.stopwatch:214] [性能] 当前耗时(6): 0.1040s, 累计耗时: 0.5274s, 当前变化: 0M, 累计变化: 7M
[2019-01-08 19:13:27,598] INFO [moprofiler.stopwatch:120] [性能] orz_instancemethod_generator, 耗时: 0.5313s, 内存变化: 7M
时间分析器模块¶
提供用于时间性能分析的工具
-
class
moprofiler.time.
TimeProfiler
(_function=None, stream=None, output_unit=None, stripzeros=False, **kwargs)[源代码]¶ 基类:
moprofiler.base.ProfilerClassDecorator
时间分析器的类装饰器
逐行分析被装饰函数每行的执行时间
参数: -
profiler_factory
¶ line_profiler.LineProfiler
的别名
-
-
moprofiler.time.
time_profiler
¶ 此变量是为了向后兼容旧版本的命名
内存分析器模块¶
提供用于内存性能分析的工具
-
class
moprofiler.memory.
MemoryProfilerWrapper
(**kw)[源代码]¶ 基类:
memory_profiler.LineProfiler
内存分析器封装
在原本分析器中增加统一的打印接口
-
class
moprofiler.memory.
MemoryProfiler
(_function=None, stream=None, precision=1, backend='psutil', **kwargs)[源代码]¶ 基类:
moprofiler.base.ProfilerClassDecorator
内存分析器的类装饰器
参数: -
profiler_factory
¶
-
-
moprofiler.memory.
memory_profiler
¶ 此变量是为了向后兼容旧版本的命名
秒表工具模块¶
提供用于计时打点的秒表工具
-
class
moprofiler.stopwatch.
Stopwatch
[源代码]¶ 基类:
object
秒表类
-
time_buf
= None¶ 用来存储计时打点时间
-
mem_buf
= None¶ 用来存储打点内存
-
name
= None¶ 秒表的名称,可在装饰时设置,默认为使用被装饰方法的方法名
-
dkwargs
= None¶ 用来存储最终输出时使用的变量
-
dotting_param_pre
= None¶ 用来记录上次打点输出时的参数信息
-
logger
= None¶ 用来日志输出的 logger
-
logging_level
= None¶ 日志输出级别
-
final_fmt
= None¶ 输出最终计时结果的字符串模板
-
is_print_memory
= None¶ 是否打印内存
-
wrap_generator
(func, wrap_param)[源代码]¶ 封装一个生成器从而使用秒表对其进行观察
参数: - func (types.FunctionType or types.MethodType) – 被封装的函数,由解释器自动传入,不需关心
- wrap_param (dict) – 封装时传入的参数字典
返回: 封装后的方法
返回类型: types.FunctionType or types.MethodType
-
wrap_function
(func, wrap_param)[源代码]¶ 封装一个函数从而使用秒表对其进行观察
参数: - func (types.FunctionType or types.MethodType) – 被封装的函数,由解释器自动传入,不需关心
- wrap_param (dict) – 封装时传入的参数字典
返回: 封装后的方法
返回类型: types.FunctionType or types.MethodType
-
dotting
(fmt='', logging_level=None, memory=False, mute=False, **kwargs)[源代码]¶ 输出打点日志
会在打点时记录当前的时间&进程内存使用(若
memory
为True
),并将其与上次的打点记录做差, 分别获取时间差time_diff
、内存差memory_diff
,再将其与进入函数时的记录做差, 分别获取到时间差time_total
、内存差memory_total
。将以上四个变量传入
fmt
模板中格式化后输出到log
。 除上述变量外,您还可以使用装饰器参数中指定的变量。注意仅当
memory
为真时,才会记录日志情况,否则将直接跳过。日志输出模板中可使用变量如下:
idx
: 当前打点的序号,从 1 开始time_diff
: 距上次打点间的时间差time_total
: 距函数/方法开始时的时间差memory_diff
: 距上次打点(memory=True)间的内存差,跳过未记录内存的时间打点,直到函数/方法进入时的内存记录memory_total
: 距函数/方法开始时的内存差
参数:
-
-
class
moprofiler.stopwatch.
StopwatchMixin
[源代码]¶ 基类:
moprofiler.base.ProfilerMixin
秒表 Mixin 类
用以提供复杂的秒表功能,如:
- 针对需要多次调用的方法进行累加记录的场景
- 在一次代码执行流程中同时记录多个方法,并灵活控制记录结果的输出
-
moprofiler.stopwatch.
stopwatch
(_function=None, print_args=False, logger=None, print_mem=False, fmt='', name='', logging_level=20, **dkwargs)[源代码]¶ 返回秒表监控下的函数或方法
通过额外的关键字参数,支持配置自定义的值到输出模板中
日志输出模板中可使用变量如下:
name
: 当前秒表名称args
: 被装饰函数/方法的位置参数kwargs
: 被装饰函数/方法time_use
: 函数/方法执行耗时mem_use
: 函数/方法执行内存
参数: - _function (types.FunctionType or types.MethodType) – 被封装的对象,由解释器自动传入,不需关心
- print_args (bool) – 是否打印被装饰函数的参数列表,若含有较长的参数,可能造成日志过长,开启时请注意
- logger (logging.Logger) – 可传入指定的日志对象,便于统一输出样式,默认使用该模块中的全局 logger
- print_mem (bool) – 是否在方法退出时打印内存信息,默认为 False
- fmt (str) – 用于格式化输出的模板,可在了解所有内置参数变量后自行定制输出样式,若指定该参数则会忽略 print_args
- name (str) – 关键字参数,被装饰方法代理生成的 stopwatch 所使用的名称,默认为使用被装饰方法的方法名
- logging_level (int) – 打印日志的级别,默认为 INFO
返回: 装饰后的函数
返回类型: types.FunctionType or types.MethodType
基础模块¶
提供用于性能分析的相关基类&函数定义
-
moprofiler.base.
get_callargs
(func, *args, **kwargs)[源代码]¶ 找到层层装饰器下最里层的函数的 callargs
参数: 返回: 调用参数字典
返回类型:
-
moprofiler.base.
is_instance_or_subclass
(self_or_cls, super_cls)[源代码]¶ 判断对象或类是否继承了指定类
参数: - self_or_cls (object) – 对象或类
- super_cls (class) – 父类
返回: 判断结果
返回类型:
-
class
moprofiler.base.
ClassDecoratorBase
(_function=None, fake_method=True)[源代码]¶ 基类:
object
类装饰器初始化
参数: - _function (types.FunctionType or types.MethodType) – 被封装的对象,由解释器自动传入,不需关心
- fake_method (bool) – 是否将被装饰后的类装饰器伪装成方法,默认为是。 注意,伪装后仍然可以正常调用类装饰器中额外定义的对象属性, 此参数仅用于装饰类方法时使用,装饰函数时无效
-
func
¶ 被封装函数的 getter 方法
-
class
moprofiler.base.
ProfilerClassDecorator
(_function=None, print_res=True, force_new_profiler=False, profiler_args=None, profiler_kwargs=None, **kwargs)[源代码]¶ 基类:
moprofiler.base.ClassDecoratorBase
分析器的类装饰器初始化
增加分析器相关的供外部使用的公共属性
参数: - _function (types.FunctionType or types.MethodType) – 被封装的对象,由解释器自动传入,不需关心
- print_res (bool) – 是否在被装饰对象退出后立刻打印分析结果,默认为
True
。 当需要将多次调用结果聚集后输出时,可设为False
,并通过调用被装饰函数/方法 (装饰后将被替换为ProfilerClassDecorator
)的print_stats()
方法进行结果输出 - force_new_profiler (bool) – 是否强制使用新的分析器,默认为
否
- profiler_args (tuple) – 分析器工厂的位置参数列表
- profiler_kwargs (dict) – 分析器工厂的关键字参数字典
-
profiler_factory
¶ 用于生产分析器的工厂
-
profiler
= None¶ 分析器实例对象
Shortcut¶
注意
此文档中列出的为 moprofiler
对外提供功能的捷径说明,从而缩短调用路径
提供针对时间、内存的分析器,以及秒表日志打点工具
-
class
moprofiler.
TimeProfiler
(_function=None, stream=None, output_unit=None, stripzeros=False, **kwargs)[源代码] 基类:
moprofiler.base.ProfilerClassDecorator
时间分析器的类装饰器
逐行分析被装饰函数每行的执行时间
参数: -
profiler_factory
line_profiler.LineProfiler
的别名
-
print_stats
()[源代码] 打印统计结果
-
-
class
moprofiler.
MemoryProfiler
(_function=None, stream=None, precision=1, backend='psutil', **kwargs)[源代码] 基类:
moprofiler.base.ProfilerClassDecorator
内存分析器的类装饰器
参数: -
profiler_factory
MemoryProfilerWrapper
的别名
-
print_stats
()[源代码] 打印统计结果
-
-
class
moprofiler.
StopwatchMixin
[源代码] 基类:
moprofiler.base.ProfilerMixin
秒表 Mixin 类
用以提供复杂的秒表功能,如:
- 针对需要多次调用的方法进行累加记录的场景
- 在一次代码执行流程中同时记录多个方法,并灵活控制记录结果的输出
-
moprofiler.
stopwatch
(_function=None, print_args=False, logger=None, print_mem=False, fmt='', name='', logging_level=20, **dkwargs)[源代码] 返回秒表监控下的函数或方法
通过额外的关键字参数,支持配置自定义的值到输出模板中
日志输出模板中可使用变量如下:
name
: 当前秒表名称args
: 被装饰函数/方法的位置参数kwargs
: 被装饰函数/方法time_use
: 函数/方法执行耗时mem_use
: 函数/方法执行内存
参数: - _function (types.FunctionType or types.MethodType) – 被封装的对象,由解释器自动传入,不需关心
- print_args (bool) – 是否打印被装饰函数的参数列表,若含有较长的参数,可能造成日志过长,开启时请注意
- logger (logging.Logger) – 可传入指定的日志对象,便于统一输出样式,默认使用该模块中的全局 logger
- print_mem (bool) – 是否在方法退出时打印内存信息,默认为 False
- fmt (str) – 用于格式化输出的模板,可在了解所有内置参数变量后自行定制输出样式,若指定该参数则会忽略 print_args
- name (str) – 关键字参数,被装饰方法代理生成的 stopwatch 所使用的名称,默认为使用被装饰方法的方法名
- logging_level (int) – 打印日志的级别,默认为 INFO
返回: 装饰后的函数
返回类型: types.FunctionType or types.MethodType
-
moprofiler.
time_profiler
此变量是为了向后兼容旧版本的命名
-
moprofiler.
memory_profiler
此变量是为了向后兼容旧版本的命名
发布说明¶
1.0.2 (2019-01-13 22:12:14)¶
Feature¶
- 实现对函数&静态方法使用
时间分析器
装饰后,在外部打印分析结果的功能 - 实现对函数&静态方法使用
内存分析器
装饰后,在外部打印分析结果的功能
Bugfix¶
- 修复 CI 过程中设置全局包版本号环境变量失败的 Bug
1.0.1.post0 (2019-01-13 15:53:12)¶
Optimize¶
- 优化
rtfd
文档编写 - 完善
Travis CI
配置,并整理徽章 - 添加
SonarCloud
代码质量分析 CI - 完善单元测试覆盖率
1.0.0 (2019-01-05 21:13:20)¶
Feature¶
- 实现对 line_profiler 封装的装饰器及相应的
TimeProfilerMixin
类 - 实现对 memory-profiler 封装的装饰器及相应的
MemoryProfilerMixin
类 - 实现用于打点计时的秒表工具,方便记录函数的关键执行节点,以及耗时