python优化问题_python——浅谈递归优化问题

前言:

前一篇简单探讨了python递归的深度问题,递归深度有了底,你可以大胆使用递归了,然而问题又来了,有朋友说python的递归和蜗牛一样慢,那么有没有优化的余地呢?因为我也是菜鸟,所以简单提供几种优化方案供大家学习交流,主要思路学习于简书我们测试案例有所区别;

具体优化:

优化思路:第一角度优化算法,根据递归的计算过程计算过程中实例化了大量重复的函数计算,第一角度尝试优化计算逻辑,但是这虽然计算过程看起来像一个二叉树,但是怎么优化算法说实话心里没谱;

既然优化算法没法实现,那么我们分析一下耗时的原因,其实在递归过程中自身调用自身不断实例化自身,计算机堆内存溢出导致递归有深度一次,在运算结果时候也是不断去计算每个实例化返回值,是否可以将计算过程中实例化返回值保存在一个缓存中或者一个IO中,计算结果时候每次从缓存护着IO中读取是不是能简化计算量从而提高效率呢?尝试去寻找一下缓存解决方案。最终找到好几种缓存优化方案,下面来共同学习下:

看方案之前我们先测试一下1000次斐波那契数列递归结果耗时,最终对比每一种缓存方案结果来进行比较:

代码实现和结果(本篇就不上图了,直接上代码和结果吧,感兴趣可以自己测试):

#coding:utf-8

from timeit import Timer

import sys

sys.setrecursionlimit(3000)

#没有使用缓存的斐波那契数列

def fib(n):

if n <= 2:

return 1

else:

return fib(n-1)+fib(n-2)

if __name__ == "__main__":

t1 = Timer("fib(1000)","from __main__ import fib")

# 斐波那契数列递归深度1000,计算1000次时间

print("fib--1000", t1.timeit(number=1000), "seconds")

#计算结果

优化方案一:使用计算缓存:

#coding:utf-8

from timeit import Timer

import sys

sys.setrecursionlimit(3000)

#使用计算缓存

def cache_fib(n, _cache={}):

if n in _cache:

return _cache[n]

elif n > 1:

return _cache.setdefault(n, cache_fib(n-1) + cache_fib(n-2))

return n

if __name__ == "__main__":

t1 = Timer("cache_fib(100)","from __main__ import cache_fib")

print("cache_fib--100", t1.timeit(number=1000), "seconds")

#计算结果:

cache_fib--100 0.00034458100000001046 seconds

方案二:使用functools 中缓存:

#coding:utf-8

from timeit import Timer

from functools import lru_cache

import sys

sys.setrecursionlimit(3000)

#使用functools装饰器

@lru_cache(maxsize=None)

def fib_cache(n):

if n <= 2:

return 1

else:

return fib_cache(n-1)+fib_cache(n-2)

if __name__ == "__main__":

t1 = Timer("fib_cache(1000)","from __main__ import fib_cache")

print("fib_cache(1000)",t1.timeit(number=1000),"seconds")

方案三:使用cache(github上方案)

#coding:utf-8

from timeit import Timer

import cache

import sys

sys.setrecursionlimit(3000)

@cache.cache(timeout=20,fname="my_cache.pkl")

def fib_cache(n):

if n <= 2:

return 1

else:

return fib_cache(n-1)+fib_cache(n-2)

if __name__ == "__main__":

t1 = Timer("fib_cache(100)","from __main__ import fib_cache")

print("fib_cache(100)",t1.timeit(number=1000),"seconds")

#计算结果:

fib_cache(100) 0.39800654799999996 seconds

#使用从cache代码如下:

import pickle

import time

import inspect

import base64

import hashlib

debug = False

def log(s):

if debug:

print(s)

caches = dict()

updated_caches = []

def get_cache(fname):

if fname in caches:

return caches[fname]

try:

with open(fname, "rb") as f:

c = pickle.load(f)

except:

c = dict()

caches[fname] = c

return c

def write_to_cache(fname, obj):

updated_caches.append(fname)

caches[fname] = obj

def cleanup():

for fname in updated_caches:

with open(fname, "wb") as f:

