你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

对一些 Python 代码加速运行的技巧进行整理

[复制链接]
gaosmile 发布时间:2020-9-4 15:09
Python 是一种脚本语言,相比 C/C++ 这样的编译语言,在效率和性能方面存在一些不足。但是,有很多时候,Python 的效率并没有想象中的那么夸张。本文对一些 Python 代码加速运行的技巧进行整理。
微信图片_20200904150838.png 6 |# F- {. d2 I; U
% ^5 M8 E$ E0 Q' j- M3 Y8 E' c
0. 代码优化原则
本文会介绍不少的 Python 代码加速运行的技巧。在深入代码优化细节之前,需要了解一些代码优化基本原则。
第一个基本原则是不要过早优化。很多人一开始写代码就奔着性能优化的目标,“让正确的程序更快要比让快速的程序正确容易得多”。因此,优化的前提是代码能正常工作。过早地进行优化可能会忽视对总体性能指标的把握,在得到全局结果前不要主次颠倒。
第二个基本原则是权衡优化的代价。优化是有代价的,想解决所有性能的问题是几乎不可能的。通常面临的选择是时间换空间或空间换时间。另外,开发代价也需要考虑。
第三个原则是不要优化那些无关紧要的部分。如果对代码的每一部分都去优化,这些修改会使代码难以阅读和理解。如果你的代码运行速度很慢,首先要找到代码运行慢的位置,通常是内部循环,专注于运行慢的地方进行优化。在其他地方,一点时间上的损失没有什么影响。
$ z* K& u+ J* _" I
1. 避免全局变量
( \" N9 q/ [% _5 N" A# 不推荐写法。代码耗时:26.8秒! k8 H" f& T+ x# x8 A) H
import math9 P' D* W3 }# G  p* _7 A

  j# k1 B  [8 R, C4 A4 H5 n9 G! isize = 100001 E$ G7 L0 c7 l  s8 E
for x in range(size):
, `5 x1 h; w7 D' W; K0 H1 O0 D    for y in range(size):7 P6 S  D& G6 E' Y
        z = math.sqrt(x) + math.sqrt(y)" y$ C. c. ?& j+ U
许多程序员刚开始会用 Python 语言写一些简单的脚本,当编写脚本时,通常习惯了直接将其写为全局变量,例如上面的代码。但是,由于全局变量和局部变量实现方式不同,定义在全局范围内的代码运行速度会比定义在函数中的慢不少。通过将脚本语句放入到函数中,通常可带来 15% - 30% 的速度提升。
# 推荐写法。代码耗时:20.6秒
$ Z. A1 V2 {4 n- ]& w; H& Y8 R* M. oimport math4 w" O+ ?. u7 \8 B" I: w
1 d2 a" R; o1 _; Z, X' d
def main():  # 定义到函数中,以减少全部变量使用
: d! R, \8 K+ ?% n$ g) u& t    size = 10000
  d6 d$ \8 S8 {4 g, A    for x in range(size):) C9 _% J8 v! h3 P4 {* ~6 A
        for y in range(size):
, h7 U0 r4 x+ K4 i- r+ z            z = math.sqrt(x) + math.sqrt(y): Y3 s  V: O, z3 c! L7 m* G
) X+ D" u. |) R
main()
7 G1 H& `4 c; I

$ g' {1 e" s6 K/ d& `& S* ]0 U
2. 避免.(属性访问操作符)
$ p, _8 f7 U" l" N+ U9 d2.1 避免模块和函数属性访问
+ @% ]/ u" I8 n, ]* D" q' Y/ i# 不推荐写法。代码耗时:14.5秒
* \7 b0 i! h6 s9 c& M7 V; oimport math, X: ?% c, k0 N& @& [6 V

% N- i; I4 J8 L) Xdef computeSqrt(size: int):
- ]: x" N& Z. l( J, `8 |    result = []9 c6 @3 D! l6 M, N
    for i in range(size):, T0 J0 j% n) C+ I
        result.append(math.sqrt(i))0 q6 I) T. Y) `7 Q: I
    return result
6 G& [& e% G/ W0 n" S6 T/ N7 e5 X2 V% B# K. k
def main():
: e4 R9 {  r  z) X+ V    size = 10000- L8 x3 Q7 a. H9 j: _; u/ b
    for _ in range(size):
1 D  m; ^$ j, N& d5 _1 b        result = computeSqrt(size): ^4 d6 D4 n( y

/ |. r1 T* M" G; z9 Dmain()
, @+ S' I! g  a) I: j
每次使用.(属性访问操作符时)会触发特定的方法,如__getattribute__()和__getattr__(),这些方法会进行字典操作,因此会带来额外的时间开销。通过from import语句,可以消除属性访问。
# 第一次优化写法。代码耗时:10.9秒
( g; f7 m5 J3 \) `$ sfrom math import sqrt$ r) I1 R4 v1 d5 v! U! |1 F

