total_entropy = 0
freq_dict = {}
row_mean_q = []
for k in range(row_size):
row_mean[k] = quan_normal(row_mean[k])
row_mean_q.append(quan_quan4(row_mean[k],row_mean))
for j in range(4,matrix.shape[1]):
for i in range(2,matrix.shape[0]):
context_list = (max(matrix[i,j-2],matrix[i,j-1]),row_mean_q[i])
if context_list not in freq_dict.keys():
freq_dict[context_list] = {matrix[i,j]:1}
total_entropy += 8
else:
if matrix[i,j] not in freq_dict[context_list].keys():
freq_dict[context_list].update({matrix[i,j]:1})
total_entropy += 8
else:
total_entropy += -np.log(freq_dict[context_list][matrix[i,j]]/np.sum(list(freq_dict[context_list].values())))
freq_dict[context_list][matrix[i,j]] += 1
说明:
利用第三方包line_profiler对程序进行程序运行时间分析,发现row_mean_q
和for循环中最后一个total_entropy
的计算花费了主要的程序运行时间;
考虑应该是row_mean_q
在最开始在定义时是一个空list类型,通过for循环不断地向list中追加数据,并且由于调用的函数quan_quan4()
是一个比较简单的分段判断函数计算量很小不会花费太多时间,所以猜测在开辟内存过程中花费了大量的时间,并且通过list.append()
方法生成最终的list本身就很不高效。
基于上面的猜测,尝试修改变量
row_mean_q
的赋值方式,在预定义时直接赋值,然后后面逐项修改,具体地如代码所示:
#row_mean_q = []
row_mean_q = row_mean #row_mean是一个已计算好且与row_mean_q数组长度、每个元素都较为接近的list
for k in range(row_size):
row_mean[k] = quan_normal(row_mean[k])
#row_mean_q.append(quan_quan4(row_mean[k],row_mean))
row_mean_q[k] = quan_quan4(row_mean[k])
说明:
这里为了更好地利用预分配的内存,直接令row_mean_q = row_mean
,row_mean是一个在程序前面部分已计算好的,且与row_mean_q数组长度、每个元素都较为接近的list。这样赋值后,在后续进行元素值替换时,内存几乎不发生变化(这里需要验证:python中list元素替换时如何实现的,新分配内存还是在原地址进行扩展?)
id(a)
查看变量a的内存地址),但占用内存空间大小可能发生变化(可通过sys.getsizeof(a)来查看内存大小)。如果对变量重新初始化赋值,就会将新的地址赋值给变量。另一个需要验证的是:如果使用row_mean_q = [None]*length
的形式进行赋值,两者最终的运行时间是否有差别,差别有多大?
row_mean
的内容地址赋值给row_mean_q
,后者则需要在内存中开辟新的内存;另一方面,由于None占用内存较小,在后续元素重新赋值过程中需要扩张内存。row_mean_q = row_mean
两者指向同一个列表内存地址,而当对列表内容进行操作时,列表本身的地址没有发生变化,也就说修改row_mean_q和row_mean中任何一个的元素值,都会导致另一个同样发生变化。参考博客:Python程序提速神器–[Numba快速上手指南] (https://blog.csdn.net/june_young_fan/article/details/103671449)
计算机只能执行二进制的机器码,C,C++等编译型语言依靠编译器将源代码转化为可执行文件后运行;Python,Java等解释型语言使用解释器将源码翻译后在虚拟机上执行。对于python,由于解释器的存在,其执行效率比C语言慢几倍甚至几十倍。
Python
是一门解释语言,Python
为我们提供了基于硬件和操作系统的一个虚拟机,并使用解释器将源代码转化为虚拟机可执行的字节码。字节码在虚拟机上执行,得到结果。
解决python执行效率低问题的解决方案:
Numba是一个针对Python的开源JIT编译器,由Anaconda公司主导开发,可以对原生代码进行cpu和gpu加速。
下图是一张由python解释器进行程序运行的过程与JIT编译器运行python程序过程的比较:
具体地,我们使用 python example.py 来执行一份源代码时, Python 解释器会在后台启动一个字节码编译器( Bytecode Compiler ),将源代码转换为字节码。字节码是一种只能运行在虚拟机上的文件, Python 的字节码默认后缀为 .pyc , Python 生成 .pyc 后一般放在内存中继续使用,并不是每次都将 .pyc 文件保存到磁盘上。有时候我们会看到自己 Python 代码文件夹里有很多 .pyc 文件与 .py 文件同名,但也有很多时候看不到 .pyc 文件。pyc 字节码通过 Python 虚拟机与硬件交互,关于.pyc文件的作用和更新机制请移步Python程序的执行过程(解释型语言和编译型语言)。虚拟机的出现导致程序和硬件之间增加了中间层,运行效率大打折扣。相信使用过虚拟机软件的朋友深有体会,在原生的系统上安装一个虚拟机软件,在虚拟机上再运行一个其他系统,经常感觉速度下降,体验变差,这与 Python 虚拟机导致程序运行慢是一个原理。
Just-In-Time(JIT) 技术为解释语言提供了一种优化,它能克服上述效率问题,极大提升代码执行速度,同时保留 Python 语言的易用性。使用 JIT 技术时,JIT 编译器将 Python 源代码编译成机器直接可以执行的机器语言,并可以直接在 CPU 等硬件上运行。这样就跳过了原来的虚拟机,执行速度几乎与用 C 语言编程速度并无二致。
延伸阅读:[How Numba and Cython speed up Python code] (https://rushter.com/blog/numba-cython-python-optimization/)文中提到Cython对python语言加速与numba的区别及适用场景。
使用numba对程序进行加速的主要问题是:目前Numba只支持python原生函数和部分Numpy函数。
通过实际编写程序,过程中遇到的主要问题:
基于以上几点对程序进行修改,修改结果如下:
freq_dict = numba.typed.Dict.empty(
key_type = numba.types.UniTuple(numba.types.int32,2),
value_type = numba.types.int32[:],)
row_mean_q = row_mean
for k in range(row_size):
row_mean[k] = quan_normal(row_mean[k])
row_mean_q[k] = quan_quan4(row_mean[k])
row_mean_q = np.array(row_mean_q,dtype=np.int32)
@numba.njit
def caculate_directory(freq_dict,total_entropy,row_mean_q):
for j in range(4,matrix.shape[1]):
for i in range(2,matrix.shape[0]):
context_list = (max(matrix[i,j-2],matrix[i,j-1]),row_mean_q[i])
if context_list not in freq_dict:
freq_dict[context_list] = np.array([matrix[i,j],1],dtype=np.int32)
total_entropy += 8
else:
if matrix[i,j] not in list(freq_dict[context_list])[::2]:
freq_dict[context_list] = np.array(list(freq_dict[context_list])+[matrix[i,j],1],dtype=np.int32)
total_entropy += 8
else:
_= (list(freq_dict[context_list])[::2].index(matrix[i,j]))*2
total_entropy += -np.log(freq_dict[context_list][_+1]/(freq_dict[context_list][1::2]).sum())
freq_dict[context_list][_+1] += 1
return total_entropy
说明:
list.index('x')
返回元素x
位于list中的位置,方便后续进一步的元素操作。[1] Python程序提速神器–Numba快速上手指南
[2] How Numba and Cython speed up Python code