高效编程

摘要

Python作为动态语言,一直被人诟病运行效率低。其实内置很多高阶的函数如map、filter、zip、reduce等,不仅运行速度快而且对内存的占用做了很大的优化。

本文通过计算20万个数的平方,分别比较使用map函数返回map对象,for循环返回迭代器,和for循环返回list,三种方式的内存占用和CPU耗时,发现map和for循环返回生成器效率远远高于for循环返回list。map函数编程是这三种方式最高效的。

安装测试模块

pip3 install memory_profiler
pip3 install psutil
pip3 install line_profiler

测试代码为map.py

import time

NUM = 200000

@profile
def gen_for_time():
    x = (i ** 2 for i in range(NUM))

@profile
def map_time():
    y = map(lambda x: x**2, range(NUM))

@profile
def list_for_time():
    y = [i ** 2 for i in range(NUM)]

gen_for_time()
map_time()
list_for_time()

测试CPU耗时

运行系统命令

kernprof -l -v map.py
[vincent@MBP tmp ]$ kernprof -l -v map.py
Wrote profile results to map.py.lprof
Timer unit: 1e-06 s

Total time: 8e-06 s
File: map.py
Function: gen_for_time at line 5

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     5                                           @profile
     6                                           def gen_for_time():
     7         1          8.0      8.0    100.0     x = (i ** 2 for i in range(NUM))

Total time: 2e-06 s
File: map.py
Function: map_time at line 9

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     9                                           @profile
    10                                           def map_time():
    11         1          2.0      2.0    100.0     y = map(lambda x: x**2, range(NUM))

Total time: 0.079965 s
File: map.py
Function: list_for_time at line 13

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    13                                           @profile
    14                                           def list_for_time():
    15         1      79965.0  79965.0    100.0     y = [i ** 2 for i in range(NUM)]

可以看到map用时为 2e-06 s,生成器用时8e-06 s,而普通列表用时0.079965 s,很明显map和迭代器用时远远低于列表。在数据较大(过万)时,列表追加元素的速度会非常慢(后续补充列表实现原理)。下面再来看看内存的占用比较:

python3 -m memory_profiler map.py
Filename: map.py

Line #    Mem usage    Increment   Line Contents
================================================
     5   59.730 MiB   59.730 MiB   @profile
     6                             def gen_for_time():
     7   59.734 MiB    0.004 MiB    x = (i ** 2 for i in range(NUM))


Filename: map.py

Line #    Mem usage    Increment   Line Contents
================================================
     9   59.734 MiB   59.734 MiB   @profile
    10                             def map_time():
    11   59.734 MiB    0.000 MiB    y = map(lambda x: x**2, range(NUM))


Filename: map.py

Line #    Mem usage    Increment   Line Contents
================================================
    13   59.734 MiB   59.734 MiB   @profile
    14                             def list_for_time():
    15   66.809 MiB -2637.891 MiB       y = [i ** 2 for i in range(NUM)]

Increment表示增量,结果发现,map > 迭代器 > 列表。而且列表非常糟糕,每次都显式的存储元素,导致占用内存非常高达到2G。

总结

map函数返回的是map对象,其实map对象也是一种迭代器,因此在内存占用和CPU耗时上,比普通迭代器稍好。这说明map函数做了更多的优化,后续继续探究map的实现原理。

到此我们比较完三种编程方式,for循环配合列表的这种方式,效率非常低,以后的编程过程中尽量少使用。

发表评论

邮箱地址不会被公开。