6 {1 M* Q3 w0 H- j  udef computeSqrt(size: int):
! T5 g$ x5 m" J) @& x' _' k2 i1 J    result = []' Z* p& f# T5 _0 G/ _% {4 ?
    for i in range(size):
7 q6 E. {  r1 ^        result.append(sqrt(i))  # 避免math.sqrt的使用
3 s4 J% K8 N; p6 |! E: M8 w! o    return result9 N  @  V7 Z3 q1 I/ v

4 d: `% Y* p, ~0 y) Y- h! [) Ydef main():' ?3 ]: s# v$ C7 Y& {- M4 ~' ?7 F
    size = 10000
* C0 \9 r0 y" Y% |) {# Q    for _ in range(size):
1 L! I7 C* S$ q3 D* q& w! o" |" O        result = computeSqrt(size)
' p! W5 p6 e( R9 I& b* H
# p$ g, }. w6 }; ?, dmain(). r7 M6 q2 E' @9 G6 F
在第 1 节中我们讲到,局部变量的查找会比全局变量更快,因此对于频繁访问的变量sqrt,通过将其改为局部变量可以加速运行。
# 第二次优化写法。代码耗时:9.9秒( H6 C* d% b, H0 S" s, ^* ?3 A
import math
* g3 f+ v; b& H3 G
: Q- n# ~5 Z( A- N! V5 u0 G5 ydef computeSqrt(size: int):- ?0 Q! x! f) r1 N& b' _
    result = []3 L, b! k7 [$ W( ^7 n
    sqrt = math.sqrt  # 赋值给局部变量
' u  [: N, E+ l    for i in range(size):( u1 w* `2 u6 b- |$ T
        result.append(sqrt(i))  # 避免math.sqrt的使用& Q* @% T! \( t& _: e9 T* ~* Y6 j' \
    return result0 f: u1 c/ k" g) [/ L

  N4 b& Q' E2 U2 @' ]% qdef main():
. Y- b; d. V/ P) c1 k" u& b3 i    size = 100009 d  ?7 E% Y6 e+ x4 d* F
    for _ in range(size):
