0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

加速Python for循環(huán)的12種方法

新機(jī)器視覺(jué) ? 來(lái)源:Deephub Imba ? 2024-01-04 17:33 ? 次閱讀

Python內(nèi)建的一個(gè)常用功能是timeit模塊。下面幾節(jié)中我們將使用它來(lái)度量循環(huán)的當(dāng)前性能和改進(jìn)后的性能。

對(duì)于每種方法,我們通過(guò)運(yùn)行測(cè)試來(lái)建立基線,該測(cè)試包括在10次測(cè)試運(yùn)行中運(yùn)行被測(cè)函數(shù)100K次(循環(huán)),然后計(jì)算每個(gè)循環(huán)的平均時(shí)間(以納秒為單位,ns)。

d61628c6-aae3-11ee-8b88-92fbcf53809c.png

幾個(gè)簡(jiǎn)單方法

1、列表推導(dǎo)式

# Baseline version (Inefficient way)
# Calculating the power of numbers
# Without using List Comprehension
deftest_01_v0(numbers):
output=[]
forninnumbers:
  output.append(n**2.5)
returnoutput

# Improved version
# (Using List Comprehension)
deftest_01_v1(numbers):
output=[n**2.5forninnumbers]
returnoutput

結(jié)果如下:

# Summary Of Test Results
  Baseline: 32.158 ns per loop
  Improved: 16.040 ns per loop
% Improvement: 50.1 %
  Speedup: 2.00x

可以看到使用列表推導(dǎo)式可以得到2倍速的提高

2、在外部計(jì)算長(zhǎng)度

如果需要依靠列表的長(zhǎng)度進(jìn)行迭代,請(qǐng)?jiān)趂or循環(huán)之外進(jìn)行計(jì)算。

# Baseline version (Inefficient way)
# (Length calculation inside for loop)
deftest_02_v0(numbers):
output_list=[]
foriinrange(len(numbers)):
 output_list.append(i*2)
returnoutput_list

# Improved version
# (Length calculation outside for loop)
deftest_02_v1(numbers):
my_list_length=len(numbers)
output_list=[]
foriinrange(my_list_length):
 output_list.append(i*2)
returnoutput_list

通過(guò)將列表長(zhǎng)度計(jì)算移出for循環(huán),加速1.6倍,這個(gè)方法可能很少有人知道吧。

# Summary Of Test Results
  Baseline: 112.135 ns per loop
  Improved: 68.304 ns per loop
% Improvement: 39.1 %
  Speedup: 1.64x

3、使用Set

在使用for循環(huán)進(jìn)行比較的情況下使用set。

# Use for loops for nested lookups
deftest_03_v0(list_1,list_2):
# Baseline version (Inefficient way)
# (nested lookups using for loop)
common_items=[]
foriteminlist_1:
  ifiteminlist_2:
    common_items.append(item)
returncommon_items

deftest_03_v1(list_1,list_2):
# Improved version
# (sets to replace nested lookups)
s_1=set(list_1)
s_2=set(list_2)
output_list=[]
common_items=s_1.intersection(s_2)
returncommon_items

在使用嵌套for循環(huán)進(jìn)行比較的情況下,使用set加速498x

# Summary Of Test Results
  Baseline: 9047.078 ns per loop
  Improved:  18.161 ns per loop
% Improvement: 99.8 %
  Speedup: 498.17x

4、跳過(guò)不相關(guān)的迭代

避免冗余計(jì)算,即跳過(guò)不相關(guān)的迭代。

# Example of inefficient code used to find
# the first even square in a list of numbers
deffunction_do_something(numbers):
forninnumbers:
 square=n*n
 ifsquare%2==0:
   returnsquare

returnNone# No even square found

# Example of improved code that
# finds result without redundant computations
deffunction_do_something_v1(numbers):
even_numbers=[iforninnumbersifn%2==0]
fornineven_numbers:
 square=n*n
 returnsquare

returnNone# No even square found

這個(gè)方法要在設(shè)計(jì)for循環(huán)內(nèi)容的時(shí)候進(jìn)行代碼設(shè)計(jì),具體能提升多少可能根據(jù)實(shí)際情況不同:

# Summary Of Test Results
  Baseline: 16.912 ns per loop
  Improved: 8.697 ns per loop
% Improvement: 48.6 %
  Speedup: 1.94x

5、代碼合并

在某些情況下,直接將簡(jiǎn)單函數(shù)的代碼合并到循環(huán)中可以提高代碼的緊湊性和執(zhí)行速度。

# Example of inefficient code
# Loop that calls the is_prime function n times.
defis_prime(n):
ifn<=?1:
??? ?return?False
???for?i?in?range(2,?int(n**0.5)?+?1):
??? ?if?n?%?i?==?0:
??? ? ?return?False
?
???return?True
?
?def?test_05_v0(n):
???# Baseline version (Inefficient way)
???# (calls the is_prime function n times)
???count?=?0
???for?i?in?range(2,?n?+?1):
??? ?if?is_prime(i):
??? ? ?count?+=?1
???return?count
?
?def?test_05_v1(n):
???# Improved version
???# (inlines the logic of the is_prime function)
???count?=?0
???for?i?in?range(2,?n?+?1):
??? ?if?i?<=?1:
??? ? ?continue
??? ?for?j?in?range(2,?int(i**0.5)?+?1):
??? ? ?if?i?%?j?==?0:
??? ? ? ?break
??? ?else:
??? ? ?count?+=?1
???return?count

這樣也可以提高1.3倍

# Summary Of Test Results
  Baseline: 1271.188 ns per loop
  Improved: 939.603 ns per loop
% Improvement: 26.1 %
  Speedup: 1.35x

這是為什么呢?

調(diào)用函數(shù)涉及開(kāi)銷(xiāo),例如在堆棧上推入和彈出變量、函數(shù)查找和參數(shù)傳遞。當(dāng)一個(gè)簡(jiǎn)單的函數(shù)在循環(huán)中被重復(fù)調(diào)用時(shí),函數(shù)調(diào)用的開(kāi)銷(xiāo)會(huì)增加并影響性能。所以將函數(shù)的代碼直接內(nèi)聯(lián)到循環(huán)中可以消除這種開(kāi)銷(xiāo),從而可能顯著提高速度。

但是這里需要注意,平衡代碼可讀性和函數(shù)調(diào)用的頻率是一個(gè)要考慮的問(wèn)題。

一些小技巧

6 .避免重復(fù)

考慮避免重復(fù)計(jì)算,其中一些計(jì)算可能是多余的,并且會(huì)減慢代碼的速度。相反,在適用的情況下考慮預(yù)計(jì)算。

deftest_07_v0(n):
# Example of inefficient code
# Repetitive calculation within nested loop
result=0
foriinrange(n):
 forjinrange(n):
  result+=i*j
returnresult

deftest_07_v1(n):
# Example of improved code
# Utilize precomputed values to help speedup
pv=[[i*jforjinrange(n)]foriinrange(n)]
result=0
foriinrange(n):
 result+=sum(pv[i][:i+1])
returnresult

結(jié)果如下

# Summary Of Test Results
  Baseline: 139.146 ns per loop
  Improved: 92.325 ns per loop
% Improvement: 33.6 %
  Speedup: 1.51x

7、使用Generators

生成器支持延遲求值,也就是說(shuō),只有當(dāng)你向它請(qǐng)求下一個(gè)值時(shí),里面的表達(dá)式才會(huì)被求值,動(dòng)態(tài)處理數(shù)據(jù)有助于減少內(nèi)存使用并提高性能。尤其是大型數(shù)據(jù)集中

deftest_08_v0(n):
# Baseline version (Inefficient way)
# (Inefficiently calculates the nth Fibonacci
# number using a list)
ifn<=?1:
??? ?return?n
???f_list?=?[0,?1]
???for?i?in?range(2,?n?+?1):
??? ?f_list.append(f_list[i?-?1]?+?f_list[i?-?2])
???return?f_list[n]
?
?def?test_08_v1(n):
???# Improved version
???# (Efficiently calculates the nth Fibonacci
???# number using a generator)
???a,?b?=?0,?1
???for?_?in?range(n):
??? ?yield?a
??? ?a,?b?=?b,?a?+?b

可以看到提升很明顯:

# Summary Of Test Results
  Baseline: 0.083 ns per loop
  Improved: 0.004 ns per loop
% Improvement: 95.5 %
  Speedup: 22.06x

8、map()函數(shù)

使用Python內(nèi)置的map()函數(shù)。它允許在不使用顯式for循環(huán)的情況下處理和轉(zhuǎn)換可迭代對(duì)象中的所有項(xiàng)。

defsome_function_X(x):
# This would normally be a function containing application logic
# which required it to be made into a separate function
# (for the purpose of this test, just calculate and return the square)
returnx**2

deftest_09_v0(numbers):
# Baseline version (Inefficient way)
output=[]
foriinnumbers:
 output.append(some_function_X(i))

returnoutput

deftest_09_v1(numbers):
# Improved version
# (Using Python's built-in map() function)
output=map(some_function_X,numbers)
returnoutput

使用Python內(nèi)置的map()函數(shù)代替顯式的for循環(huán)加速了970x。

# Summary Of Test Results
  Baseline: 4.402 ns per loop
  Improved: 0.005 ns per loop
% Improvement: 99.9 %
  Speedup: 970.69x

這是為什么呢?

map()函數(shù)是用C語(yǔ)言編寫(xiě)的,并且經(jīng)過(guò)了高度優(yōu)化,因此它的內(nèi)部隱含循環(huán)比常規(guī)的Python for循環(huán)要高效得多。因此速度加快了,或者可以說(shuō)Python還是太慢,哈。

9、使用Memoization

記憶優(yōu)化算法的思想是緩存(或“記憶”)昂貴的函數(shù)調(diào)用的結(jié)果,并在出現(xiàn)相同的輸入時(shí)返回它們。它可以減少冗余計(jì)算,加快程序速度。

首先是低效的版本。

# Example of inefficient code
deffibonacci(n):
ifn==0:
 return0
elifn==1:
 return1
returnfibonacci(n-1)+fibonacci(n-2)

deftest_10_v0(list_of_numbers):
output=[]
foriinnumbers:
 output.append(fibonacci(i))

returnoutput

然后我們使用Python的內(nèi)置functools的lru_cache函數(shù)。

# Example of efficient code
# Using Python's functools' lru_cache function
importfunctools

@functools.lru_cache()
deffibonacci_v2(n):
ifn==0:
 return0
elifn==1:
 return1
returnfibonacci_v2(n-1)+fibonacci_v2(n-2)

def_test_10_v1(numbers):
output=[]
foriinnumbers:
 output.append(fibonacci_v2(i))

returnoutput

結(jié)果如下:

# Summary Of Test Results
  Baseline: 63.664 ns per loop
  Improved: 1.104 ns per loop
% Improvement: 98.3 %
  Speedup: 57.69x

使用Python的內(nèi)置functools的lru_cache函數(shù)使用Memoization加速57x。

lru_cache函數(shù)是如何實(shí)現(xiàn)的?

“LRU”是“Least Recently Used”的縮寫(xiě)。lru_cache是一個(gè)裝飾器,可以應(yīng)用于函數(shù)以啟用memoization。它將最近函數(shù)調(diào)用的結(jié)果存儲(chǔ)在緩存中,當(dāng)再次出現(xiàn)相同的輸入時(shí),可以提供緩存的結(jié)果,從而節(jié)省了計(jì)算時(shí)間。lru_cache函數(shù),當(dāng)作為裝飾器應(yīng)用時(shí),允許一個(gè)可選的maxsize參數(shù),maxsize參數(shù)決定了緩存的最大大小(即,它為多少個(gè)不同的輸入值存儲(chǔ)結(jié)果)。如果maxsize參數(shù)設(shè)置為None,則禁用LRU特性,緩存可以不受約束地增長(zhǎng),這會(huì)消耗很多的內(nèi)存。這是最簡(jiǎn)單的空間換時(shí)間的優(yōu)化方法。

10、向量化

importnumpyasnp

deftest_11_v0(n):
# Baseline version
# (Inefficient way of summing numbers in a range)
output=0
foriinrange(0,n):
 output=output+i

returnoutput

deftest_11_v1(n):
# Improved version
# (# Efficient way of summing numbers in a range)
output=np.sum(np.arange(n))
returnoutput

向量化一般用于機(jī)器學(xué)習(xí)的數(shù)據(jù)處理庫(kù)numpy和pandas

# Summary Of Test Results
  Baseline: 32.936 ns per loop
  Improved: 1.171 ns per loop
% Improvement: 96.4 %
  Speedup: 28.13x

11、避免創(chuàng)建中間列表

使用filterfalse可以避免創(chuàng)建中間列表。它有助于使用更少的內(nèi)存。

deftest_12_v0(numbers):
# Baseline version (Inefficient way)
filtered_data=[]
foriinnumbers:
 filtered_data.extend(list(
   filter(lambdax:x%5==0,
       range(1,i**2))))

returnfiltered_data

使用Python的內(nèi)置itertools的filterfalse函數(shù)實(shí)現(xiàn)相同功能的改進(jìn)版本。

fromitertoolsimportfilterfalse

deftest_12_v1(numbers):
# Improved version
# (using filterfalse)
filtered_data=[]
foriinnumbers:
 filtered_data.extend(list(
   filterfalse(lambdax:x%5!=0,
         range(1,i**2))))
 
 returnfiltered_data

這個(gè)方法根據(jù)用例的不同,執(zhí)行速度可能沒(méi)有顯著提高,但通過(guò)避免創(chuàng)建中間列表可以降低內(nèi)存使用。我們這里獲得了131倍的提高

# Summary Of Test Results
  Baseline: 333167.790 ns per loop
  Improved: 2541.850 ns per loop
% Improvement: 99.2 %
  Speedup: 131.07x

12、高效連接字符串

任何使用+操作符的字符串連接操作都會(huì)很慢,并且會(huì)消耗更多內(nèi)存。使用join代替。

deftest_13_v0(l_strings):
# Baseline version (Inefficient way)
# (concatenation using the += operator)
output=""
fora_strinl_strings:
 output+=a_str

returnoutput

deftest_13_v1(numbers):
# Improved version
# (using join)
output_list=[]
fora_strinl_strings:
 output_list.append(a_str)

return"".join(output_list)

該測(cè)試需要一種簡(jiǎn)單的方法來(lái)生成一個(gè)較大的字符串列表,所以寫(xiě)了一個(gè)簡(jiǎn)單的輔助函數(shù)來(lái)生成運(yùn)行測(cè)試所需的字符串列表。

fromfakerimportFaker

defgenerate_fake_names(count:int=10000):
# Helper function used to generate a
# large-ish list of names
fake=Faker()
output_list=[]
for_inrange(count):
 output_list.append(fake.name())

returnoutput_list

l_strings=generate_fake_names(count=50000)

結(jié)果如下:

# Summary Of Test Results
  Baseline: 32.423 ns per loop
  Improved: 21.051 ns per loop
% Improvement: 35.1 %
  Speedup: 1.54x

使用連接函數(shù)而不是使用+運(yùn)算符加速1.5倍。為什么連接函數(shù)更快?

使用+操作符的字符串連接操作的時(shí)間復(fù)雜度為O(n2),而使用join函數(shù)的字符串連接操作的時(shí)間復(fù)雜度為O(n)。

總結(jié)

本文介紹了一些簡(jiǎn)單的方法,將Python for循環(huán)的提升了1.3到970x。

使用Python內(nèi)置的map()函數(shù)代替顯式的for循環(huán)加速970x

使用set代替嵌套的for循環(huán)加速498x[技巧#3]

使用itertools的filterfalse函數(shù)加速131x

使用lru_cache函數(shù)使用Memoization加速57x







審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 生成器
    +關(guān)注

    關(guān)注

    7

    文章

    313

    瀏覽量

    20844
  • python
    +關(guān)注

    關(guān)注

    53

    文章

    4753

    瀏覽量

    84078
  • for循環(huán)
    +關(guān)注

    關(guān)注

    0

    文章

    61

    瀏覽量

    2471

原文標(biāo)題:加速Python循環(huán)的12種方法,最高可以提速900倍

文章出處:【微信號(hào):vision263com,微信公眾號(hào):新機(jī)器視覺(jué)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    使用CUDA并行化矩陣乘法加速Blender Python

      這篇文章描述了兩不同的加速矩陣乘法的方法。第一種方法使用 Numba 編譯器來(lái)減少 Python 代碼中與
    的頭像 發(fā)表于 04-24 17:04 ?5351次閱讀
    使用CUDA并行化矩陣乘法<b class='flag-5'>加速</b>Blender <b class='flag-5'>Python</b>

    Python字符串的特點(diǎn)和修改字符串的常見(jiàn)四種方法

    Python中修改字符串的幾種方法
    發(fā)表于 02-26 16:52

    1.3 兩運(yùn)行 Python 程序方法

    界面上執(zhí)行 Python 語(yǔ)句使用命令行執(zhí)行 .py 后綴的腳本文件下面分別對(duì)這兩種方法進(jìn)行演示。1. 第一種方法首先打開(kāi)你的終端,直接輸入 python3 回車(chē),然后輸入 prin
    發(fā)表于 02-16 18:31

    python花式導(dǎo)包的八種方法

    python花式導(dǎo)包的八種方法1. 直接 import人盡皆知的方法,直接導(dǎo)入即可>>> import os>>> os.getcwd()'/home/xxx
    發(fā)表于 03-10 16:51

    python執(zhí)行函數(shù)的九種方法

    = methodcaller("speak", "明哥")p = People()caller(p)以上就是函數(shù)執(zhí)行的九種方法,很多方法,大家也都知道,但是也有幾個(gè)方法,幾乎是見(jiàn)不到的,尤其是后面使用 operator 庫(kù)的那
    發(fā)表于 03-29 17:43

    python判斷是否包含子串的7種方法

    ", "lol"))# False5、通過(guò)魔法方法在第一種方法中,我們使用 in 和 not in 判斷一個(gè)子串是否存在于另一個(gè)字符中,實(shí)際上
    發(fā)表于 04-08 15:15

    Python 轉(zhuǎn)義字符的5表示方法

    ;>> repr(body)"'hello\\nworld'"5. 使用 string_escape如果你還在使用 Python 2 ,其實(shí)還可以使用另一種方法。那就是
    發(fā)表于 04-11 15:18

    調(diào)試Python程序代碼的幾種方法總結(jié)

    本文主要介紹了調(diào)試Python程序代碼的幾種方法總結(jié)。第一種方法簡(jiǎn)單直接粗暴有效,就是用print把可能有問(wèn)題的變量打印出來(lái)看看。凡是用print來(lái)輔助查看的地方,都可以用斷言(assert)來(lái)替代
    發(fā)表于 01-14 11:22 ?4212次閱讀
    調(diào)試<b class='flag-5'>Python</b>程序代碼的幾<b class='flag-5'>種方法</b>總結(jié)

    小猿圈python學(xué)習(xí)之Python列表list合并的4種方法

    Python作為目前市面上最常用的編程語(yǔ)言之一,贏得了我們很多技術(shù)人員的喜愛(ài),同時(shí)越來(lái)越多的人紛紛開(kāi)始學(xué)習(xí)python,今天小猿圈就給大家分享在python3中合并列表的4種方法下面是
    發(fā)表于 05-16 21:37 ?1565次閱讀

    python統(tǒng)計(jì)詞頻的三種方法

    python統(tǒng)計(jì)詞頻的三種方法方法。
    發(fā)表于 05-25 14:33 ?2次下載

    python花式導(dǎo)包的八種方法

    python花式導(dǎo)包的八種方法 1. 直接 import 人盡皆知的方法,直接導(dǎo)入即可 import os os.getcwd()'/home/xxx' 與此類(lèi)似的還有,不再細(xì)講 import
    的頭像 發(fā)表于 03-10 16:48 ?2256次閱讀

    Python的while循環(huán)是什么

    Python中有2循環(huán)。一循環(huán)次數(shù)明確,另一循環(huán)
    的頭像 發(fā)表于 02-23 11:15 ?1068次閱讀

    Python中的for循環(huán)結(jié)構(gòu)

    Python 中,for 循環(huán)是一常用的結(jié)構(gòu),用于遍歷序列(如列表、元組、字符串)中的元素。
    的頭像 發(fā)表于 04-19 15:45 ?2046次閱讀

    python循環(huán)創(chuàng)建變量并賦值

    Python中如何使用循環(huán)創(chuàng)建變量并賦值,以及它的一些應(yīng)用場(chǎng)景。 首先,讓我們來(lái)了解一下Python中的循環(huán)。Python提供了兩
    的頭像 發(fā)表于 11-23 14:51 ?1356次閱讀

    python如何一直循環(huán)一個(gè)代碼

    Python中,有幾種方法可以實(shí)現(xiàn)代碼的循環(huán)執(zhí)行。下面我將詳盡、詳實(shí)、細(xì)致地介紹這些方法和它們的使用情況。 使用while循環(huán): 在
    的頭像 發(fā)表于 11-23 15:54 ?1597次閱讀