我們利用Percepio的Tracealyzer來(lái)深入了解同一算法的兩種不同實(shí)現(xiàn)的性能。具體來(lái)說(shuō),我們確定使用迭代函數(shù)而不是遞歸函數(shù)實(shí)現(xiàn)生成斐波那契數(shù)列的算法性能更高。我們不需要在序列中生成大量條目并測(cè)量時(shí)間來(lái)識(shí)別性能差異;相反,我們能夠使用 Tracealyzer 讓我們深入了解實(shí)現(xiàn)的性能,同時(shí)在序列中僅生成 10 個(gè)條目。
由于斐波那契序列生成器是在 Python 中實(shí)現(xiàn)的,讓我們進(jìn)一步擴(kuò)展一下。關(guān)于StackOverflow的一些主要問(wèn)題與特定算法的最有效和“Pythonic”實(shí)現(xiàn)有關(guān)。在本文中,我們將研究一個(gè)流行的操作:反轉(zhuǎn)列表。
根據(jù)以下關(guān)于 StackOverflow 的問(wèn)題,在反轉(zhuǎn)列表時(shí),關(guān)于一個(gè)實(shí)現(xiàn)相對(duì)于另一個(gè)實(shí)現(xiàn)的性能存在一些爭(zhēng)論。為了深入了解差異,我們可以在一個(gè)簡(jiǎn)單的應(yīng)用程序中實(shí)現(xiàn)這兩種技術(shù),如下面的列表所示;我們還包括必要的源代碼,以捕獲 Tracealyzer 評(píng)估每個(gè)實(shí)現(xiàn)的性能所需的標(biāo)記:
import logging
def example():
list_basis = list(range(10))
logging.basicConfig()
logger = logging.getLogger(‘my-logger’)
logger.info(‘Start’)
reverse_list = reversed(list_basis)
logger.info(‘Stop’)
logger.info(‘Start’)
reverse_list = list_basis[::-1]
logger.info(‘Stop’)
if __name__ == ‘__main__’:
example()
我們創(chuàng)建了一個(gè)包含 10 個(gè)元素的列表,并使用內(nèi)置的“反轉(zhuǎn)”函數(shù)以及 Python 中稱為“切片”的技術(shù)來(lái)反轉(zhuǎn)列表。切片只是獲取列表中的某些元素并將它們返回到新列表;在我們的例子中,我們正在切片整個(gè)列表,但向后遍歷。
與上一篇博客文章一樣,我們可以創(chuàng)建一個(gè) LTTng 會(huì)話,運(yùn)行我們的 Python 腳本,在 Tracealyzer 中打開(kāi)生成的跟蹤,并評(píng)估結(jié)果:
我們可以看到,使用反向函數(shù)的實(shí)現(xiàn)大約需要 475 微秒,使用切片的實(shí)現(xiàn)大約需要 360 微秒。
讓我們看看當(dāng)我們將初始列表中的元素?cái)?shù)量增加 10 倍時(shí)會(huì)發(fā)生什么,從而得到一個(gè)由 100 個(gè)元素組成的列表:
import lttngust
import logging
def example():
list_basis = list(range(100))
logging.basicConfig()
logger = logging.getLogger(‘my-logger’)
logger.info(‘Start’)
reverse_list = reversed(list_basis)
logger.info(‘Stop’)
logger.info(‘Start’)
reverse_list = list_basis[::-1]
logger.info(‘Stop’)
if __name__ == ‘__main__’:
example()
同樣,我們重新運(yùn)行必要的步驟來(lái)創(chuàng)建 LTTng 會(huì)話,運(yùn)行我們的應(yīng)用程序,并在 Tracealyzer 中打開(kāi)生成的跟蹤,它向我們顯示了下圖:
雖然我們看到與以前相同的模式,其中切片技術(shù)比反向函數(shù)表現(xiàn)更好,但我們可以看到,與只有 10 個(gè)元素的結(jié)果相比,這兩種機(jī)制的絕對(duì)時(shí)間幾乎減少了一半。這很有趣,因?yàn)槲覀冾A(yù)計(jì),與 100 個(gè)元素的情況相比,在 10 個(gè)元素的情況下,這兩種技術(shù)所花費(fèi)的時(shí)間應(yīng)該更少。讓我們通過(guò)首先更新 Python 腳本來(lái)收集更多數(shù)據(jù),以允許我們以自動(dòng)方式收集更多數(shù)據(jù):
import lttngust
import logging
import sys
def example(list_size):
list_basis = list(range(list_size))
logging.basicConfig()
logger = logging.getLogger(“my-logger”)
logger.info(“Start”)
reverse_list = reversed(list_basis)
logger.info(“Stop”)
logger.info(“Start”)
reverse_list = list_basis[::-1]
logger.info(“Stop”)
if __name__ == ‘__main__’:
example(int(sys.argv[-1]))
在上面的例子中,我們只是允許自己在列表中傳入我們想要的元素?cái)?shù)量。然后我們可以創(chuàng)建以下 bash 腳本,這將允許我們執(zhí)行 Python 應(yīng)用程序,連續(xù)給它一個(gè) 10 和 100 的參數(shù),收集使用反向函數(shù)和切片方法反轉(zhuǎn)列表所需的時(shí)間,并存儲(chǔ)該數(shù)據(jù);腳本將循環(huán)瀏覽每個(gè)測(cè)試 10 次。
#!/bin/bash
set -e
list_compare() {
echo “Running test with a list size of $1 elements”
lttng create
lttng enable-event --kernel sched_switch
lttng enable-event --python my-logger
lttng start
python3 list_compare.py $1
lttng stop
lttng destroy
}
for i in {1..10}
do
list_compare 10
cp -r ~/lttng-traces/auto* “/home/pi/percepio/elements_10_trace_$i”
chown -R pi: “/home/pi/percepio/elements_10_trace_$i”
mv ~/lttng-traces/auto* ~/lttng-traces/archive/
list_compare 100
cp -r ~/lttng-traces/auto* “/home/pi/percepio/elements_100_trace_$i”
chown -R pi: “/home/pi/percepio/elements_100_trace_$i”
mv ~/lttng-traces/auto* ~/lttng-traces/archive/
done
當(dāng)我們?cè)?Tracealyzer 中打開(kāi)生成的跡線并繪制圖表時(shí),我們會(huì)看到以下趨勢(shì):
黃色和橙色圖表示 10 個(gè)元素列表的反轉(zhuǎn)時(shí)間,綠色和深紅色圖表示 100 個(gè)元素列表的反轉(zhuǎn)時(shí)間相同。我們可以看到一些實(shí)例,其中 10 個(gè)元素列表反轉(zhuǎn)的執(zhí)行時(shí)間與 100 個(gè)元素結(jié)果的執(zhí)行時(shí)間一致。類似地,我們可以看到,有一個(gè)實(shí)例減少了反轉(zhuǎn) 100 個(gè)元素列表的執(zhí)行時(shí)間,并且與反轉(zhuǎn) 10 個(gè)元素列表的執(zhí)行時(shí)間相同。但是,總的來(lái)說(shuō),我們可以看到反轉(zhuǎn) 10 個(gè)元素列表的執(zhí)行時(shí)間大約是反轉(zhuǎn) 100 個(gè)元素列表所需時(shí)間的一半。
也許我們?cè)谇耙粋€(gè)案例中觀察到了這些異常之一?讓我們分析包含 1000 個(gè)元素的結(jié)果:
同樣,我們看到相同的模式,與使用反向功能相比,使用切片技術(shù)花費(fèi)的時(shí)間更少。但是,即使我們?cè)俅螌⒘斜碇械脑財(cái)?shù)量增加了 10 倍(達(dá)到驚人的 1000 個(gè)元素),每個(gè)實(shí)現(xiàn)的時(shí)間與 100 個(gè)元素的時(shí)間大致相同,與只有 10 個(gè)元素相比,時(shí)間仍然是一半的時(shí)間。
讓我們將列表大小增加到 100k 個(gè)元素并觀察結(jié)果:
以前我們觀察到切片技術(shù)花費(fèi)的時(shí)間大約是反向函數(shù)的一半,而在這里我們可以看到相反的情況。事實(shí)上,對(duì)于 100k 個(gè)元素的列表大小,切片技術(shù)花費(fèi)的時(shí)間大約延長(zhǎng)了 15 倍!
總之,我們從這個(gè)簡(jiǎn)單的練習(xí)中獲得了一些寶貴的見(jiàn)解。首先,與反向函數(shù)相比,切片技術(shù)似乎花費(fèi)的時(shí)間更少,對(duì)于“幾個(gè)”元素的列表大小(最多 1000)。但是,當(dāng)我們將列表大小增加到 100k 個(gè)元素時(shí),我們可以看到切片技術(shù)比反向函數(shù)花費(fèi)大約 15 倍的時(shí)間。其次,當(dāng)我們運(yùn)行較小大小列表的多次迭代時(shí),我們確實(shí)注意到反轉(zhuǎn)包含 100 個(gè)元素的列表的時(shí)間與反轉(zhuǎn)包含 10 個(gè)元素的列表的時(shí)間大致相同的實(shí)例;然而,總體趨勢(shì)是,反轉(zhuǎn)包含 10 個(gè)元素的列表比反轉(zhuǎn)包含 100 個(gè)元素的列表花費(fèi)的時(shí)間更少,這是意料之中的!我們可以更詳細(xì)地分析Tracealyzer捕獲的痕跡,以了解觀察到的異常的原因。
這里的關(guān)鍵要點(diǎn)是,Percepio 的 Tracealyzer 使我們能夠在沒(méi)有太多儀器基礎(chǔ)設(shè)施的情況下執(zhí)行這種詳細(xì)的分析;我們能夠辨別出大約 100 微秒的性能數(shù)字!
由于Python一直是機(jī)器學(xué)習(xí)的流行語(yǔ)言,因此我們必須擁有一個(gè)工具,可以快速將一種算法的性能與另一種算法的性能進(jìn)行比較。我們可以開(kāi)發(fā)一些小函數(shù)來(lái)隔離我們要評(píng)估的算法,并使用 Tracealyzer 為我們提供有關(guān)算法在不同維度的性能的必要見(jiàn)解。Tracealyzer還可以快速發(fā)現(xiàn)可能需要進(jìn)一步分析的異常行為。
審核編輯:郭婷
-
函數(shù)
+關(guān)注
關(guān)注
3文章
4306瀏覽量
62431 -
python
+關(guān)注
關(guān)注
56文章
4782瀏覽量
84453
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論