前言

​ 上次抠图比赛失利,最后发现是网络最后一层操作花费太多时间,直接改成插值就能到70fps(还是python运行的!!,onnx只会更快)。上次面试也问到项目代码量是多少,我发现需要一些代码审计工具来统计分析程序的效率等功能

代码量统计

​ 这个很简单,pycharm里安装插件Statistic就行了,注意可以在设置里exclude 不需要统计的文件,前几次统计会有点慢,后面就快了。

image-20220521103420081

代码运行时间统计

查看文件代码运行时间

Pycharm 专业版有一个profile功能在Run/profile xxx.py,能查看该文件下运行的,最后会生成一个statistic和一个可视化的graph。这个分析的最小单位是函数

查看每行代码运行时间

​ profile最小单位是函数,有时候挺鸡肋的,想到vsdebug模式下能直接看到每行代码运行的时间,pycharm能不能有这个功能呢?找了半天发现似乎只有line_profile是最满足需求的包(参考这篇blog

使用

首先简单安装一下

pip install line_profiler

有实例化line_profile类进行使用的,但是更多使用@profile装饰器会更方便(无需导入包,运行时会自动实例化)。

@profile(func=None, stream=None, precision=1, backend=’psutil’)

stream表示将分析结果输出的文件,默认为命令行;precision表示分析结果中数字小数点保留位数。

# 在需要查看时间的函数上使用装饰器,比如我想要查看网络哪层更耗时
class SRResNet(nn.Module):
def __init__(self,in_channels,n_block,scale_factor,hidden_channels):
super(SRResNet, self).__init__()
self.num_blocks=n_block
self.conv1=nn.Conv2d(in_channels,hidden_channels,kernel_size=(9,9),stride=(1,1),padding=4)
self.ac1=nn.PReLU(num_parameters=1,init=0.25)
self.blocks=nn.Sequential(*[Block(in_channels=hidden_channels,hidden=hidden_channels) for i in range(self.num_blocks)])

self.conv2=nn.Conv2d(hidden_channels,hidden_channels,kernel_size=3,stride=1,padding=1)
self.bn=nn.BatchNorm2d(hidden_channels)
self.pixel_shuffle=nn.Sequential(*[SubPixelShuffleConvBlock(in_channels=hidden_channels) for i in range(scale_factor//2)])
self.conv3=nn.Conv2d(hidden_channels,3,kernel_size=(9,9),stride=1,padding=4)


def init_weights(self):
for m in self.modules():
if isinstance(m,nn.Conv2d):
n=m.kernel_size[0]*m.kernel_size[1]*m.out_channels
m.weight.data.normal_(0,math.sqrt(2./n))
if m.bias!=None:
m.bias.data.zero_()
@profile
def forward(self,x):
out1=self.conv1(x) # B,64,w,h
out1=self.ac1(out1)
out2=self.blocks(out1) # B,64,w,h
out3=self.conv2(out2) # B,64,w,h
out3=self.bn(out3)
out4=out1+out3
out5=self.pixel_shuffle(out4) # B,64,w*4,h*4
out6=self.conv3(out5) # B,3,w*4,h*4
return out6

查看参数:

$ kernprof --help
usage: kernprof [-h] [-V] [-l] [-b] [-o OUTFILE] [-s SETUP] [-v] [-u UNIT]
[-z]
script ...

Run and profile a python script.

positional arguments:
script The python script file to run
args Optional script arguments

optional arguments:
-h, --help show this help message and exit
-V, --version show program's version number and exit
-l, --line-by-line Use the line-by-line profiler instead of cProfile.
Implies --builtin.
-b, --builtin Put 'profile' in the builtins. Use
'profile.enable()'/'.disable()', '@profile' to
decorate functions, or 'with profile:' to profile a
section of code.
-o OUTFILE, --outfile OUTFILE
Save stats to <outfile> (default: 'scriptname.lprof'
with --line-by-line, 'scriptname.prof' without)
-s SETUP, --setup SETUP
Code to execute before the code to profile
-v, --view View the results of the profile in addition to saving
it
-u UNIT, --unit UNIT Output unit (in seconds) in which the timing info is
displayed (default: 1e-6)
-z, --skip-zero Hide functions which have not been called

通常都是下面的参数运行

kernprof -l -v -z -u 1e-3 xxx.py

结果:

Wrote profile results to eval.py.lprof
Timer unit: 0.001 s

Total time: 89.0344 s
File: E:\XXX\SRResNet.py
Function: forward at line 30

Line # Hits Time Per Hit % Time Line Contents
==============================================================
30 @profile
31 def forward(self,x):
32 2 296.9 148.5 0.3 out1=self.conv1(x) # B,64,w,h
33 2 47.0 23.5 0.1 out1=self.ac1(out1)
34 2 11893.6 5946.8 13.4 out2=self.blocks(out1) # B,64,w,h
35 2 240.6 120.3 0.3 out3=self.conv2(out2) # B,64,w,h
36 2 57.4 28.7 0.1 out3=self.bn(out3)
37 2 34.9 17.4 0.0 out4=out1+out3
38 2 9280.0 4640.0 10.4 out5=self.pixel_shuffle(out4) # B,64,w*4,h*4
39 2 67184.0 33592.0 75.5 out6=self.conv3(out5) # B,3,w*4,h*4
40 2 0.0 0.0 0.0 return out6



名称 意义
Timer unit 计时器单位,微秒
Total time 测试代码总运行时间
File 测试代码文件名
Hits 每行代码运行次数
Time 每行代码运行时间
Per Hit 每行代码运行一次的时间
% Time 每行代码运行时间的百分比
Line Contents 每行代码内容

通常看Time,%Time就好了,觉得命令麻烦的可以加alias

读取lprof 文件

进入当前目录后,在命令行中使用

python -m line_profiler xxxx.lprof (x.lprof是自己的文件名)

但是正常调试运行时会报错没有这个属性,我们需要把profile属性定义一下,让他正常运行时不用删更方便:

# 加在需要运行的程序最上方,本质是不使用kernprof时profile装饰器调用原本函数
import builtins
if 'builtins' not in dir() or not hasattr(builtins, 'profile'):
def profile(func):
def inner(*args, **kwargs):
return func(*args, **kwargs)
return inner
builtins.__dict__['profile'] = profile

代码运行内存统计

也是模仿上面那位大神开发出的包:

pip install memory_profiler
# 加速memory profiler
pip install psutil

也是同样加@profile装饰器(方便二者能同时用,而不冲突)

运行方式:

python -m memory_profiler test.py

运行结果:

Filename: E:\paper解读\Gan\2016.09_SRGAN\codes\model\SRResNet.py

Line # Mem usage Increment Occurrences Line Contents
=============================================================
30 505.516 MiB -60.969 MiB 2 @profile
31 def forward(self,x):
32 547.445 MiB -349.020 MiB 2 out1=self.conv1(x) # B,64,w,h
33 547.488 MiB -432.805 MiB 2 out1=self.ac1(out1)
34 585.656 MiB -356.387 MiB 2 out2=self.blocks(out1) # B,64,w,h
35 623.355 MiB -357.324 MiB 2 out3=self.conv2(out2) # B,64,w,h
36 623.355 MiB -432.723 MiB 2 out3=self.bn(out3)
37 661.055 MiB -357.324 MiB 2 out4=out1+out3
38 606.195 MiB -110.090 MiB 2 out5=self.pixel_shuffle(out4) # B,64,w*4,h*4
39 640.629 MiB 65.297 MiB 2 out6=self.conv3(out5) # B,3,w*4,h*4
40 640.645 MiB -3.902 MiB 2 return out6

个人感觉没有line_profile常用,更详细方法可参考这篇

最后加一点代码检查:在code选项中inspect code可以看到自己哪些有warning,还可以防一手内存泄漏。