程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

【python學習】--執行時間計算方法以及優化

編輯:Python

系列文章目錄

文章目錄

  • 系列文章目錄
  • 前言
  • 一、時間不一致的猜想
  • 二、原因探索
    • 1.方法一
    • 2.方法二
    • 2.方法三:
  • 三、python 運行效率慢的原因
    • 1.簡介
    • 2.運行效率慢的原因
  • 四、python 優化
  • 五、python 優化技巧
    • 1.優化原則
    • 2.避免全局變量
    • 3.避免模塊和函數屬性訪問
    • 4.避免類內屬性訪問
    • 5.避免不必要的抽象
    • 6.避免數據復制
  • 總結


前言

一、時間不一致的猜想

python腳本運行時間遠遠大於python腳本中統計的計算時間
猜想:

  1. python中用到的是py2neo的寫數據異步,阻塞進程運行;
  2. python腳本使用統計時間的方式是time.clock(),而這種方式統計的是CPU的執行時間,不是程序的執行時間。

程序執行時間 = CPU運行時間 + IO時間 + 休眠或等待時間

二、原因探索

1.方法一

import asyncio
import datetime
starttime = datetime.datetime.now()
# long running
# do something other
async def sayhi():
print("你好,若竹")
await asyncio.sleep(10)
print("用余生去守護")
asyncio.run(sayhi())
endtime = datetime.datetime.now()
print(("程序運行時間為:")+ str((endtime-starttime).seconds)+"秒")

輸出:

你好,若竹
用余生去守護
程序運行時間為:10秒

datetime.datetime.now()獲取的是當前日期,在程序執行結束之後,這個方式獲得的時間值為程序執行的時間。

2.方法二

import asyncio
import datetime
import time
starttime = time.time()
# long running
# do something other
async def sayhi():
print("你好,若竹")
await asyncio.sleep(10)
print("用余生去守護")
asyncio.run(sayhi())
endtime = time.time()
print("程序運行時間為:"+ str(float(endtime-starttime))+"秒")

輸出:

你好,若竹
用余生去守護
程序運行時間為:10.002257108688354秒

time.time()獲取自紀元以來的當前時間(以秒為單位)。如果系統時鐘提供它們,則可能存在秒的分數,所以這個地方返回的是一個浮點型類型。這裡獲取的也是程序的執行時間。

2.方法三:

import asyncio
import datetime
import time
starttime = time.clock()
# long running
# do something other
async def sayhi():
print("你好,若竹")
await asyncio.sleep(10)
print("用余生去守護")
asyncio.run(sayhi())
endtime = time.clock()
print("程序運行時間為:"+ str(float(endtime-starttime))+"秒")

輸出:

.\py_study.py:807: DeprecationWarning: time.clock has been deprecated in Python 3.3 and will be removed from Python 3.8: use time.perf_counter or time.process_time instead
starttime = time.clock()
你好,若竹
用余生去守護
.\py_study.py:818: DeprecationWarning: time.clock has been deprecated in Python 3.3 and will be removed from Python 3.8: use time.perf_counter or time.process_time instead
endtime = time.clock()
程序運行時間為:10.0219916秒

Deprecation Warning: time. clock has been deprecated in Python 3.3 and will be removed from Python 3.8: use time. perf_counter or time. process_time instead
棄用警告:時間。clock在Python 3.3中已棄用,並將從Python 3.8中移除:使用time。perf_counter或時間。process_time代替。

代碼如下:

import asyncio
import datetime
import time
starttime = time.perf_counter()
# long running
# do something other
async def sayhi():
print("你好,若竹")
await asyncio.sleep(10)
print("用余生去守護")
asyncio.run(sayhi())
endtime = time.perf_counter()
print("程序運行時間為:"+ str(float(endtime-starttime))+"秒")

輸出:

你好,若竹
用余生去守護
程序運行時間為:10.060287599999999秒

三、python 運行效率慢的原因

1.簡介

編程語言的效率一方面指開發效率,即程序員完成編碼所需的時間,另一方面是運行效率,即計算任務所需的時間。編碼效率和運行效率往往很難兼顧。