pickle.dump(caches[fname], f)

def get_fn_hash(f):

return base64.b64encode(hashlib.sha1(inspect.getsource(f).encode("utf-8")).digest())

NONE = 0

ARGS = 1

KWARGS = 2

def cache(fname=".cache.pkl", timeout=-1, key=ARGS | KWARGS):

def impl(fn):

load_t = time.time()

c = get_cache(fname)

log("loaded cache in {:.2f}s".format(time.time() - load_t))

def d(*args, **kwargs):

log("checking cache on {}".format(fn.__name__))

if key == ARGS | KWARGS:

k = pickle.dumps((fn.__name__, args, kwargs))

if key == ARGS:

k = pickle.dumps((fn.__name__, args))

if key == KWARGS:

k = pickle.dumps((fn.__name__, kwargs))

if key == NONE:

k = pickle.dumps((fn.__name__))

if k in c:

h, t, to, res = c[k]

if get_fn_hash(fn) == h and (to < 0 or (time.time() - t) < to):

log("cache hit.")

return res

log("cache miss.")

res = fn(*args, **kwargs)

c[k] = (get_fn_hash(fn), time.time(), timeout, res)

save_t = time.time()

write_to_cache(fname, c)

log("saved cache in {:.2f}s".format(time.time() - save_t))

return res

return d

return impl

@cache(timeout=0.2)

def expensive(k):

time.sleep(0.2)

return k

@cache(key=KWARGS)

def expensive2(k, kwarg1=None):

time.sleep(0.2)

return k

def test():

# Test timeout

t = time.time()

v = expensive(1)

assert v == 1

assert time.time() - t > 0.1

t = time.time()

expensive(1)

assert time.time() - t < 0.1

time.sleep(0.3)

t = time.time()

expensive(1)

assert time.time() - t > 0.1

t = time.time()

v = expensive(2)

assert v == 2

assert time.time() - t > 0.1

# Test key=_ annotation

t = time.time()

v = expensive2(2, kwarg1="test")

assert v == 2

assert time.time() - t > 0.1

t = time.time()

v = expensive2(1, kwarg1="test")

assert v == 2

assert time.time() - t < 0.1

t = time.time()

v = expensive2(1, kwarg1="test2")

assert v == 1

assert time.time() - t > 0.1

cleanup()

print("pass")

if __name__ == "__main__":

test()

方案四:使用diskcache专业的缓存方案(重量级选手,越复杂效果越显著)

#coding:utf-8

from timeit import Timer

from diskcache import FanoutCache

import sys

sys.setrecursionlimit(3000)

#缓存临时文件位置

cache = FanoutCache('tmp/diskcache/fanoutcache')

@cache.memoize(typed=True,expire=None,tag='fib_disk')

def fib_disk(n):

if n <= 2:

return 1

else:

return fib_disk(n-1)+fib_disk(n-2)

if __name__ == "__main__":

t1 = Timer("fib_disk(100)","from __main__ import fib_disk")

print("fib_disk(100)",t1.timeit(number=1000),"seconds")

#运行时间:

fib_disk(100) 0.12914266199999996 seconds

以上就是五组递归缓存方案,通过运行时间对比采取合适的优化方案即可,没有使用缓存方案的递归运行期间我的cpu利用率一直都是100%,好伤,所以如果有非常多层次递归深度,而且计算次数还非常多,奉劝一句已经使用缓存,或者放弃python的递归,要不那就是在玩火;

#计算结果:

使用计算缓存:

cache_fib--100 0.00034458100000001046 seconds

使用functools中装饰器:

fib_cache(100) 0.0001750409999999869 seconds

使用缓存临时文件:

fib_disk(100) 0.12914266199999996 seconds

使用github上的cache方案:

fib_cache(100) 0.39800654799999996 seconds

至于斐波那契数列递归深度100,运行1000次平均时间感觉说起来就没意思,记住一句不要玩火就好;递归深度100运行1000次是第一次我物理机风扇转了,以后不这么测试了;

本次均使用的python3.7环境测试的,python2性能可能会有细微差异;

所以使用@lru_cache深度层次感觉还是不错的选择;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值