2 H  G1 i% b8 E0 z        result = computeSqrt(size)
) s! \- f2 N& |: m6 {7 m" }) J5 b8 K6 G9 d2 m4 ~5 l- S
main()
; H( J! p* l& _! U
除了math.sqrt外,computeSqrt函数中还有.的存在,那就是调用list的append方法。通过将该方法赋值给一个局部变量,可以彻底消除computeSqrt函数中for循环内部的.使用。
# 推荐写法。代码耗时:7.9秒
% u+ L$ m$ _& q1 G3 Y8 ~7 D, Eimport math
6 G7 R. V$ o1 L6 t( k, F5 S4 v* b$ ~, _( D: E7 ~8 l
def computeSqrt(size: int):6 q" K& K1 |/ j5 K/ ]9 }
    result = []8 P% |% k+ s$ ~  U
    append = result.append$ ?& i" M4 u5 O* d$ A
    sqrt = math.sqrt    # 赋值给局部变量
8 [* _- V- N- D  ?+ b2 e2 e    for i in range(size):) |- U% s0 H" {- l. k) J& s
        append(sqrt(i))  # 避免 result.append 和 math.sqrt 的使用6 E& a  V1 n9 k* T1 y% B. }9 `1 ~
    return result
# r' ?  V; j; a" C5 z1 E
( e! m7 F0 B( D5 l; g# A% bdef main():& b/ H  f+ W8 I$ F# f6 R
    size = 10000
, u& r/ e$ D3 A4 ~    for _ in range(size):+ ^( w/ D; ~3 g$ F) z6 y3 l
        result = computeSqrt(size)6 g5 q: h8 h$ }) \/ \
- o! Z+ ?" x+ T4 G
main()
+ {. u- @8 ?2 H1 W
2.2 避免类内属性访问
1 I; T5 t3 [- H, a8 ^; e6 q$ n# 不推荐写法。代码耗时:10.4秒  X% N8 }0 d0 _, b5 Q
import math
1 x& L9 a, [# y% |* s) Lfrom typing import List- R8 w& W$ E. s9 {

' d; ^) ~; ~) z/ z# E5 o+ v  x; ^class DemoClass:
. K. x  ]: B& ^+ w- ]" A+ t    def __init__(self, value: int):+ M7 O$ q' b7 q! O& p
        self._value = value
$ g  e  u' G" ^" ~' K8 h+ l. Q' u  d   
! j* U: E6 i# y0 {6 [    def computeSqrt(self, size: int) -> List[float]:
+ y6 {5 u- P; t' O2 U' t        result = []
0 S2 N5 A& ?) s& n; O) l        append = result.append
* @& e3 D; A6 R. d; f' ~        sqrt = math.sqrt) V3 ?. R4 w4 S- D& [/ W& J
        for _ in range(size):
' n1 @) ~9 |* y            append(sqrt(self._value))4 f5 @$ q+ X1 a7 L- `
        return result) X- E( J. l9 u( Y* Y! B

- V7 M, R6 R6 T1 ddef main():
  |# @+ }! i/ h. M5 F6 b' a1 k* m8 i    size = 10000' W, h- q. a; Z* V- z5 E1 j; k
    for _ in range(size):' T6 k2 Z9 y' r/ }# D6 |' j
        demo_instance = DemoClass(size)4 o0 a! M, F! Y: ~& F4 W
        result = demo_instance.computeSqrt(size)7 f) n' h* P8 e; K+ g

3 Y! t  B$ p, G. i) C5 u2 fmain()6 ?. Q. G2 k. t+ n
避免.的原则也适用于类内属性,访问self._value的速度会比访问一个局部变量更慢一些。通过将需要频繁访问的类内属性赋值给一个局部变量,可以提升代码运行速度。
# 推荐写法。代码耗时:8.0秒
) A% S) \0 G- ^0 kimport math
3 K& i  Z$ F, K9 Xfrom typing import List
: ^) g9 f* \" d- j2 N
5 B: }5 s: d/ _7 I9 \9 lclass DemoClass:
+ x1 p# G; h& R4 L8 A' @' j    def __init__(self, value: int):
: A. z# B* d7 S0 s' y        self._value = value
& t2 Z0 h# R& g( O9 ]" x& e    ( [/ {4 R4 B+ S# T2 t' H
    def computeSqrt(self, size: int) -> List[float]:
& b" }7 C6 Z. {8 c        result = []5 u2 u5 H2 ]0 x& V% M, e! O
        append = result.append
4 q3 i/ O  ~% i* K; D& i        sqrt = math.sqrt7 D" C" n) D9 @# [6 F
        value = self._value7 H: k  O, N! t& M" F# ]
        for _ in range(size):+ N5 Z4 B/ t! _( D
            append(sqrt(value))  # 避免 self._value 的使用
+ d" `1 y8 N3 j. h+ b8 H        return result
9 D7 t- |) X" m/ _4 h- X, t. z; S# C! M+ N
def main():
" D  e& U  C/ G% E2 u$ y( G    size = 100006 o/ M% I0 ^" S# C7 _
    for _ in range(size):
& L5 i% Q9 z9 S4 @8 M6 Y        demo_instance = DemoClass(size)
7 J& Y0 e1 @2 S, H. u* @4 d$ V        demo_instance.computeSqrt(size)' F0 L8 r+ s& I2 z  }: S