2.運行效率慢的原因

  1. python 是動態語言,造成運行時的不確定性影響運行效率;
    動態語言是一類在運行時可以改變其結構的語言,如新的函數、對象、代碼可以被引入,已有的函數可以被刪除或其他結構上的變化等,該類語言更具有活性,但是不可避免的因為運行時的不確定性也影響運行效率。數據的比較和轉換類型的開銷很大,每次讀取、寫入或引用一個變量,都要檢查類型。很難優化一種極具動態性的語言。Python的許多替代語言之所以快得多,原因在於它們為了性能在靈活性方面作出了犧牲。

  2. python 是解釋執行,不支持JIT(just in time compiler);
    相比於C語言編譯性語言編寫的程序,Python是解釋執行語言,其運行過程是Python運行文件程序時,Python解釋器將源代碼轉換為字節碼,然後再由Python解釋器來執行這些字節碼。其每次運行都要進行轉換成字節碼,然後再由虛擬機把字節碼轉換成機器語言,最後才能在硬件上運行,與編譯性語言相比,其過程更復雜,性能肯定會受影響。

  3. python 中一切皆對象,每個對象都需要維護引用計數,增加額外工作;
    Python是一門面向對象的編程語言,其設計理念是一切皆是對象,如數字、字符串、元組、列表、字典、函數、方法、類、模塊等都是對象,包括代碼,每個對象都需要維護引用計數,因此,增加了額外工作,影響了性能。

  4. python GIL,全局解釋器鎖導致無法實現真正的並發;
    GIL是Python最為诟病的一點,因為GIL,Python中的多線程並不能真正的並發,即使在單線程,GIL也會帶來很大的性能影響,因為python每執行100個opcode就會嘗試線程的切換,因此,影響Python運行效率。

  5. 垃圾回收機制,會中斷正在執行的程序,造成所謂的卡頓;
    Python采用標記和分代的垃圾回收策略,每次垃圾回收的時候都會中斷正在執行的程序,造成所謂的頓卡,影響運行效率。

四、python 優化

  1. 優化算法:時間復雜度
    算法的時間復雜度對程序的執行效率影響最大,在python 中可以通過選擇合適的數據結構來優化時間復雜度,如list和set查找某一個元素的時間復雜度分別是O(n)和O(1).不同的場景有不同的優化方式,總的來說,一般有分治,分支界限,貪心,動態規劃等。

  2. 減少冗余數據
    如用上三角或下三角的方式去保存一個大的對稱矩陣。在0元素占大多數的矩陣裡使用稀疏矩陣表示。

  3. 合理使用copy與deepcopy
    對於dict和list等數據結構的對象,直接賦值使用的是引用的方式。而有些情況下需要復制整個對象,這時可以使用copy包裡的copy和deepcopy,這兩個函數的不同之處在於後者是遞歸復制的。效率也不一樣:(以下程序在ipython中運行)

  4. 使用dict或set查找元素

  5. 合理使用生成器(generator)和yield

  6. 優化循環

  7. 優化包含多個判斷表達式的順序

  8. 使用join合並迭代器中的字符串

  9. 選擇合適的格式化字符方式

  10. 不借助中間變量交換兩個變量的值

  11. 使用if is

  12. 使用級聯比較x < y < z

  13. while 1 比 while True 更快

  14. 使用**而不是pow

  15. 使用 cProfile, cStringIO 和 cPickle等用c實現相同功能(分別對應profile, StringIO, pickle)的包

  16. 使用最佳的反序列化方式

  17. 使用C擴展(Extension)

  18. 並行編程

  19. 終級大殺器:PyPy

  20. 使用性能分析工具

五、python 優化技巧

python是一種腳本語言,相比C/C++這樣的編譯語言,在效率和性能方面存在一些不足。

1.優化原則

  1. 不要過早優化,優化的前提是代碼可以正常工作,過早優化可能會忽視對總體性能指標的把握,在得到全局結果前不要主次顛倒。
  2. 權衡優化的代價,想要解決所有性能問題是幾乎不可能的,通常面臨的選擇是時間換空間或空間換時間,另外,還要考慮開發的代價。
  3. 優化關鍵位置。

2.避免全局變量

不推薦寫法:

