Chinaunix首页 | 论坛 | 博客
  • 博客访问: 4429795
  • 博文数量: 1214
  • 博客积分: 13195
  • 博客等级: 上将
  • 技术积分: 9105
  • 用 户 组: 普通用户
  • 注册时间: 2007-01-19 14:41
个人简介

C++,python,热爱算法和机器学习

文章分类

全部博文(1214)

文章存档

2021年(13)

2020年(49)

2019年(14)

2018年(27)

2017年(69)

2016年(100)

2015年(106)

2014年(240)

2013年(5)

2012年(193)

2011年(155)

2010年(93)

2009年(62)

2008年(51)

2007年(37)

分类: Python/Ruby

2020-09-01 11:08:49

https://blog.csdn.net/DCclient/article/details/102961599

前言:

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

具体优化:

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

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

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

代码实现和结果(本篇就不上图了):

  1. #coding:utf-8
  2. from timeit import Timer
  3. import sys
  4. sys.setrecursionlimit(3000)
  5. #没有使用缓存的斐波那契数列
  6. def fib(n):
  7. if n <= 2:
  8. return 1
  9. else:
  10. return fib(n-1)+fib(n-2)
  11. if __name__ == "__main__":
  12. t1 = Timer("fib(100)","from __main__ import fib")
  13. # 斐波那契数列递归深度100,计算1000次平均时间
  14. print("fib--100", t1.timeit(number=1000), "seconds")
  15. #计算结果我的电脑没计算出来,后边使用深度20做了计算;

方案一:使用计算缓存:

  1. #coding:utf-8
  2. from timeit import Timer
  3. import sys
  4. sys.setrecursionlimit(3000)
  5. #使用计算缓存
  6. def cache_fib(n, _cache={}):
  7. if n in _cache:
  8. return _cache[n]
  9. elif n > 1:
  10. return _cache.setdefault(n, cache_fib(n-1) + cache_fib(n-2))
  11. return n
  12. if __name__ == "__main__":
  13. t1 = Timer("cache_fib(100)","from __main__ import cache_fib")
  14. print("cache_fib--100", t1.timeit(number=1000), "seconds")
  15. #计算结果:
  16. cache_fib--100 0.00034458100000001046 seconds