# o9 g: ~2 J! `/ Y8 v6 J# omain()& j, b1 I: T$ n
3. 避免不必要的抽象, }, l' u9 L" B* |- n. b2 F
# 不推荐写法,代码耗时:0.55秒
% o! B/ z# a5 [' Y( u' xclass DemoClass:
9 I/ B5 S9 r# S8 P4 N" g    def __init__(self, value: int):1 ]) C1 d) F' u5 ?
        self.value = value+ \( s. d3 c3 t' q0 I3 Q

+ T: u! @: I/ G( {    @property
# G+ l# O( A( ~7 x4 D, T' C    def value(self) -> int:' w0 f' r6 }( u! C3 h( _6 T$ f
        return self._value
" [2 Q# u3 i7 P& S
: }# T0 s7 ^% m7 l  ]; |    @value.setter
3 V9 S7 `" ^1 D$ X, _2 ~5 a- p    def value(self, x: int):
) m. h4 s) G; m        self._value = x- \! G+ L( N% r

4 o+ R, o, d* r/ n5 \" L  odef main():0 _5 e: o! x& ~& d$ n' y+ x
    size = 1000000
' L% @+ h' x9 {1 B6 c: B, {    for i in range(size):1 }$ i1 [, }3 k
        demo_instance = DemoClass(size)
( @, k' w- W' [        value = demo_instance.value
" {5 v4 K: [, f3 V! ]        demo_instance.value = i
) o( }' K) z. B& e7 ^% d8 ~; R2 @% m3 O) L
main()
) h: N9 N: O# ]' X5 v
任何时候当你使用额外的处理层(比如装饰器、属性访问、描述器)去包装代码时,都会让代码变慢。大部分情况下,需要重新进行审视使用属性访问器的定义是否有必要,使用getter/setter函数对属性进行访问通常是 C/C++ 程序员遗留下来的代码风格。如果真的没有必要,就使用简单属性。
# 推荐写法,代码耗时:0.33秒+ \3 ~. ]! U! \9 q* Z
class DemoClass:
/ P* r- n$ L. q2 D    def __init__(self, value: int):6 Q4 F& ^9 }5 }$ {9 C. N) a/ w
        self.value = value  # 避免不必要的属性访问器
0 C& N. C& Q3 E! @$ @3 X  R+ i8 H2 N' ]- V
def main():
3 c9 S6 _) j  j5 o( E    size = 10000005 C! T1 R" a' [* R7 D9 I" I4 Z% `' ~
    for i in range(size):5 Z9 C5 l' y1 l" n
        demo_instance = DemoClass(size)
. u8 o+ W$ ]7 ^" B2 \7 ^- \        value = demo_instance.value. {( b0 G3 p$ `2 t' \) ?. o* ~& q2 C
        demo_instance.value = i/ |' d  B. E  a7 z2 J! N  r: r2 @
0 T" S$ Z9 @) J( d8 Q+ ]; U
main()
* z) J# x3 a6 ^# k9 Y  ]
+ a/ P) {' e3 I  d: t5 j8 v; J
4. 避免数据复制$ F0 A5 Z8 Q  W7 g) @* N7 S
4.1 避免无意义的数据复制
/ y" K/ h% i' V4 ?7 F) ?( J" \- [# 不推荐写法,代码耗时:6.5秒
0 y' F/ Y& g. }9 N( u* {5 ?def main():* i  A2 V$ P5 h3 j/ t# D* ]
    size = 100000 p! f$ h8 V% _1 j7 B
    for _ in range(size):
! o. }/ M$ A2 S7 s1 h$ c; X0 J        value = range(size)
7 D. x8 A, T4 e/ Z        value_list = [x for x in value]
) V; `$ T- ?: I0 x" m0 J, y        square_list = [x * x for x in value_list]% y( }  |/ e6 ?' B8 O

+ j9 T7 C7 C* `main()
7 v. v# u0 o' {
上面的代码中value_list完全没有必要,这会创建不必要的数据结构或复制。
# 推荐写法,代码耗时:4.8秒) x& J% v/ j8 x/ E) M8 b: X
def main():' y  K6 G. c) a! O( K' ?7 p
    size = 100007 p4 }* z" T* N% K
    for _ in range(size):# Y" L# E" ?( f
        value = range(size)
) Q( |( f/ `* K6 B        square_list = [x * x for x in value]  # 避免无意义的复制
, [+ r8 o6 ~9 `: b- c
' R/ U6 y8 J/ m: l+ u- A6 Z+ B+ vmain(). _6 i% d/ p6 U
另外一种情况是对 Python 的数据共享机制过于偏执,并没有很好地理解或信任 Python 的内存模型,滥用 copy.deepcopy()之类的函数。通常在这些代码中是可以去掉复制操作的。
4.2 交换值时不使用中间变量6 Z* P5 j2 [% q0 p- U7 K
# 不推荐写法,代码耗时:0.07秒  }9 l* B  D' j" N5 Y+ w' S
def main():) z" g# f: b" }* P# O+ Q
    size = 1000000$ F8 ]6 B. g- [/ }4 m& N
    for _ in range(size):. R  W, d! H6 c7 ^" f9 D
        a = 3
+ G4 B/ h. a, X4 r) m; N! _1 U        b = 5
  ?9 P4 x  l2 _$ N8 {$ g# N        temp = a
/ n3 \; ~$ }3 q& r        a = b
7 d+ f2 m( P" I4 a8 b  ~+ m* Y. S        b = temp
& V3 p% W* L- V/ K1 g% Y- L% m. Z1 z, {8 [
main()3 |! y! W& e- V9 ^& S
上面的代码在交换值时创建了一个临时变量temp,如果不借助中间变量,代码更为简洁、且运行速度更快。
# 推荐写法,代码耗时:0.06秒4 Q! A' I; F: v! x! a, }
def main():) y# `! J, n7 \/ q( q
    size = 10000009 [( O* f, I2 d8 k% u
    for _ in range(size):* M" z) g) M: [1 i# J1 V( {
        a = 35 ?3 W" _6 P9 x; r' z
        b = 5
" L' |$ g5 f: G7 W6 `. }5 m        a, b = b, a  # 不借助中间变量" K4 Y/ T- m: U; S5 I& c% s5 V
! b+ s8 A4 j( m. M/ A4 F$ l
main()# X0 X! ?2 F+ ^& c
4.3 字符串拼接用join而不是+
0 L2 o7 E7 |3 f: t9 P6 g# 不推荐写法,代码耗时:2.6秒
9 o( i& C4 n0 X4 {import string) {$ a* J, O0 v4 {/ |( v! M% ~$ w! z
from typing import List! Q6 p+ D! E8 X$ h+ K

3 v+ [7 y1 B+ M' P2 b! u9 `) tdef concatString(string_list: List[str]) -> str:
4 J5 u5 \9 e% F# D8 F' D, H- s2 Q    result = ''' A# U. _  _' H% A
    for str_i in string_list:
8 ?+ `' w$ j9 {8 O) G& U& w        result += str_i
) b4 i$ G7 c5 C3 g; e5 N    return result0 B; E- e. i1 i5 v9 B
; A' a. E2 O9 {- ^6 s8 R$ h+ O( _+ T, h
def main():* o+ b: p" `8 u: X" a1 ?( O
    string_list = list(string.ascii_letters * 100)
8 G) A% l  Q( Q4 P: o( q    for _ in range(10000):
# G9 U/ |8 V0 D: g7 v* Q' O" V) c( Q. t        result = concatString(string_list): h3 o. H  \$ {1 ~/ q. Q4 j5 s

" c, s5 N# l: i3 ?3 g0 [main()
3 E& B1 L9 f4 `& W0 s' @6 Z
当使用a + b拼接字符串时,由于 Python 中字符串是不可变对象,其会申请一块内存空间,将a和b分别复制到该新申请的内存空间中。因此,如果要拼接  个字符串,会产生  个中间结果,每产生一个中间结果都需要申请和复制一次内存,严重影响运行效率。而使用join()拼接字符串时,会首先计算出需要申请的总的内存空间,然后一次性地申请所需内存,并将每个字符串元素复制到该内存中去。
# 推荐写法,代码耗时:0.3秒) ?' b& T  k$ n5 }- C. N4 g. R
import string8 B& p( Z7 k2 C' o4 [4 ]
from typing import List
+ T# t, q9 B3 X; o3 w& i! `+ J' X3 w4 }
def concatString(string_list: List[str]) -> str:
/ E, Y5 i' g6 ^) r# O    return ''.join(string_list)  # 使用 join 而不是 +* q  o! ]/ u) v' o
) P( g% J7 @! N, |
def main():& T4 t, i3 I5 s  C5 d# [  V$ y
    string_list = list(string.ascii_letters * 100)
$ z' d4 d. p$ E" e    for _ in range(10000):
, F, n' h5 k4 h5 b# ~        result = concatString(string_list): s: \: J) ?1 V# K1 G9 D

) z2 a2 p% A& J# v' \main()
# L+ i% v0 Q8 y, ?
* ~4 U, D. i' `. d
5. 利用if条件的短路特性, R4 t% ?: V2 M( X2 W) D
# 不推荐写法,代码耗时:0.05秒$ [% s" K- r, D) L1 a
from typing import List
( f8 ~* x) v/ ?' y1 j+ |' V; b3 \6 t4 B2 F* N0 ?: H- s
def concatString(string_list: List[str]) -> str:2 ^- l9 _% j0 U0 L, Q1 r5 c3 Y
    abbreviations = {'cf.', 'e.g.', 'ex.', 'etc.', 'flg.', 'i.e.', 'Mr.', 'vs.'}9 Z. g' v0 u4 ?4 I% t  J# T6 i- o: Z" v
    abbr_count = 0
: L8 J! k" |+ f8 ~& Y! h, C2 }    result = ''
7 p3 X$ n* H  ^4 u, c9 o8 n    for str_i in string_list:: b. S+ g6 d( e% u
        if str_i in abbreviations:
/ D1 \5 U. T, c            result += str_i
+ g5 J1 Q5 ?2 }% q4 L" V    return result
! W: o7 X* B9 s1 |  K& U" Y0 y) ~6 Y( f% ]' v0 I& Q* @9 P
def main():
6 C+ |4 O" @/ E: P" V    for _ in range(10000):8 m& c: F. H# A7 r
        string_list = ['Mr.', 'Hat', 'is', 'Chasing', 'the', 'black', 'cat', '.']: }! u' A: i* d* Y
        result = concatString(string_list)
) w$ t8 C+ D& M: G! @/ s/ o1 [# F$ x- N+ {) S4 y5 |
main()
0 L9 p# G  q  H+ c# x" d
if 条件的短路特性是指对if a and b这样的语句, 当a为False时将直接返回,不再计算b;对于if a or b这样的语句,当a为True时将直接返回,不再计算b。因此, 为了节约运行时间,对于or语句,应该将值为True可能性比较高的变量写在or前,而and应该推后。
# 推荐写法,代码耗时:0.03秒
/ T& j% k: @# p4 M; |from typing import List) ?6 N/ ?+ M4 T7 K
( @' u( ^+ q' E2 R1 g: N
def concatString(string_list: List[str]) -> str:- O9 L* p) S# a5 u) Z2 }1 F
    abbreviations = {'cf.', 'e.g.', 'ex.', 'etc.', 'flg.', 'i.e.', 'Mr.', 'vs.'}
# Q' f+ E* G$ A* `1 P2 N1 j  R    abbr_count = 05 {  p" x7 F9 d" _; t& [0 H+ n
    result = ''
, \: |5 X6 M; t- d    for str_i in string_list:/ N6 v) Y' f" i5 V
        if str_i[-1] == '.' and str_i in abbreviations:  # 利用 if 条件的短路特性. Q! M* ]$ ~7 i" J: y" }
            result += str_i
/ k% H9 b/ \3 H    return result
  L& h' v  ]0 ]
9 @4 S+ T* x" Cdef main():
5 \1 P  P$ v; }. N% F# `    for _ in range(10000):
% S6 g4 }" L+ B; L8 m  I        string_list = ['Mr.', 'Hat', 'is', 'Chasing', 'the', 'black', 'cat', '.']2 [1 K6 G1 x2 ?
        result = concatString(string_list)
3 \7 j- E( ?! N- x* @1 K: M* _5 v' l+ j! w
main()

  c- P! g4 c" S  C( t# f5 d8 v' f' B7 T1 t, z% O" `
6. 循环优化$ B5 {; u7 L6 z6 Q% G6 Q- k
6.1 用for循环代替while循环' o3 S* s8 h' s2 c4 t  t8 d
# 不推荐写法。代码耗时:6.7秒, ]$ U4 x% c4 P0 u! r
def computeSum(size: int) -> int:% \' k. b* ^4 I! V3 z9 M
    sum_ = 0+ D" k0 V" ?. ]( x) v# v  w
    i = 0: n4 r4 ^8 d) f& A  L
    while i < size:+ X& O  h& `# s+ P8 K
        sum_ += i
" l/ y8 M; k. t        i += 1) |1 w$ D( z5 c7 [* O5 Y9 X
    return sum_. v/ k; p. [5 M( j

2 \, Y0 t$ q7 W3 H/ [1 e0 x7 Odef main():/ e* ?0 t/ }" j; g
    size = 10000
+ I+ _, v1 j, k7 {9 z2 S& n    for _ in range(size):$ u6 y+ ~5 E# _' l
        sum_ = computeSum(size)8 ~% V0 F8 e$ g

+ Z, }( o/ l- Rmain(); m- ~; ^* P, J
Python 的for循环比while循环快不少。
# 推荐写法。代码耗时:4.3秒
' _1 n) B4 d" T. u8 U3 }# W; ~def computeSum(size: int) -> int:5 _; Z6 T5 w  o* o7 Z
    sum_ = 0
+ n9 [0 F2 ]# e    for i in range(size):  # for 循环代替 while 循环
7 Y6 x- `4 e! E6 E        sum_ += i! w6 M4 O1 o0 F3 _' g7 l1 r
    return sum_% v+ D  |2 K1 \. K) T" [% d
7 x4 h4 m% O8 b9 Y& D! [/ _. ?. o$ T* L
def main():
9 l7 f7 u* `- [6 d$ U    size = 100002 z& [1 s% x- k3 g1 v- K
    for _ in range(size):8 A( e+ z! B5 d) ~0 q0 ?
        sum_ = computeSum(size)
2 j) B: {8 G: [8 U) S- i1 g& T, f) L5 R% k; C
main()
( c( z( q% s8 M3 r
6.2 使用隐式for循环代替显式for循环
针对上面的例子,更进一步可以用隐式for循环来替代显式for循环
# 推荐写法。代码耗时:1.7秒7 N6 _2 ^& h3 Y
def computeSum(size: int) -> int:& F7 w3 r0 l! O# N( k$ ]$ C% I
    return sum(range(size))  # 隐式 for 循环代替显式 for 循环# R( x2 T! r: x4 M  C

% X2 t/ n) _( \% J; U) F: _def main():% |7 N3 {; Y- V6 y/ H5 Y; D) C
    size = 10000
: p* c: C# A$ C: x8 A' `    for _ in range(size):
3 ~' B1 p" f6 s$ v9 ?) W9 X. i5 _        sum = computeSum(size)
& R; u. Q; e( s& j6 O, |' }
% w% G3 S5 h! R8 T( n5 q; Gmain()/ ^( P: Y# U  Q7 A- I$ E: L& Q& R
6.3 减少内层for循环的计算) A) `9 ^) B# q9 G1 j# d
# 不推荐写法。代码耗时:12.8秒
" H" D4 O$ s9 ~+ l* simport math
' t, j" v- G- n- J- d1 E
- Q2 o  U# g8 Mdef main():
  B) o( ]9 v& g: v" W6 N$ F6 s; q% ?    size = 100003 |: i) P' ?5 F0 f+ ]" J0 g
    sqrt = math.sqrt
- ^$ v( d3 U9 d  t1 n' Z    for x in range(size):! ?* ^- u6 b6 U  o
        for y in range(size):
( J5 a0 i" Q5 G% Z4 _2 Q" r            z = sqrt(x) + sqrt(y)
( `3 Z! ^: B6 M! z) ~$ J. V/ ~" v+ P, M% h0 S$ r: t
main()
' s' l2 k# X# K! `/ C
上面的代码中sqrt(x)位于内侧for循环, 每次训练过程中都会重新计算一次,增加了时间开销。
# 推荐写法。代码耗时:7.0秒
4 ?0 M1 l/ ^( R- ximport math, C8 @  K4 x) _

* s/ b& o1 ?2 N$ ^, l7 Fdef main():
+ U1 G4 g# P9 f! x3 o! {    size = 10000
& o: Q" Y# n; V. B2 r    sqrt = math.sqrt
; L' L8 T: U) s' q$ x    for x in range(size):5 m1 h  Y6 \7 ]* ^3 x7 i
        sqrt_x = sqrt(x)  # 减少内层 for 循环的计算5 O) N5 c1 y/ r4 a5 e% \0 k
        for y in range(size):
, B  Y0 Y; F: Q) p& L            z = sqrt_x + sqrt(y), s* m% v  R+ {8 k

7 F- c/ w* ^4 L% R. w; Qmain()
4 W  {, P% d$ q8 m
$ Q0 Z9 K$ d8 P5 @# k' |, d- ^! x( m
7. 使用numba.jit
我们沿用上面介绍过的例子,在此基础上使用numba.jit。numba可以将 Python 函数 JIT 编译为机器码执行,大大提高代码运行速度。关于numba的更多信息见下面的主页:
http://numba.pydata.org/numba.pydata.org
# 推荐写法。代码耗时:0.62秒
- R0 G! Y5 L3 O0 a( E6 P0 kimport numba
/ H) K1 j$ ]  B/ U* ?# }4 p, d, R% {8 o
@numba.jit
5 w: {% [* H7 ^) E1 N# B& P0 wdef computeSum(size: float) -> int:8 @) f- Q8 d; U! r9 _7 {
    sum = 0
$ D! J% p4 k1 @4 T6 t    for i in range(size):
" f$ C) H6 Y$ T" S        sum += i
7 N: r5 l3 g* i5 _8 _    return sum
' r- m+ u, @& |0 ]7 T3 {
! `5 y( B" Z$ c& n3 p% q8 ]  Ydef main():
5 g' o4 G) Z: o- v" q/ f. b8 X    size = 10000$ {( }& p. @6 J0 _. T
    for _ in range(size):; e$ Q/ e& w  Z. \( L  u) j9 E8 f
        sum = computeSum(size)" J! i  A; P( R- L  A7 |4 F
6 c! w7 Y9 h" h0 K# z5 \6 v$ O+ ~
main()

$ A. F: b" k% @- r' B: k2 ~4 I; l& H& S
8. 选择合适的数据结构
Python 内置的数据结构如str, tuple, list, set, dict底层都是 C 实现的,速度非常快,自己实现新的数据结构想在性能上达到内置的速度几乎是不可能的。
list类似于 C++ 中的std::vector,是一种动态数组。其会预分配一定内存空间,当预分配的内存空间用完,又继续向其中添加元素时,会申请一块更大的内存空间,然后将原有的所有元素都复制过去,之后销毁之前的内存空间,再插入新元素。删除元素时操作类似,当已使用内存空间比预分配内存空间的一半还少时,会另外申请一块小内存,做一次元素复制,之后销毁原有大内存空间。因此,如果有频繁的新增、删除操作,新增、删除的元素数量又很多时,list的效率不高。此时,应该考虑使用collections.deque。collections.deque是双端队列,同时具备栈和队列的特性,能够在两端进行  复杂度的插入和删除操作。
list的查找操作也非常耗时。当需要在list频繁查找某些元素,或频繁有序访问这些元素时,可以使用bisect维护list对象有序并在其中进行二分查找,提升查找的效率。
另外一个常见需求是查找极小值或极大值,此时可以使用heapq模块将list转化为一个堆,使得获取最小值的时间复杂度是  。
下面的网页给出了常用的 Python 数据结构的各项操作的时间复杂度:
TimeComplexity - Python Wikiwiki.python.org

+ v" I1 [# y  {# r+ _9 {4 k4 G% k# s2 w  l/ L' F$ n3 M
收藏 评论0 发布时间:2020-9-4 15:09

举报

0个回答

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版