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

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

[复制链接]
gaosmile 发布时间:2020-9-4 15:09
Python 是一种脚本语言,相比 C/C++ 这样的编译语言,在效率和性能方面存在一些不足。但是,有很多时候,Python 的效率并没有想象中的那么夸张。本文对一些 Python 代码加速运行的技巧进行整理。
微信图片_20200904150838.png
+ a+ r; _4 t7 r8 o7 b# `' F: }/ d

' ?& `" Q. u! Y0. 代码优化原则
本文会介绍不少的 Python 代码加速运行的技巧。在深入代码优化细节之前,需要了解一些代码优化基本原则。
第一个基本原则是不要过早优化。很多人一开始写代码就奔着性能优化的目标,“让正确的程序更快要比让快速的程序正确容易得多”。因此,优化的前提是代码能正常工作。过早地进行优化可能会忽视对总体性能指标的把握,在得到全局结果前不要主次颠倒。
第二个基本原则是权衡优化的代价。优化是有代价的,想解决所有性能的问题是几乎不可能的。通常面临的选择是时间换空间或空间换时间。另外,开发代价也需要考虑。
第三个原则是不要优化那些无关紧要的部分。如果对代码的每一部分都去优化,这些修改会使代码难以阅读和理解。如果你的代码运行速度很慢,首先要找到代码运行慢的位置,通常是内部循环,专注于运行慢的地方进行优化。在其他地方,一点时间上的损失没有什么影响。
. G* o- v+ G4 g% \
1. 避免全局变量
+ L3 x- t2 W7 i, o! Q# 不推荐写法。代码耗时:26.8秒+ [$ K7 U, ?$ l6 ~
import math2 w: Q' O6 W* [% t5 H

, U9 L! ?  D2 [size = 100002 U( l/ C7 H# Y5 S! ^" g) y
for x in range(size):, x! q$ s# q% m$ Q# B
    for y in range(size):( m$ n+ O# V2 q7 J
        z = math.sqrt(x) + math.sqrt(y)
$ V/ F4 }/ h" }+ e# C2 H; v7 j0 B
许多程序员刚开始会用 Python 语言写一些简单的脚本,当编写脚本时,通常习惯了直接将其写为全局变量,例如上面的代码。但是,由于全局变量和局部变量实现方式不同,定义在全局范围内的代码运行速度会比定义在函数中的慢不少。通过将脚本语句放入到函数中,通常可带来 15% - 30% 的速度提升。
# 推荐写法。代码耗时:20.6秒
8 i. `4 d) R8 kimport math
7 C+ P  T+ ^; \- B$ `" h. ^
( J5 M8 F7 y: gdef main():  # 定义到函数中,以减少全部变量使用
- ~2 V9 G  T' U1 b    size = 100002 ^: ^' Z/ m9 a  n* J5 e6 W
    for x in range(size):
& x1 W' o: k" G' S( @$ ?        for y in range(size):
- Z) C9 S" l  R) A5 V$ Q( A! ]            z = math.sqrt(x) + math.sqrt(y)* e; P+ v# k2 @4 {
. y) e$ n6 d* Q( N
main()

4 ^. V) X8 |6 X
; U6 q; S6 F" b9 j' V9 x: ~
2. 避免.(属性访问操作符)
4 T6 \, s9 N; h- {% [; q% U2 C2.1 避免模块和函数属性访问
" o3 `+ o3 X3 n. ]- Z9 F$ r" L+ j# 不推荐写法。代码耗时:14.5秒7 y4 k  W5 d) o. z
import math
3 b( G( b. d% k" p9 S* t* P2 a
3 h* X! U! l, h! Ndef computeSqrt(size: int):/ V0 _6 ~) d6 C' V5 O5 Y9 b
    result = []* ?! N5 D, x) d% k0 n& p
    for i in range(size):" z3 s2 P& t* a* }2 E
        result.append(math.sqrt(i))1 U& v% R& z2 E' \# i% o
    return result( }, `9 R, ]7 f* i) F( ^) S! h

$ y3 _, G( w9 Z" u: W. odef main():, _: O8 C/ d9 h
    size = 10000! ~* F; @7 G0 X; l. R7 X$ O/ i: W
    for _ in range(size):
8 K6 U, d; G4 C( {. q        result = computeSqrt(size)! P. @  z" f; X8 ^  O0 s' [
. T6 q* B. S+ ~4 w. b
main()
/ A- Z; Z: W8 ^' O/ y
每次使用.(属性访问操作符时)会触发特定的方法,如__getattribute__()和__getattr__(),这些方法会进行字典操作,因此会带来额外的时间开销。通过from import语句,可以消除属性访问。
# 第一次优化写法。代码耗时:10.9秒, x. n" @! i% c) D; g! |( c  U
from math import sqrt7 M8 f( u. Q2 w7 o: i
7 [# x7 f4 v# a3 l3 h: A6 t: S
def computeSqrt(size: int):, ~% y( T& ~7 }' C
    result = []
8 C$ B! x, O* n2 r0 r$ ~6 R    for i in range(size):
! T- A% e1 _* [% E" t- G        result.append(sqrt(i))  # 避免math.sqrt的使用
6 S% n( _$ [2 b  h2 ]    return result) n+ f6 c' Z4 W
% k  N8 c- S3 T$ |$ {' V3 V3 i
def main():
. B, e8 J2 \4 {5 W' |    size = 10000
  n' A  I; }' B8 l. m) U" N( s) m    for _ in range(size):
4 ]3 O) E% [% D        result = computeSqrt(size)6 j6 _3 R4 e# r/ z) W' }% g

$ F2 }' w% A3 p9 z, h0 H; H: Bmain()
1 z6 k" a) g  K. i# P+ u) w! @" S
在第 1 节中我们讲到,局部变量的查找会比全局变量更快,因此对于频繁访问的变量sqrt,通过将其改为局部变量可以加速运行。
# 第二次优化写法。代码耗时:9.9秒
+ u( R3 H# v0 `* y2 Qimport math
2 }+ ]! ]8 V; |3 E
& }% v. [" Z! U3 a( p% e& Y& J& udef computeSqrt(size: int):
! @2 K6 z+ E$ T0 v: p* Z( L# v    result = []# i. v! g7 j" p& |0 \
    sqrt = math.sqrt  # 赋值给局部变量1 G. s5 f. b3 V& R
    for i in range(size):6 v# C! ?4 S# O9 @8 O* I& o
        result.append(sqrt(i))  # 避免math.sqrt的使用
: C$ G' P3 E- H# s8 E    return result. O  K, G1 D% E, L3 }' {
! v8 c" a, X+ \$ l
def main():4 O5 g2 T" j; b& T* E
    size = 10000) c  N- ~8 s1 ^. {
    for _ in range(size):
0 ]- `+ H) W" y$ M6 e4 J        result = computeSqrt(size)( I- T3 b9 ]% K  p+ a  q$ `

2 {7 _. l. x. `, C% q) T8 |7 Imain()& c  Q( n+ u8 m9 H8 |+ E- O
除了math.sqrt外,computeSqrt函数中还有.的存在,那就是调用list的append方法。通过将该方法赋值给一个局部变量,可以彻底消除computeSqrt函数中for循环内部的.使用。
# 推荐写法。代码耗时:7.9秒; j6 A5 _* v' @4 @0 R
import math
2 C# y/ D: `' M+ {/ {9 z1 N' u  o1 k! u, \) t
def computeSqrt(size: int):( E: M" V5 p. A: T
    result = []
' B. }$ ~9 Y  }3 N6 ?# m* b    append = result.append! {- T2 S% z8 q4 p2 }1 E* a
    sqrt = math.sqrt    # 赋值给局部变量
# v. \: k8 f! K& q0 ^, L    for i in range(size):7 Q. Y0 U, v  r5 A5 K$ E
        append(sqrt(i))  # 避免 result.append 和 math.sqrt 的使用# x% g: E% E* X4 X
    return result
+ \# r1 B7 _. o9 j& A
) ^& ]( k) Q6 t# ~; t3 F4 Cdef main():
* m4 P2 V; g) V- E+ u8 g$ m. |    size = 10000
; D. g: u# j- K- s    for _ in range(size):2 @. h0 r7 T& z1 }: T7 C2 J5 o
        result = computeSqrt(size)
& m/ ?: J2 K4 A7 q9 a. Z- c' {8 w* r7 G
main()
, R. A* s4 L' b( b% ~, `
2.2 避免类内属性访问
- T% S" m4 Q0 T1 P: o# 不推荐写法。代码耗时:10.4秒, F/ @8 K, M; W% p; y
import math0 E% O+ M- B, ?# ^6 C8 r
from typing import List1 \. O% i5 v/ L9 D" u
" N  D, x2 ~  ]  `- F) V5 W
class DemoClass:
! }. u+ Y: R8 h: U. f5 b# C    def __init__(self, value: int):
+ x, }2 X- y  a) J# U6 ~: Q3 m        self._value = value! G1 g! E% c' ?& X8 D0 U
   
2 t7 J' }' R- x( a( p- T0 F; e    def computeSqrt(self, size: int) -> List[float]:# g/ W0 W" W2 M. a$ [( {
        result = []
# `- g* L# p6 K0 d4 T        append = result.append
0 C: L1 h% G, J1 T% U4 G3 o- r        sqrt = math.sqrt7 K! Z/ H* h$ t2 G
        for _ in range(size):  g: n* O* @3 b/ ]1 \
            append(sqrt(self._value))
* K" @( |. u: R        return result. Z1 e; l0 U! \2 U5 `! c; q( N. P

/ J! x6 G3 h( Q. d# o' X; n, }def main():: M! u% ?" b" v; u3 k
    size = 10000& e3 I3 Z% I# O1 s* X0 C7 D- C# G" ~
    for _ in range(size):
- `1 g3 m$ f1 W: D3 e        demo_instance = DemoClass(size)
) N5 V- k' ^$ T5 ~3 @        result = demo_instance.computeSqrt(size)5 ]1 z6 I# |1 `! X" m7 h7 M

& `' r! j  |& \; i# y% L' Kmain()) |" I' j/ p, L, R
避免.的原则也适用于类内属性,访问self._value的速度会比访问一个局部变量更慢一些。通过将需要频繁访问的类内属性赋值给一个局部变量,可以提升代码运行速度。
# 推荐写法。代码耗时:8.0秒
( F4 X* k( p4 x2 dimport math- Y2 t4 L: P4 R- r7 e
from typing import List* J7 b8 Z+ k6 b' V) m* l2 i
8 N2 @$ g( U, A8 I9 v; r( v  J
class DemoClass:
- U9 ~) ~; ?" a' N% W) S# `& D1 j    def __init__(self, value: int):1 o, b+ }. J3 Y# E4 }9 b' _) Z/ G
        self._value = value' J% d4 L9 m" e9 d
   
" m; @. d. `0 A' P( q/ L! Q    def computeSqrt(self, size: int) -> List[float]:
1 m- Y6 V& ^2 \" n6 g% T7 d0 [        result = []
( I) Y& v) W& p        append = result.append
5 j" y9 X) s# K  Z. h3 L        sqrt = math.sqrt
5 e1 t+ W4 Q. m7 X6 I        value = self._value
8 u- z4 y0 y4 }: R# f' n- T# `% A        for _ in range(size):
3 _5 ~. E7 ]4 k& S2 l5 Q            append(sqrt(value))  # 避免 self._value 的使用
* Q6 e% n0 e7 ^- Z0 m: Z% Y. g        return result% d! _9 U" B( U" M7 C$ w
1 S; K( l. v2 b* u; `* y
def main():
$ Y. N5 z4 O1 f! m) ~* @    size = 10000
+ ?) c2 f6 y2 q) c! Q& ~    for _ in range(size):; B( }1 V' C% c" W
        demo_instance = DemoClass(size)
( \" R3 e  m( }/ ?$ ^        demo_instance.computeSqrt(size)" o; h  T& I. Q! s2 [

% J9 S4 X- t) v: o. bmain()
) h9 U, l) q2 Z/ A& r9 C. P
3. 避免不必要的抽象1 B, S5 P& @* q
# 不推荐写法,代码耗时:0.55秒: L1 T* Y( g2 k# K1 L( z
class DemoClass:. G3 Q$ V! k5 F( @" R; {; j4 |0 H
    def __init__(self, value: int):6 I$ s, l* r  ?8 f& H2 ~* }
        self.value = value3 l; c; w7 c- G: U& v
+ X  q- D3 x. `- Z
    @property
; u# I, t5 ^8 P; ^* h  C; {- k6 ]1 O    def value(self) -> int:& |2 I! F' V+ p0 W* H6 \5 A
        return self._value
, I  r" I8 e. q4 _8 m8 V
7 j, F, Y: L) L; I& Y1 Y    @value.setter, \1 p; c: `/ _: {
    def value(self, x: int):; W0 I3 K& v) t2 P
        self._value = x
8 z! e  H+ i- a
+ q! ^4 s" d/ y1 G; q6 g) g  {def main():. G$ w& ]& W! v: S, g
    size = 1000000
9 t. }* C6 p4 a% o' L" j" ^    for i in range(size):2 C1 `. h* B: g" I! t6 i0 \, ~
        demo_instance = DemoClass(size)0 ^  p# k3 X9 s  w
        value = demo_instance.value4 U& }4 T1 _) j7 I# m; v* e( u* ?
        demo_instance.value = i& g" u( |/ S, c9 X

, Q) x* Z1 W9 N; l3 H) ~& h+ omain()
* L1 R8 y! J6 n. s% L8 S6 \0 b
任何时候当你使用额外的处理层(比如装饰器、属性访问、描述器)去包装代码时,都会让代码变慢。大部分情况下,需要重新进行审视使用属性访问器的定义是否有必要,使用getter/setter函数对属性进行访问通常是 C/C++ 程序员遗留下来的代码风格。如果真的没有必要,就使用简单属性。
# 推荐写法,代码耗时:0.33秒
+ c9 }0 [; H9 x$ P2 g/ P; Zclass DemoClass:2 ~  X, k. K" n5 G8 U% v6 J
    def __init__(self, value: int):
  P# j* E- {( M% V3 Q5 y, r        self.value = value  # 避免不必要的属性访问器- F$ c3 U# x$ d0 w  w( @
2 h* }! |* l, F8 e) v8 J
def main():  M, F( v3 |4 z" C  D9 ^
    size = 1000000
8 R3 f# k7 y/ H# P    for i in range(size):: i" v3 {$ Q+ X7 v# D
        demo_instance = DemoClass(size)7 V0 |$ e, r; D+ [& i  C! K
        value = demo_instance.value
' p+ _# i) y& o- t; w4 |        demo_instance.value = i
" x5 N" N. f3 I- W' P7 A! S8 O/ V, m* F( h5 ^" N* B  J; b: D: v
main()

6 b6 Y/ U# ~0 O1 {$ c6 |( G0 c' R& M7 z- \5 P+ Y, y
4. 避免数据复制% D1 B- z1 {& i. Y% n( k* z4 b
4.1 避免无意义的数据复制: n: P1 Z7 q' J, N! N
# 不推荐写法,代码耗时:6.5秒
: b8 _& u( N$ M8 Jdef main():
' [5 m5 [8 Y  V( h    size = 10000
; m, u; `# H8 p+ q    for _ in range(size):# ]' |. ]* b" u+ B* R4 j. G' S
        value = range(size)2 T, V- G+ `9 \0 m6 c
        value_list = [x for x in value]: `7 s, r" }" R; [
        square_list = [x * x for x in value_list]( L" |4 ]" r! o. q, D* r8 A/ L
+ k! K& K8 d4 d9 H# G; ^/ K! d, C' P
main()
2 Q1 _5 G/ B: }' x
上面的代码中value_list完全没有必要,这会创建不必要的数据结构或复制。
# 推荐写法,代码耗时:4.8秒
6 u5 }$ O& F5 ], Mdef main():
; W- n8 y: n# p    size = 10000  A% d7 q$ D" |
    for _ in range(size):( P( q0 f3 b$ Q/ L
        value = range(size)$ r& {3 V  V4 v3 U4 }5 c
        square_list = [x * x for x in value]  # 避免无意义的复制
( U  v1 K7 B: y# j! M4 |9 x
: i: h) X0 x" C4 {4 Bmain(). C  K! O6 L2 U1 U  s7 e" ^, z. e
另外一种情况是对 Python 的数据共享机制过于偏执,并没有很好地理解或信任 Python 的内存模型,滥用 copy.deepcopy()之类的函数。通常在这些代码中是可以去掉复制操作的。
4.2 交换值时不使用中间变量
6 \8 ]$ E6 }7 y# 不推荐写法,代码耗时:0.07秒. d: x4 w( k9 H3 W! Z. T2 w
def main():
& T. I7 d* ^4 \' {    size = 1000000
* ]2 q# B" w0 X) Q9 z    for _ in range(size):- \. l# P5 j6 g+ n0 d2 y( t5 B
        a = 3/ S; P) B& s1 _! L9 o& A
        b = 5
3 [9 f& S$ i8 |. V2 q        temp = a# W8 p. o" m+ L
        a = b$ c# K8 N3 ^2 T8 F3 M
        b = temp7 [1 B) q8 F( n1 Q$ I: v* G  |

. k( Q* h2 Q& {6 _( N7 U+ I& zmain()
( @/ Q* w% l* i1 \: [
上面的代码在交换值时创建了一个临时变量temp,如果不借助中间变量,代码更为简洁、且运行速度更快。
# 推荐写法,代码耗时:0.06秒
0 F8 J3 P1 e6 |5 ~  e( C: Sdef main():. R3 c% A. ?. m
    size = 10000001 f8 r& |, V7 s" O$ m- `! U
    for _ in range(size):4 R3 x3 R4 k' B, Q
        a = 3
6 [1 g( r" J  q% \        b = 5: R6 m/ L* M. z5 L
        a, b = b, a  # 不借助中间变量. {* L) m" L2 w5 u$ P
8 O- d2 ?! H8 T" k4 a
main(); v# C( v, H3 i8 C
4.3 字符串拼接用join而不是+
1 C- Z7 m' D/ [# 不推荐写法,代码耗时:2.6秒+ u3 G8 A0 t. Q6 ?
import string
/ @! y3 v( A+ h* X, k6 z7 ffrom typing import List# M: \  t# ]  Z2 X! L0 }5 m

; L1 l/ z/ z( O6 {9 _' odef concatString(string_list: List[str]) -> str:* U6 S1 T& ~3 E* S  K
    result = ''
. h( V0 X+ U# C& W5 y, F    for str_i in string_list:
/ t' e& y  r& I  f) `, ~        result += str_i  I. U. _- ?# f- S  \
    return result. ~, @: `8 r: b( g

4 E. m9 k& R! H" Zdef main():
. R) p$ H$ P% `3 G' q6 @% R    string_list = list(string.ascii_letters * 100)
6 b& P3 \- Q. V+ Y) n, x3 n3 b  l    for _ in range(10000):4 s9 {5 O! l6 ]1 G) x8 N0 v
        result = concatString(string_list)
' \2 c) r( N% q0 u4 E  ~$ l$ W; B- J: W
main()
1 @. Q; l' C% V: t, P* v( h4 t
当使用a + b拼接字符串时,由于 Python 中字符串是不可变对象,其会申请一块内存空间,将a和b分别复制到该新申请的内存空间中。因此,如果要拼接  个字符串,会产生  个中间结果,每产生一个中间结果都需要申请和复制一次内存,严重影响运行效率。而使用join()拼接字符串时,会首先计算出需要申请的总的内存空间,然后一次性地申请所需内存,并将每个字符串元素复制到该内存中去。
# 推荐写法,代码耗时:0.3秒( Z3 s7 N* `/ L. h, w) l3 q( H
import string  q- `  f$ n; Q) C
from typing import List. \, y; ]/ S4 b" |5 m8 \
2 f4 V7 g0 u( f
def concatString(string_list: List[str]) -> str:
5 C3 l* \% H* ]5 S    return ''.join(string_list)  # 使用 join 而不是 +6 J8 ?3 o0 w1 u9 ]8 }0 H$ \
: r1 b! ?* t" m8 B2 V
def main():
' `% p; M6 K, i5 t" t    string_list = list(string.ascii_letters * 100)
# X/ A/ ]# K* \: w' j( F. {, a    for _ in range(10000):
" S( b& d9 s9 z/ f/ p) ]. i4 ]) K# h        result = concatString(string_list)
& [6 C" V1 i( v% t2 S* O& b1 g0 j
& D+ W3 `$ q' }  Vmain()
0 }1 X) S* T$ }

' j% h7 ]3 M  E8 n" }
5. 利用if条件的短路特性7 m! H. V5 a5 g7 r$ |
# 不推荐写法,代码耗时:0.05秒* `  z4 w4 @; [6 `3 e% f, ~
from typing import List
9 B( C3 t5 L1 s, O9 H8 `, @, P) @+ Q, c+ o; `4 N* H
def concatString(string_list: List[str]) -> str:" C8 y' Q) p- @. q" v6 V5 }
    abbreviations = {'cf.', 'e.g.', 'ex.', 'etc.', 'flg.', 'i.e.', 'Mr.', 'vs.'}
7 w# i" `1 G4 T: o" K  Q    abbr_count = 0" a; d$ S. @- ^8 ^
    result = ''; [9 i/ j2 K, K8 [
    for str_i in string_list:; X& y1 [$ @& r. U2 _) j. F% I
        if str_i in abbreviations:
% _' B) L# R4 w4 o! ~            result += str_i
# |$ w" {; R- Y2 _- b, \    return result% N; J7 c1 M/ A5 c- p9 p9 S* e

' a& K* X& H( ]! r. y. adef main():; q) v" l& S" W/ C* d- J  Y) c. i
    for _ in range(10000):
5 j8 e  v# E+ F8 ^        string_list = ['Mr.', 'Hat', 'is', 'Chasing', 'the', 'black', 'cat', '.']
( J) T( ~' |& K0 I! G% R        result = concatString(string_list)
% D! s8 X! ?0 R0 @! s4 n9 E1 H) s* E9 }, j$ e( K* M
main()5 i# D" _7 l! s: j; F2 {4 i
if 条件的短路特性是指对if a and b这样的语句, 当a为False时将直接返回,不再计算b;对于if a or b这样的语句,当a为True时将直接返回,不再计算b。因此, 为了节约运行时间,对于or语句,应该将值为True可能性比较高的变量写在or前,而and应该推后。
# 推荐写法,代码耗时:0.03秒- H- r5 N9 w0 M4 N% G/ R
from typing import List- z6 l0 ?1 F4 S* j4 `

% `8 z: u) m; Q) `2 u( R6 Gdef concatString(string_list: List[str]) -> str:7 G' _8 o4 l1 T
    abbreviations = {'cf.', 'e.g.', 'ex.', 'etc.', 'flg.', 'i.e.', 'Mr.', 'vs.'}! `3 V4 [( i( h8 `" _3 u$ p! Y
    abbr_count = 0
9 y0 A2 G, H# p+ T; B5 l3 o    result = ''8 l3 s% r% Z  S4 Y  o
    for str_i in string_list:
0 t, f+ y# g& T2 V1 ?8 H. c        if str_i[-1] == '.' and str_i in abbreviations:  # 利用 if 条件的短路特性2 W  e% e1 H: b1 a
            result += str_i
1 A" Z* I- O1 X! D4 q; o    return result+ K: e, x* H( c3 K, l- a

3 Z# A0 K# v9 }# bdef main():3 i# _8 G3 X; T" R
    for _ in range(10000):
1 n( m5 m- J) {5 ]% _( I/ K        string_list = ['Mr.', 'Hat', 'is', 'Chasing', 'the', 'black', 'cat', '.']% M) E- m% ^  K& f* I
        result = concatString(string_list)
1 [5 j* j# F$ ~  ?& m" P+ `6 {9 ?+ B& y
main()

# {8 h0 K/ ]: q8 e- o5 y, u# [9 l# n# o2 r; m! _1 u
6. 循环优化
* Y9 X- Z0 w9 f6.1 用for循环代替while循环' B( w: y( U- u) L
# 不推荐写法。代码耗时:6.7秒
  X6 X4 c" ]* \  p4 J2 ddef computeSum(size: int) -> int:1 R8 v8 ?1 B# l; H2 Y" F3 {
    sum_ = 0
  e8 a8 P% r$ t. d1 c/ k    i = 0! y( x+ ~% ]5 _# N/ a- S6 ]
    while i < size:) T& j6 q9 M  s3 ~" l" J' B- v
        sum_ += i
% w8 F+ N- j5 [4 v        i += 1
! M9 }. [9 m. ~    return sum_% w' u( M. N7 p( C9 Y1 j

0 F6 h* o/ A! s! N2 E. x) j0 udef main():
& r; z! G) x1 _7 d  C    size = 100000 y/ x+ T& _# Q  r
    for _ in range(size):; B7 @" r& t1 I, G- q& d
        sum_ = computeSum(size)* {* s6 ^7 m# D( s/ H9 B. a) j
9 m6 C0 w% f" J: N  r  Z
main()
+ P5 |7 K0 E/ o4 D
Python 的for循环比while循环快不少。
# 推荐写法。代码耗时:4.3秒' G& y1 C6 M! Q' t
def computeSum(size: int) -> int:' i! ^$ a2 s4 m- |( b
    sum_ = 0& s9 j% b9 ]4 [
    for i in range(size):  # for 循环代替 while 循环1 t9 \* r7 N3 c% Z$ @) B
        sum_ += i6 j4 |" H7 G/ v' Z! ^( T, m5 L5 H
    return sum_
- u+ K: o9 t$ a0 N6 F
$ {* `* ^  o# g# ?def main():5 R, w6 K3 q# ]; c; G
    size = 100005 A" A! C6 I6 G0 V( [* U7 L
    for _ in range(size):+ B' T: p' ]& d& O' I
        sum_ = computeSum(size)1 N8 t- E; h- R) ]5 a8 p

+ D% E7 d6 W% K$ @main()
. ]$ J+ \0 j; p! |4 Q
6.2 使用隐式for循环代替显式for循环
针对上面的例子,更进一步可以用隐式for循环来替代显式for循环
# 推荐写法。代码耗时:1.7秒
4 w  b; u" m7 L5 i0 C0 mdef computeSum(size: int) -> int:
  n4 l  ^. c7 _* V" {' @    return sum(range(size))  # 隐式 for 循环代替显式 for 循环
7 x" o3 b# C' x0 T  N7 V4 o  j0 d& o9 m" X8 [6 P: M
def main():
5 W7 r  z; R- o7 W( J8 H    size = 10000
, V+ d! ~" M9 r6 X9 h+ l+ U5 T    for _ in range(size):8 e1 @9 L! a5 t
        sum = computeSum(size), L+ w3 Y0 I, N1 J+ h
/ l1 O! {3 k" X) q7 q" @
main()$ n. {' t: ~! n" Z* X
6.3 减少内层for循环的计算
, i7 v6 b/ b8 M  P# 不推荐写法。代码耗时:12.8秒
# q: Y1 {+ d2 B0 Timport math2 L9 V: K! H( z

- U# ~* }5 j8 k+ g6 I9 ]def main():: W( C1 x: ~: @% r5 C$ t1 p! b
    size = 10000
1 Z: T+ E# ^: t* X4 I    sqrt = math.sqrt' f  `5 L9 l7 R
    for x in range(size):
5 {8 f$ S( y9 c3 @        for y in range(size):, S; P: O+ K# @
            z = sqrt(x) + sqrt(y)
0 L. j) a7 L; m: j4 F
1 L8 N# o( e- _7 umain()
, H5 Q2 S( e" B7 N! M
上面的代码中sqrt(x)位于内侧for循环, 每次训练过程中都会重新计算一次,增加了时间开销。
# 推荐写法。代码耗时:7.0秒
: e1 }4 W  D' i2 Y" T/ E# |7 _import math7 M/ V( P( T9 I8 P, `+ i! k' n2 J

% Q( V' j1 f) Jdef main():6 j" B" L+ c% r0 ^9 t% O$ y, v/ r
    size = 10000
" j/ n0 n0 ]2 n* b7 V; T    sqrt = math.sqrt
1 z5 o& t+ H9 W0 f3 }    for x in range(size):
* r! D5 ^! d; F5 I        sqrt_x = sqrt(x)  # 减少内层 for 循环的计算
; w/ w0 o4 y( P5 ?        for y in range(size):" f; n( ]7 A  @9 h& F, a4 I  D4 X
            z = sqrt_x + sqrt(y)' f  k0 K6 S7 u
4 C! N# T( I' D- a# q
main()

7 ~9 c! p% x& U( D$ J$ s
- j4 s4 h: k0 C/ Y7 k
7. 使用numba.jit
我们沿用上面介绍过的例子,在此基础上使用numba.jit。numba可以将 Python 函数 JIT 编译为机器码执行,大大提高代码运行速度。关于numba的更多信息见下面的主页:
http://numba.pydata.org/numba.pydata.org
# 推荐写法。代码耗时:0.62秒) w' P: v! W% P4 c' n5 {4 I
import numba
1 K0 Y5 J" T1 q( Z  E2 F5 P; K, U3 Q7 Y9 i$ t5 e
@numba.jit8 O7 F* M: Y+ _0 K' U
def computeSum(size: float) -> int:
5 o* C' P4 F; I. T    sum = 0  ~& j8 M* G7 O
    for i in range(size):
4 j. g' k% g- d. n  K        sum += i
, @8 Z! r* g5 W0 a- e, k9 `    return sum  i0 U- ~, N  Z/ l! W0 K

1 ^* P$ _  `1 r& ^  `2 A, hdef main():% ^! r7 o$ j+ B7 A& `
    size = 10000
+ @% W6 Z/ R4 X9 J) E    for _ in range(size):- a. T, s% ?0 f1 b1 k! a9 @
        sum = computeSum(size)
; {: Y7 u+ ?( n8 {+ g- I, S; }
* @" |, w; Y' a& c( ^main()

. a% D8 l1 u% A) s! q% i3 W
9 Y3 N9 v8 ]0 u( Q) P
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

* h: D: U0 }2 ~
/ l7 y# \% \" V
赞 收藏 评论0 发布时间:2020-9-4 15:09

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版