方案二:使用functools 中缓存:

  1. #coding:utf-8
  2. from timeit import Timer
  3. from functools import lru_cache
  4. import sys
  5. sys.setrecursionlimit(3000)
  6. #使用functools装饰器
  7. @lru_cache(maxsize=None)
  8. def fib_cache(n):
  9. if n <= 2:
  10. return 1
  11. else:
  12. return fib_cache(n-1)+fib_cache(n-2)
  13. if __name__ == "__main__":
  14. t1 = Timer("fib_cache(100)","from __main__ import fib_cache")
  15. print("fib_cache(100)",t1.timeit(number=1000),"seconds")
  16. #计算结果;
  17. #fib_cache(100) 0.0001750409999999869 seconds

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

  1. #coding:utf-8
  2. from timeit import Timer
  3. import cache
  4. import sys
  5. sys.setrecursionlimit(3000)
  6. @cache.cache(timeout=20,fname="my_cache.pkl")
  7. def fib_cache(n):
  8. if n <= 2:
  9. return 1
  10. else:
  11. return fib_cache(n-1)+fib_cache(n-2)
  12. if __name__ == "__main__":
  13. t1 = Timer("fib_cache(100)","from __main__ import fib_cache")
  14. print("fib_cache(100)",t1.timeit(number=1000),"seconds")
  15. #计算结果:
  16. fib_cache(100) 0.39800654799999996 seconds
  17. #使用从cache代码如下:
  18. import pickle
  19. import time
  20. import inspect
  21. import base64
  22. import hashlib
  23. debug = False
  24. def log(s):
  25. if debug:
  26. print(s)
  27. caches = dict()
  28. updated_caches = []
  29. def get_cache(fname):
  30. if fname in caches:
  31. return caches[fname]
  32. try:
  33. with open(fname, "rb") as f:
  34. c = pickle.load(f)
  35. except:
  36. c = dict()
  37. caches[fname] = c
  38. return c
  39. def write_to_cache(fname, obj):
  40. updated_caches.append(fname)
  41. caches[fname] = obj
  42. def cleanup():
  43. for fname in updated_caches:
  44. with open(fname, "wb") as f:
  45. pickle.dump(caches[fname], f)
  46. def get_fn_hash(f):
  47. return base64.b64encode(hashlib.sha1(inspect.getsource(f).encode("utf-8")).digest())
  48. NONE = 0
  49. ARGS = 1
  50. KWARGS = 2
  51. def cache(fname=".cache.pkl", timeout=-1, key=ARGS | KWARGS):
  52. def impl(fn):
  53. load_t = time.time()
  54. c = get_cache(fname)
  55. log("loaded cache in {:.2f}s".format(time.time() - load_t))
  56. def d(*args, **kwargs):
  57. log("checking cache on {}".format(fn.__name__))
  58. if key == ARGS | KWARGS:
  59. k = pickle.dumps((fn.__name__, args, kwargs))
  60. if key == ARGS:
  61. k = pickle.dumps((fn.__name__, args))
  62. if key == KWARGS:
  63. k = pickle.dumps((fn.__name__, kwargs))
  64. if key == NONE:
  65. k = pickle.dumps((fn.__name__))
  66. if k in c:
  67. h, t, to, res = c[k]
  68. if get_fn_hash(fn) == h and (to < 0 or (time.time() - t) < to):
  69. log("cache hit.")
  70. return res
  71. log("cache miss.")
  72. res = fn(*args, **kwargs)
  73. c[k] = (get_fn_hash(fn), time.time(), timeout, res)
  74. save_t = time.time()
  75. write_to_cache(fname, c)
  76. log("saved cache in {:.2f}s".format(time.time() - save_t))
  77. return res
  78. return d
  79. return impl
  80. @cache(timeout=0.2)
  81. def expensive(k):
  82. time.sleep(0.2)
  83. return k
  84. @cache(key=KWARGS)
  85. def expensive2(k, kwarg1=None):
  86. time.sleep(0.2)
  87. return k
  88. def test():
  89. # Test timeout
  90. t = time.time()
  91. v = expensive(1)
  92. assert v == 1
  93. assert time.time() - t > 0.1
  94. t = time.time()
  95. expensive(1)
  96. assert time.time() - t < 0.1
  97. time.sleep(0.3)
  98. t = time.time()
  99. expensive(1)
  100. assert time.time() - t > 0.1
  101. t = time.time()
  102. v = expensive(2)
  103. assert v == 2
  104. assert time.time() - t > 0.1
  105. # Test key=_ annotation
  106. t = time.time()
  107. v = expensive2(2, kwarg1="test")
  108. assert v == 2
  109. assert time.time() - t > 0.1
  110. t = time.time()
  111. v = expensive2(1, kwarg1="test")
  112. assert v == 2
  113. assert time.time() - t < 0.1
  114. t = time.time()
  115. v = expensive2(1, kwarg1="test2")
  116. assert v == 1
  117. assert time.time() - t > 0.1
  118. cleanup()
  119. print("pass")
  120. if __name__ == "__main__":
  121. test()

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

  1. #coding:utf-8
  2. from timeit import Timer
  3. from diskcache import FanoutCache
  4. import sys
  5. sys.setrecursionlimit(3000)
  6. #缓存临时文件位置
  7. cache = FanoutCache('tmp/diskcache/fanoutcache')
  8. @cache.memoize(typed=True,expire=None,tag='fib_disk')
  9. def fib_disk(n):
  10. if n <= 2:
  11. return 1
  12. else:
  13. return fib_disk(n-1)+fib_disk(n-2)
  14. if __name__ == "__main__":
  15. t1 = Timer("fib_disk(100)","from __main__ import fib_disk")
  16. print("fib_disk(100)",t1.timeit(number=1000),"seconds")
  17. #运行时间:
  18. fib_disk(100) 0.12914266199999996 seconds

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

斐波那契数列递归深度100,1000次运行平均秒数如下:

#计算结果:

使用计算缓存:
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次平均时间感觉说起来就没意思,记住一句不要玩火就好;

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

递归深度100,不使用任何优化跑1000次我的物理机cpu转的飞起,cpu利用率时刻接近100%,运行了15分钟没出结果我掐断了,所以如果递归深度够大请不要玩火,然后尝试小的深度和之前最优方案比对了下:
20深度1000次平均结果:

fib--20 1.707903223 seconds

然后再看下lru_cache缓存20深度1000次运行平均结果:

fib_cache(20) 7.102700000000128e-05 seconds

注意是科学输入法输出的,7.1乘以10的-5次方,结果显而易见,优化速度是多少备我就不算了,所以你的递归还不使用缓存优化吗?

阅读(1021) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~