import asyncio
import datetime
import time
import math
starttime = time.time()
# long running
# do something other
size = 10000
for x in range(size):
for y in range(size):
z = math.sqrt(x) + math.sqrt(y)
endtime = time.time()
print("程序運行時間為:"+ str(float(endtime-starttime))+"秒")

輸出:

程序運行時間為:32.05525302886963秒

推薦寫法:

import asyncio
import datetime
import time
import math
starttime = time.time()
# long running
# do something other
def main(): # 定義到函數中,以減少全局變量的使用
size = 10000
for x in range(size):
for y in range(size):
z = math.sqrt(x) + math.sqrt(y)
main()
endtime = time.time()
print("程序運行時間為:"+ str(float(endtime-starttime))+"秒")

輸出:

程序運行時間為:26.54142999649048秒

全局變量和局部變量實現方式不同,定義在全局范圍內的代碼運行速度會比定義在函數中慢不少,通常將腳本語句放入到函數中,可以帶來15%-30%的速度提升。

3.避免模塊和函數屬性訪問

不推薦寫法:

import asyncio
import datetime
import time
import math
starttime = time.time()
# long running
# do something other
def computeSqrt(size: int):
result = []
for i in range(size):
result.append(math.sqrt(i))
return result
def main():
size = 10000
for _ in range(size):
result = computeSqrt(size)
main()
endtime = time.time()
print("程序運行時間為:"+ str(float(endtime-starttime))+"秒")

輸出:

程序運行時間為:17.07011365890503秒

每次使用.(屬性訪問操作符時)會觸發特定的方法,如__getattribute__()和__getattr__(),這些方法會進行字典操作,因此會帶來額外的時間開銷。通過from import語句,可以消除屬性訪問。

第一次優化:

import asyncio
import datetime
import time
import math
from math import sqrt
starttime = time.time()
# long running
# do something other
def computeSqrt(size: int):
result = []
for i in range(size):
#result.append(math.sqrt(i)) # 避免math.sqrt的使用
result.append(sqrt(i))
return result
def main():
size = 10000
for _ in range(size):
result = computeSqrt(size)
main()
endtime = time.time()
print("程序運行時間為:"+ str(float(endtime-starttime))+"秒")

輸出:

程序運行時間為:14.559865236282349秒

上面我們講到,局部變量的查找比全局變量更快,因此對於頻繁訪問的變量sqrt,通過將其改為局部變量可以加速運行。

第二次優化:

import asyncio
import datetime
import time
import math
from math import sqrt
starttime = time.time()
# long running
# do something other
def computeSqrt(size: int):
result = []
sqrt = math.sqrt # 賦值給局部變量
for i in range(size):
result.append(sqrt(i)) # 避免math.sqrt的使用
return result
def main():
size = 10000
for _ in range(size):
result = computeSqrt(size)
main()
endtime = time.time()
print("程序運行時間為:"+ str(float(endtime-starttime))+"秒")

輸出:

程序運行時間為:14.179925203323364秒

除了math.sqrt外,computeSqrt函數中還有.的存在,那就是調用list的append方法。通過將該方法賦值給一個局部變量,可以徹底消除computeSqrt函數中for循環內部的.使用。

推薦寫法:

import asyncio
import datetime
import time
import math
from math import sqrt
starttime = time.time()
# long running
# do something other
def computeSqrt(size: int):
result = []
append = result.append
sqrt = math.sqrt # 賦值給局部變量
for i in range(size):
append(sqrt(i)) # 避免result.append和math.sqrt的使用
return result
def main():
size = 10000
for _ in range(size):
result = computeSqrt(size)
main()
endtime = time.time()
print("程序運行時間為:"+ str(float(endtime-starttime))+"秒")

輸出:

程序運行時間為:13.289391279220581秒

4.避免類內屬性訪問

不推薦寫法:

import asyncio
import datetime
import time
import math
from typing import List
starttime = time.time()
# long running
# do something other
class DemoClass:
def __init__(self, value: int):
self._value = value
def computeSqrt(self, size: int) -> List[float]:
result = []
append = result.append
sqrt = math.sqrt
for _ in range(size):
append(sqrt(self._value))
return result
def main():
size = 10000
for _ in range(size):
demo_instance = DemoClass(size)
result = demo_instance.computeSqrt(size)
main()
endtime = time.time()
print("程序運行時間為:"+ str(float(endtime-starttime))+"秒")

輸出:

程序運行時間為:14.498187780380249秒

避免.的原則也適用於類內屬性,訪問self._value的速度會比訪問一個局部變量更慢一些。通過將需要頻繁訪問的類內屬性賦值給一個局部變量,可以提升代碼運行速度。

推薦寫法:

import asyncio
import datetime
import time
import math
from typing import List
starttime = time.time()
# long running
# do something other
class DemoClass:
def __init__(self, value: int):
self._value = value
def computeSqrt(self, size: int) -> List[float]:
result = []
append = result.append
sqrt = math.sqrt
value = self._value
for _ in range(size):
append(sqrt(value)) # 避免self._value的使用
return result
def main():
size = 10000
for _ in range(size):
demo_instance = DemoClass(size)
result = demo_instance.computeSqrt(size)
main()
endtime = time.time()
print("程序運行時間為:"+ str(float(endtime-starttime))+"秒")

輸出:

程序運行時間為:12.744902849197388秒

5.避免不必要的抽象

不推薦寫法:

import asyncio
import datetime
import time
import math
from typing import List
starttime = time.time()
# long running
# do something other
class DemoClass:
def __init__(self, value: int):
self._value = value
@property
def value(self) -> int:
return self._value
@value.setter
def value(self, x: int):
self._value = x
def main():
size = 1000000
for i in range(size):
demo_instance = DemoClass(size)
value = demo_instance.value
demo_instance.value = i
main()
endtime = time.time()
print("程序運行時間為:"+ str(float(endtime-starttime))+"秒")

輸出:

程序運行時間為:0.3770256042480469秒

任何時候當你使用額外的處理層(比如裝飾器、屬性訪問、描述器)去包裝代碼時,都會讓代碼變慢。大部分情況下,需要重新進行審視使用屬性訪問器的定義是否有必要,使用getter/setter函數對屬性進行訪問通常是 C/C++ 程序員遺留下來的代碼風格。如果真的沒有必要,就使用簡單屬性。

推薦寫法:

import asyncio
import datetime
import time
import math
from typing import List
starttime = time.time()
# long running
# do something other
class DemoClass:
def __init__(self, value: int):
self.value = value # 避免不必要的屬性訪問器 
def main():
size = 1000000
for i in range(size):
demo_instance = DemoClass(size)
value = demo_instance.value
demo_instance.value = i
main()
endtime = time.time()
print("程序運行時間為:"+ str(float(endtime-starttime))+"秒")

輸出:

程序運行時間為:0.26129961013793945秒

6.避免數據復制

  1. 避免無意義的數據復制

不推薦的寫法:

import asyncio
import datetime
import time
starttime = time.time()
# long running
# do something other
def main():
size = 10000
for _ in range(size):
value = range(size)
value_list = [x for x in value]
square_list = [x * x for x in value_list]
main()
endtime = time.time()
print("程序運行時間為:"+ str(float(endtime-starttime))+"秒")

輸出:

程序運行時間為:7.3553197383880615秒

上面的代碼中value_list完全沒有必要,這會創建不必要的數據結構或復制。

推薦寫法:

import asyncio
import datetime
import time
starttime = time.time()
# long running
# do something other
def main():
size = 10000
for _ in range(size):
value = range(size)
square_list = [x * x for x in value] # 避免無意義的復制
main()
endtime = time.time()
print("程序運行時間為:"+ str(float(endtime-starttime))+"秒")

輸出:

程序運行時間為:5.667866230010986秒

另外一種情況是對 Python 的數據共享機制過於偏執,並沒有很好地理解或信任 Python 的內存模型,濫用 **copy.deepcopy()之類的函數。通常在這些代碼中是可以去掉復制操作的。

  1. 交換值時不使用中間變量

總結

分享:
人的最高境界就是追求獨立的精神價值,形成高度獨立的人格。獨立意識較強的人,有較強的理性和情緒調控能力,他的心靈深處擁有崇高、永恆的精神家園,生命從此不再空虛。


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved