3atv精品不卡视频,97人人超碰国产精品最新,中文字幕av一区二区三区人妻少妇,久久久精品波多野结衣,日韩一区二区三区精品

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > c/c++ >内容正文

c/c++

C++ 性能优化篇三《测量性能》

發(fā)布時(shí)間:2023/12/20 c/c++ 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++ 性能优化篇三《测量性能》 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

測(cè)量可測(cè)量之物,將不可測(cè)量之物變?yōu)榭蓽y(cè)量。 ——伽利略 ? 伽利雷(1564—1642)

測(cè)量和實(shí)驗(yàn)是所有改善程序性能嘗試的基礎(chǔ)。本章將介紹兩種測(cè)量性能的工具軟件:分析 器和計(jì)時(shí)器軟件。我將討論如何設(shè)計(jì)性能測(cè)量實(shí)驗(yàn),使得測(cè)量結(jié)果更有指導(dǎo)意義,而不是 誤導(dǎo)我們。

最基本和最頻繁地執(zhí)行的軟件性能測(cè)量會(huì)告訴我們“需要多長(zhǎng)時(shí)間”。執(zhí)行函數(shù)需要多長(zhǎng) 時(shí)間?從磁盤讀取配置文件需要多長(zhǎng)時(shí)間?啟動(dòng)和退出程序需要多長(zhǎng)時(shí)間?

這些測(cè)量問(wèn)題的解答方法有時(shí)簡(jiǎn)單得令人覺(jué)得可笑。牛頓通過(guò)用物體掉落至地面的時(shí)間除 以他的心跳速度測(cè)量出了重力常數(shù) 1 。我相信每位開(kāi)發(fā)人員都有通過(guò)大聲數(shù)數(shù)進(jìn)行計(jì)時(shí)的經(jīng) 歷。在美國(guó),我們通過(guò)喊“one-Mississippi, two-Mississippi, three-Mississippi...” 來(lái)得到比較 精確的秒數(shù)。帶有秒表功能的電子手表曾經(jīng)是計(jì)算機(jī)極客的必備之物,而非僅僅是潮流的 象征。在嵌入式開(kāi)發(fā)中,熟悉硬件的開(kāi)發(fā)人員有很多優(yōu)秀的工具可以使用,其中有頻率計(jì) 數(shù)器和信號(hào)示波器等甚至可以精確地測(cè)量極短例程的時(shí)間的工具。軟件廠商也會(huì)出售專業(yè) 工具,由于數(shù)量太多,這里不會(huì)逐一介紹。

本章將主要介紹兩種被廣泛使用的、具有通用性且價(jià)格低廉的工具。第一個(gè)工具是編譯器 廠商通常在編譯器中都會(huì)提供的分析器(profiler)。分析器會(huì)生成各個(gè)函數(shù)在程序運(yùn)行過(guò) 程中被調(diào)用的累積時(shí)間的表格報(bào)表。對(duì)性能優(yōu)化而言,它是一個(gè)非常關(guān)鍵的工具,因?yàn)樗鼤?huì)列出程序中最熱點(diǎn)的函數(shù)。

第二個(gè)工具是計(jì)時(shí)器軟件(software timer)。開(kāi)發(fā)人員可以自己實(shí)現(xiàn)這個(gè)工具,就像絕地武 士自己打造他們的光劍一樣(請(qǐng)?jiān)徫以谶@里引用了《星球大戰(zhàn)》中的內(nèi)容打比喻)。如 果帶有分析器的豪華版編譯器太過(guò)昂貴,或是編譯器廠商在某些嵌入式平臺(tái)上不提供分析 器,開(kāi)發(fā)人員依然可以通過(guò)測(cè)量長(zhǎng)時(shí)間運(yùn)行的活動(dòng)來(lái)進(jìn)行性能實(shí)驗(yàn)。計(jì)時(shí)器軟件還可以用 于測(cè)量不受計(jì)算限制的任務(wù)。

第三個(gè)工具是非常古老的“實(shí)驗(yàn)筆記本”,許多開(kāi)發(fā)人員認(rèn)為它已經(jīng)完全過(guò)時(shí)了。但是實(shí) 驗(yàn)筆記本或是其他文本文件仍然是不可或缺的優(yōu)化工具。

3.1 優(yōu)化思想

在開(kāi)始介紹測(cè)量和實(shí)驗(yàn)之前,我想談一點(diǎn)點(diǎn)我一直在實(shí)踐的、也是我想在本書(shū)中教授的優(yōu) 化哲學(xué)。

3.1.1 必須測(cè)量性能

人的感覺(jué)對(duì)于檢測(cè)性能提高了多少來(lái)說(shuō)是不夠精確的。人的記憶力不足以準(zhǔn)確地回憶起以 往多次實(shí)驗(yàn)的結(jié)果。書(shū)本中的知識(shí)可能會(huì)誤導(dǎo)你,使你相信了一些并非總是正確的事情。 當(dāng)判斷是否應(yīng)當(dāng)對(duì)某段代碼進(jìn)行優(yōu)化的時(shí)候,開(kāi)發(fā)人員的直覺(jué)往往差得令人吃驚。他們 編寫(xiě)了函數(shù),也知道這個(gè)函數(shù)會(huì)被調(diào)用,但他們并不清楚調(diào)用頻率以及會(huì)被什么代碼所調(diào) 用。于是,一段低效的代碼混入了核心組件中并被調(diào)用了無(wú)數(shù)次。經(jīng)驗(yàn)也可能會(huì)欺騙你。 編程語(yǔ)言、編譯器、庫(kù)和處理器都在不斷地發(fā)展。之前曾經(jīng)肯定是熱點(diǎn)的函數(shù)可能會(huì)變得 非常高效,反之亦然。只有測(cè)量才能告訴你到底是在優(yōu)化游戲中取勝了還是失敗了。 那些具有最讓我折服的優(yōu)化技巧的開(kāi)發(fā)人員都會(huì)系統(tǒng)地完成他們的優(yōu)化任務(wù):

  • 他們做出的預(yù)測(cè)都是可測(cè)試的,而且他們會(huì)記錄下預(yù)測(cè);
  • 他們保留代碼變更記錄;
  • 他們使用可以使用的最優(yōu)秀的工具進(jìn)行測(cè)量;
  • 他們會(huì)保留實(shí)驗(yàn)結(jié)果的詳細(xì)筆記。
停下來(lái)思考
請(qǐng)回過(guò)頭來(lái)再次閱讀上節(jié)中的內(nèi)容。其中包含了本書(shū)中最重要的建議。多數(shù)開(kāi)發(fā)人員 (包括筆者)都會(huì)想當(dāng)然地,而不是按照以上方式有條不紊地進(jìn)行優(yōu)化。這是一項(xiàng)必須 不斷實(shí)踐的技能。

3.1.2 優(yōu)化器是王牌獵人

我說(shuō)起飛后用核彈炸掉這地方。這是唯一的方法。 ——艾倫 ? 蕾普莉(西格麗 ? 維弗飾演),《異形 2》,1986 年

優(yōu)化器是王牌獵人。如果只能讓程序的運(yùn)行速度提高 1% 是不值得冒險(xiǎn)去修改代碼的,因?yàn)樾薷拇a可能會(huì)引入 bug。只有能顯著地提升性能時(shí)才值得修改代碼。而且,這 1% 的 速度提升可能只是將測(cè)量套件的誤差當(dāng)作了性能改善。因此,我們必須用隨機(jī)抽樣統(tǒng)計(jì) 和置信水平來(lái)證明速度的提升。但是完全沒(méi)有必要為了這么一點(diǎn)點(diǎn)性能提升花費(fèi)這么大氣 力。本書(shū)中不會(huì)推薦大家這么做。

當(dāng)性能提升 20% 的時(shí)候,事情就完全不同了。它會(huì)消除所有反對(duì)方法論的聲音。本書(shū)中雖 然沒(méi)有太多統(tǒng)計(jì)數(shù)字,不過(guò)我并不會(huì)為此感到抱歉。本書(shū)的重點(diǎn)是幫助開(kāi)發(fā)人員找到這樣 的性能改善點(diǎn):其顯著的效果足以戰(zhàn)勝任何對(duì)其價(jià)值的質(zhì)疑。這些性能改善點(diǎn)可能仍然取 決于操作系統(tǒng)和編譯器等因素,因此它們可能會(huì)在其他操作系統(tǒng)上或是其他時(shí)間點(diǎn)沒(méi)有太 好的效果。但是即使開(kāi)發(fā)人員把他們的代碼移植到新操作系統(tǒng)上,這些修改也幾乎從來(lái)都 不會(huì)反過(guò)來(lái)降低程序性能。

3.1.3 90/10規(guī)則

性能優(yōu)化的基本規(guī)則是 90/10 規(guī)則:一個(gè)程序花費(fèi) 90% 的時(shí)間執(zhí)行其中 10% 的代碼。這 只是一條啟發(fā)性的規(guī)則,并非自然法則,但對(duì)于我們的思考和計(jì)劃卻具有指導(dǎo)性。這條規(guī) 則有時(shí)也被稱為 80/20 規(guī)則,但思想是一樣的。直觀地說(shuō),90/10 規(guī)則表示某些代碼塊是會(huì) 被頻繁地執(zhí)行的熱點(diǎn)(hot spot),而其他代碼則幾乎不會(huì)被執(zhí)行。這些熱點(diǎn)就是我們要進(jìn) 行性能優(yōu)化的對(duì)象。

優(yōu)化戰(zhàn)爭(zhēng)故事
我是在作為專業(yè)開(kāi)發(fā)人員研發(fā)一種叫作 9010A 的帶鍵盤的嵌入式設(shè)備(圖 3-1)的項(xiàng) 目中初識(shí) 90/10 規(guī)則的。程序中有個(gè)函數(shù)會(huì)輪詢鍵盤,查看用戶是否按下了 STOP 鍵。這個(gè)函數(shù)會(huì)被每個(gè)例程 頻繁地執(zhí)行。手動(dòng)優(yōu)化 C 編譯器輸出的這個(gè)函數(shù)的 Z80 匯編代碼(耗費(fèi)了 45 分鐘) 將整體吞吐量提高了 7%,對(duì)這臺(tái)設(shè)備來(lái)說(shuō),非常不錯(cuò)了。一般情況下,這是一條典型的性能優(yōu)化經(jīng)驗(yàn)。在優(yōu)化過(guò)程的初期,大量的運(yùn)行時(shí)間都 集中消耗在程序中的某個(gè)位置。這個(gè)位置也非常明顯:在每個(gè)循環(huán)的每次迭代中都要 重復(fù)進(jìn)行的處理,就像每天的家務(wù)勞動(dòng)一樣。想要優(yōu)化這些代碼需要做出一項(xiàng)痛苦的 選擇——用匯編語(yǔ)言重寫(xiě)這些 C 語(yǔ)言代碼。但是由于使用匯編語(yǔ)言的代碼范圍極其有 限,選擇使用匯編語(yǔ)言降低了需要承受的風(fēng)險(xiǎn)。當(dāng)這段代碼被頻繁執(zhí)行時(shí),這條經(jīng)驗(yàn)同樣很典型。當(dāng)我們改善了這段代碼后,另一段 代碼成為了最頻繁地被執(zhí)行的代碼——不過(guò)它對(duì)整體運(yùn)行時(shí)間的影響已經(jīng)小多了。它 實(shí)在是太小了,以至于我們?cè)谶M(jìn)行了這一處改動(dòng)后就停止了性能優(yōu)化。我們甚至找不 到改動(dòng)后可以將程序執(zhí)行速度提高 1% 的地方了。

90/10 規(guī)則的一個(gè)結(jié)論是,優(yōu)化程序中的所有例程并沒(méi)有太大幫助。優(yōu)化一小部分代碼事 實(shí)上已經(jīng)足夠提供你所想要的性能提升了。識(shí)別出 10% 的熱點(diǎn)代碼是值得花費(fèi)時(shí)間的,但 靠猜想選擇優(yōu)化哪些代碼可能只是浪費(fèi)時(shí)間。

這里我想再一次引用第 1 章中曾經(jīng)引用過(guò)的高德納的一句名言。不過(guò),此處是那句名言一 個(gè)較長(zhǎng)的版本:

程序員浪費(fèi)了太多的時(shí)間去思考和擔(dān)憂程序中那些非關(guān)鍵部分的速度,而且考慮 到調(diào)試和維護(hù),這些為優(yōu)化而進(jìn)行的修改實(shí)際上是有很大負(fù)面影響的。我們應(yīng)當(dāng) 忘記小的性能改善,97% 的情況下,過(guò)早優(yōu)化都是萬(wàn)惡之源。

? ——高德納,“使用 goto 語(yǔ)句進(jìn)行結(jié)構(gòu)化編程”, ACM Computing Surveys 6 (Dec 1974): 268. CiteSeerX: 10.1.1.103.6084(http://citeseerx.ist.psu.edu/ viewdoc/summary?doi=10.1.1.103.6084)

正如有些人所建議的那樣,高德納博士也并非警告我們所有的優(yōu)化都是罪惡的。他只是說(shuō) 浪費(fèi)時(shí)間去優(yōu)化那非關(guān)鍵的 90% 的程序是罪惡的。很明顯,他也意識(shí)到了 90/10 規(guī)則。

3.1.4 阿姆達(dá)爾定律

阿姆達(dá)爾定律是由計(jì)算機(jī)工程先鋒基恩 ? 阿姆達(dá)爾(Gene Amdahl)提出并用他的名字命名 的,它定義了優(yōu)化一部分代碼對(duì)整體性能有多大改善。阿姆達(dá)爾定律有多種表達(dá)方式,不 過(guò)就優(yōu)化而言,可以表示為下面的等式:

其中 ST 是因優(yōu)化而導(dǎo)致程序整體性能提升的比率,P 是被優(yōu)化部分的運(yùn)行時(shí)間占原來(lái)程序 整體運(yùn)行時(shí)間的比例,SP 是被優(yōu)化部分 P 的性能改善的比率。

例如,假設(shè)一個(gè)程序的運(yùn)行時(shí)間是 100 秒。通過(guò)分析(請(qǐng)參見(jiàn) 3.3 節(jié))發(fā)現(xiàn)程序花費(fèi)了 80 秒多次調(diào)用函數(shù) f?,F(xiàn)在假設(shè)修改 f 使其運(yùn)行速度提升了 30%,那么這對(duì)程序整體運(yùn)行時(shí) 間有多大改善呢?

P 是函數(shù) f 的運(yùn)行時(shí)間占原來(lái)程序整體運(yùn)行時(shí)間的比例,即 0.8;SP 是被優(yōu)化的部分 P 的 性能改善的比率,即 1.3。將它們代入到阿姆達(dá)爾定律的公式中:

也就是說(shuō),將這個(gè)函數(shù)的性能提升 30% 會(huì)將程序整體運(yùn)行時(shí)間縮短 22%。在這個(gè)例子中, 阿姆達(dá)爾定律證明了 90/10 規(guī)則,而且通過(guò)這個(gè)例子向我們展示了,對(duì) 10% 的熱點(diǎn)代碼進(jìn) 行適當(dāng)?shù)膬?yōu)化,就可以帶來(lái)如此大的性能提升。

下面我們?cè)賮?lái)看一個(gè)例子。我們還是假設(shè)一個(gè)程序的運(yùn)行時(shí)間是 100 秒。通過(guò)分析,你發(fā) 現(xiàn)有一個(gè)函數(shù) g 的運(yùn)行時(shí)間是 10 秒?,F(xiàn)在假設(shè)你修改了函數(shù) g,將它的運(yùn)行速度提高了 100 倍。那么這對(duì)程序整體性能的提升有多大呢?

P 是函數(shù) g 的運(yùn)行時(shí)間占原來(lái)程序整體運(yùn)行時(shí)間的比例,即 0.1;SP 是 100。將它們代入到 阿姆達(dá)爾定律的公式中:

在這個(gè)例子中阿姆達(dá)爾定律是具有警示性的。即使有異常優(yōu)秀的編碼能力或是黑科技將函 數(shù) g 的運(yùn)行時(shí)間縮短為 0,它仍然是那并不重要的 90% 代碼中的一部分。將性能提升的比 率精確到兩個(gè)小數(shù)位后,對(duì)程序整體性能的提升依然只有 11%。阿姆達(dá)爾定律告訴我們, 如果被優(yōu)化的代碼在程序整體運(yùn)行時(shí)間中所占的比率不大,那么即使對(duì)它的優(yōu)化非常成功 也是不值得的。阿姆達(dá)爾定律的教訓(xùn)是,當(dāng)你的同事興沖沖地在會(huì)議上說(shuō)他知道如何將一 段計(jì)算處理的速度提高 10 倍,這并不一定意味著性能優(yōu)化工作就此結(jié)束了。

3.2 進(jìn)行實(shí)驗(yàn)

開(kāi)發(fā)軟件在某種意義上就是一項(xiàng)實(shí)驗(yàn)。你想讓程序做一些事情,然后開(kāi)始編程,最后觀察 程序的運(yùn)行結(jié)果是否與預(yù)想的一樣。性能調(diào)優(yōu)則是更有正式意義的實(shí)驗(yàn)。在開(kāi)始性能調(diào)優(yōu) 前,必須要有正確的代碼,即在某種意義上可以完成我們所期待的處理的代碼。你需要擦 亮眼睛審視這些代碼,然后問(wèn)自己:“為什么這些代碼是熱點(diǎn)?”為什么某個(gè)函數(shù)與程序 中的上百個(gè)函數(shù)不同,出現(xiàn)在了分析器的最差性能列表中的最前面?是這個(gè)函數(shù)浪費(fèi)了很 多時(shí)間在冗余處理上嗎?有其他更快的方法進(jìn)行相同的計(jì)算嗎?這個(gè)函數(shù)使用了緊缺的計(jì) 算機(jī)資源嗎?是這個(gè)函數(shù)自身已經(jīng)是非常快了,只不過(guò)它被調(diào)用了太多次,已經(jīng)沒(méi)有優(yōu)化 的余地了嗎?

你對(duì)于“為什么這些代碼是熱點(diǎn)”這個(gè)問(wèn)題的回答構(gòu)成了你要測(cè)試的假設(shè)。實(shí)驗(yàn)要對(duì)程序 的兩種運(yùn)行時(shí)間進(jìn)行測(cè)量:一種是修改前的運(yùn)行時(shí)間,一種是修改后的運(yùn)行時(shí)間。如果后 者比前者短,那么實(shí)驗(yàn)驗(yàn)證了你的假設(shè)。

請(qǐng)注意這里的用詞。實(shí)驗(yàn)并不需要證明任何事情。修改后的代碼可能會(huì)因?yàn)槟承┰蜻\(yùn)行 得更快或者更慢,但這些原因卻與你修改的部分沒(méi)有任何關(guān)系。比如:

  • 當(dāng)你在測(cè)量運(yùn)行時(shí)間時(shí),計(jì)算機(jī)可能在接收郵件或是檢查 Java 是否有版本更新;
  • 在你重編譯之前,一位同事剛剛簽入了一個(gè)性能改善后的庫(kù);
  • 你的修改可能運(yùn)行得更快,但是處理邏輯卻是不正確的。

優(yōu)秀的科學(xué)家是懷疑論者。他們總是對(duì)事物持有懷疑。如果沒(méi)有出現(xiàn)所期待的實(shí)驗(yàn)結(jié)果, 或是實(shí)驗(yàn)結(jié)果太好了,不像是對(duì)的,那么懷疑論者會(huì)再進(jìn)行一次實(shí)驗(yàn)或者質(zhì)疑她的假設(shè), 抑或檢查是否有 bug。

優(yōu)秀的科學(xué)家會(huì)接受新知識(shí),即使這些知識(shí)與他們腦海中的知識(shí)相悖。我在編寫(xiě)本書(shū)的過(guò) 程中學(xué)到了一些出乎意料的優(yōu)化知識(shí)。本書(shū)的技術(shù)審核者也從本書(shū)中學(xué)到了知識(shí)。優(yōu)秀的 科學(xué)家從不會(huì)停止學(xué)習(xí)。

優(yōu)化戰(zhàn)爭(zhēng)故事
在第 5 章有一個(gè)查找關(guān)鍵字的示例函數(shù)。我為這個(gè)示例函數(shù)編寫(xiě)了幾個(gè)不同的版本。 其中一個(gè)版本是線性查找(linear search),另一個(gè)版本則是二分查找(binary search)。 當(dāng)測(cè)量這兩個(gè)函數(shù)的性能時(shí),我發(fā)現(xiàn)線性查找的速度比二分查找快幾個(gè)百分點(diǎn)。這讓 我覺(jué)得不可思議。二分查找本應(yīng)當(dāng)更快,但是測(cè)量結(jié)果卻不是這樣的。 我注意到有人在互聯(lián)網(wǎng)上發(fā)表報(bào)告說(shuō)線性查找經(jīng)常會(huì)更快,因?yàn)橄啾榷植檎?#xff0c;它的 緩存局部性(cache locality)更好,而且確實(shí)我實(shí)現(xiàn)的線性查找應(yīng)當(dāng)具有非常優(yōu)秀的緩 存局部性。但是這個(gè)結(jié)果卻與我的經(jīng)驗(yàn)以及我從受人尊崇的書(shū)本上學(xué)到的有關(guān)查找算 法性能的知識(shí)相違背。 進(jìn)行了更深入的調(diào)查后我發(fā)現(xiàn),在測(cè)試時(shí)所使用的測(cè)試表格中只有幾個(gè)單詞,而且要 查找的關(guān)鍵字我自己都能從表格中找到。如果一個(gè)表格有 8 個(gè)項(xiàng)目,那么線性查找平 均會(huì)檢查其中半數(shù)(4)后返回結(jié)果。而二分查找每次被調(diào)用時(shí)都會(huì)將表格一分為二 (共 4 次),然后才能查找到關(guān)鍵字。這兩種算法對(duì)小的關(guān)鍵字集有著完全相同的平均 性能。直覺(jué)告訴我二分查找總是比線性查找更快,但這個(gè)結(jié)果告訴我我錯(cuò)了。 但是這并非我想證明的結(jié)果。所以我擴(kuò)大了測(cè)試數(shù)據(jù)表格,想著這個(gè)表格在達(dá)到某 個(gè)大小時(shí),一定會(huì)出現(xiàn)二分查找更快的結(jié)果。另外,我還向其中加入了一些原本不 在測(cè)試表格中的單詞??墒菧y(cè)試結(jié)果依然不變,線性查找更快。這時(shí),我不得不將 編寫(xiě)這份示例代碼的任務(wù)擱置了幾天,但是這個(gè)結(jié)果卻一直折磨著我。 我仍然相信二分查找應(yīng)當(dāng)更快。我檢查了兩種查找方式的單元測(cè)試代碼,最終發(fā)現(xiàn)線 性查找在進(jìn)行第一次比較后總是返回成功。我的測(cè)試用例檢查了是否返回了非零值, 而不是檢查是否返回了正確值。接著,我慚愧地修改了線性查找算法和測(cè)試用例?,F(xiàn) 在,實(shí)驗(yàn)結(jié)果與我所期待的一樣,二分查找的速度更快了。 在這個(gè)例子中,實(shí)驗(yàn)結(jié)果先否定然后又驗(yàn)證了我的假設(shè)——整個(gè)過(guò)程中一直在挑戰(zhàn) 我的假設(shè)。

3.2.1 記實(shí)驗(yàn)筆記

優(yōu)秀的優(yōu)化人員(如同所有優(yōu)秀的科學(xué)家)都會(huì)關(guān)心可重復(fù)性。這時(shí)實(shí)驗(yàn)室筆記本就可以 發(fā)揮作用了。為了驗(yàn)證猜想,優(yōu)化人員在對(duì)代碼進(jìn)行一處或多處修改后,利用輸入數(shù)據(jù)集 對(duì)代碼進(jìn)行性能測(cè)試,而測(cè)試則會(huì)在若干毫秒后結(jié)束。在與下次運(yùn)行時(shí)間進(jìn)行比較前一直 記著上次程序的運(yùn)行時(shí)間,這事兒并不難。如果每次代碼改善都是成功的,用腦袋記住就 足夠了。

不過(guò),開(kāi)發(fā)人員的猜想可能會(huì)出錯(cuò),這將導(dǎo)致最近一次的程序運(yùn)行時(shí)間比上一次的更長(zhǎng)。 這時(shí),無(wú)數(shù)的疑問(wèn)會(huì)充斥在開(kāi)發(fā)人員的腦中。雖然 5 號(hào)測(cè)試的運(yùn)行時(shí)間比 4 號(hào)長(zhǎng),但是它 比 3 號(hào)短嗎?在進(jìn)行 3 號(hào)測(cè)試時(shí)修改了哪些代碼?兩次測(cè)試間的速度差異是由其他因素造 成的,還是的確變快了?

如果每次的測(cè)試運(yùn)行情況都被記錄在案,那么就可以快速地重復(fù)實(shí)驗(yàn),回答上述問(wèn)題就會(huì) 變得很輕松了。否則,開(kāi)發(fā)人員必須回過(guò)頭去重新做一次實(shí)驗(yàn)來(lái)獲取運(yùn)行時(shí)間——前提是 他還記得應(yīng)該修改哪些代碼或是撤銷哪些修改。如果測(cè)試運(yùn)行很簡(jiǎn)單,開(kāi)發(fā)人員的記憶力 也非常好,那么他很幸運(yùn),只需要花費(fèi)一點(diǎn)時(shí)間即可重復(fù)實(shí)驗(yàn)。但是也有可能沒(méi)那么幸 運(yùn),明明想重復(fù)實(shí)驗(yàn)卻偏離了正確的前進(jìn)道路,或是毫無(wú)意義地浪費(fèi)一天去重復(fù)實(shí)驗(yàn)。

每當(dāng)我給出這條建議時(shí),總會(huì)有人說(shuō):“我不需要筆和紙就能做到!我可以寫(xiě)一段 Perl 腳 本去修改代碼版本管理工具的命令,讓它幫忙將每次運(yùn)行的測(cè)試結(jié)果和所修改的代碼一起 保存起來(lái)。如果我將測(cè)試結(jié)果保存在文件中……如果我在不同的目錄下做測(cè)試……”

我并不想妨礙開(kāi)發(fā)人員創(chuàng)新。如果你是一位主動(dòng)吸收最佳實(shí)踐的高級(jí)開(kāi)發(fā)經(jīng)理,那么盡管 這么做吧。不過(guò)我想說(shuō)的是,使用紙和筆記錄是一種很穩(wěn)健、容易使用而且有著千年歷史 的技術(shù)。即使在開(kāi)發(fā)團(tuán)隊(duì)替換了版本管理工具或測(cè)試套件的情況下,這項(xiàng)技術(shù)仍然可用。 它還適用于開(kāi)發(fā)人員的下一份工作。這項(xiàng)傳統(tǒng)的解決方案仍然可以節(jié)省開(kāi)發(fā)人員的時(shí)間。

3.2.2 測(cè)量基準(zhǔn)性能并設(shè)定目標(biāo)

獨(dú)立開(kāi)發(fā)人員可以隨意地、迭代地進(jìn)行優(yōu)化,直到他覺(jué)得性能足夠好了為止。不過(guò)工作于 團(tuán)隊(duì)中的開(kāi)發(fā)人員需要滿足經(jīng)理和其他利益相關(guān)人員的需求。優(yōu)化工作受兩個(gè)數(shù)字主導(dǎo): 優(yōu)化前的性能基準(zhǔn)測(cè)量值和性能目標(biāo)值。測(cè)量性能基準(zhǔn)不僅對(duì)于衡量每次獨(dú)立的改善是否 成功非常重要,而且對(duì)于向其他利益相關(guān)人員就優(yōu)化成本開(kāi)銷做出解釋也是非常重要的。

而優(yōu)化目標(biāo)值之所以重要,是因?yàn)樵趦?yōu)化過(guò)程中優(yōu)化效果會(huì)逐漸變小。在優(yōu)化過(guò)程的最初 階段,樹(shù)上總是有些容易摘取的掛得很低的水果:一些獨(dú)立的進(jìn)程或是想當(dāng)然地編寫(xiě)的函 數(shù),優(yōu)化它們后可以使性能提升很多。但是一旦實(shí)現(xiàn)了這些簡(jiǎn)單的優(yōu)化目標(biāo)后,下一輪性 能提升就需要付出更多的努力。

許多團(tuán)隊(duì)之所以在一開(kāi)始沒(méi)有為性能或是響應(yīng)性設(shè)定目標(biāo),只是因?yàn)樗麄儾⒉涣?xí)慣這么 做。幸運(yùn)的是,差勁的性能往往表現(xiàn)得非常明顯(例如用戶界面長(zhǎng)時(shí)間不響應(yīng)、托管服 務(wù)器的規(guī)模沒(méi)有可擴(kuò)展性、按照 CPU 時(shí)間付費(fèi)的成本非常高等)。一旦團(tuán)隊(duì)研究下性能問(wèn) 題,那么目標(biāo)數(shù)字很容易被設(shè)定下來(lái)。用戶體驗(yàn)(UX)設(shè)計(jì)的一個(gè)學(xué)科分支專門研究用 戶如何看待等待時(shí)間。下面是一份常用的性能測(cè)試項(xiàng)目清單,你可以從為這些項(xiàng)目設(shè)定性能目標(biāo)開(kāi)始。這其中有足夠多的與用戶體驗(yàn)相關(guān)的數(shù)字,可以讓你意識(shí)到危險(xiǎn)性。

啟動(dòng)時(shí)間

從用戶按下回車鍵直至程序進(jìn)入主輸入處理循環(huán)所經(jīng)過(guò)的時(shí)間。通常,開(kāi)發(fā)人員可以通 過(guò)測(cè)量程序進(jìn)入 main() 函數(shù)到進(jìn)入主循環(huán)的時(shí)間來(lái)得到啟動(dòng)時(shí)間,但是有時(shí)候也有例 外。為程序提供認(rèn)證的操作系統(tǒng)廠商對(duì)程序在計(jì)算機(jī)啟動(dòng)時(shí)或某個(gè)用戶登入時(shí)就運(yùn)行有 嚴(yán)格的要求。例如,對(duì)那些尋求認(rèn)證的硬件廠商,微軟會(huì)要求 Windows shell 必須在啟 動(dòng)后 10 秒內(nèi)能夠進(jìn)入它們的主循環(huán)。這限制了在忙碌的啟動(dòng)環(huán)境中,廠商可以預(yù)載和 啟動(dòng)的其他程序的數(shù)量。為此,微軟提供了專用工具來(lái)幫助硬件廠商測(cè)量啟動(dòng)時(shí)間。

退出時(shí)間

從用戶點(diǎn)擊關(guān)閉圖標(biāo)或是輸入退出命令直至程序?qū)嶋H完全退出所經(jīng)過(guò)的時(shí)間。通常,開(kāi) 發(fā)人員可以通過(guò)測(cè)量主窗口接收到關(guān)閉命令到程序退出 main() 的時(shí)間來(lái)得到退出時(shí)間, 但是有時(shí)候也有例外。退出時(shí)間也包含停止所有的線程和所依賴的進(jìn)程所需的時(shí)間。為 程序提供認(rèn)證的操作系統(tǒng)廠商對(duì)程序的退出時(shí)間有嚴(yán)格的要求。退出時(shí)間同樣非常重 要,因?yàn)橹貑⒁粋€(gè)服務(wù)或是長(zhǎng)時(shí)間運(yùn)行的程序所需的時(shí)間等于它的退出時(shí)間加上它的啟 動(dòng)時(shí)間。

響應(yīng)時(shí)間

執(zhí)行一個(gè)命令的平均時(shí)間或最長(zhǎng)時(shí)間。對(duì)于網(wǎng)站來(lái)說(shuō),平均響應(yīng)時(shí)間和最長(zhǎng)響應(yīng)時(shí)間 都會(huì)影響用戶對(duì)網(wǎng)站的滿意度。響應(yīng)時(shí)間可以粗略地以 10 的冪為單位劃分為以下幾 個(gè)級(jí)別。

時(shí)間狀態(tài)
低于 0.1 秒:用戶在直接控制如果響應(yīng)時(shí)間低于 0.1 秒,用戶會(huì)感覺(jué)他們?cè)谥苯涌刂朴脩艚缑?#xff0c;他們的操作直接 改變了用戶界面。這是用戶開(kāi)始拖動(dòng)對(duì)象至對(duì)象發(fā)生移動(dòng),或是用戶點(diǎn)擊輸入框至 輸入框變?yōu)楦吡林g的最小延遲。任何高于這個(gè)值的延遲都會(huì)讓用戶覺(jué)得他們發(fā)送 了一條命令讓計(jì)算機(jī)去執(zhí)行。
0.1 秒至 1 秒:用戶在控制命令如果響應(yīng)時(shí)間在 0.1 秒至 1 秒之間,用戶雖然仍然會(huì)覺(jué)得他們處于掌控狀態(tài),但是 這個(gè)短暫的延遲會(huì)被用戶理解為計(jì)算機(jī)執(zhí)行了一條命令導(dǎo)致 UI 發(fā)生了變化。用戶可 以忍受這種程度的延遲,不至于分散注意力。
1秒至 10 秒:計(jì)算機(jī)在控制如果響應(yīng)時(shí)間在 1 秒至 10 秒之間,用戶會(huì)覺(jué)得他們?cè)趫?zhí)行了一條命令后失去了對(duì) 計(jì)算機(jī)的控制,雖然這時(shí)候計(jì)算機(jī)仍然在處理命令。用戶可能會(huì)分散注意力,忘記 一件剛才發(fā)生的事情——他們需要完成自己的任務(wù)。10 秒是用戶能保持注意力的 最長(zhǎng)時(shí)間。如果他們多次遇到這種長(zhǎng)時(shí)間等待 UI 發(fā)生改變的情況,用戶滿意度會(huì) 急速下降。
高于 10 秒:喝杯咖啡休息一下如果響應(yīng)時(shí)間高于 10 秒,用戶會(huì)覺(jué)得他們有足夠的時(shí)間去做一些其他的事情。如 果他們的工作需要用到 UI,那么他們會(huì)利用等待計(jì)算機(jī)進(jìn)行計(jì)算的時(shí)間去喝一杯 咖啡。如果可以的話,他們甚至?xí)P(guān)閉程序,然后去其他地方找找滿足感。

雅各布 ? 尼爾森(Jakob Nielsen)就用戶體驗(yàn)中的響應(yīng)時(shí)間范圍寫(xiě)了一篇非常有意思的 文章(https://www.nngroup.com/articles/powers-of-10-time-scales-in-ux/),這是一份出于 好奇而進(jìn)行的學(xué)術(shù)研究。

吞吐量

與響應(yīng)時(shí)間相對(duì)。通常,吞吐量表述為在一定的測(cè)試負(fù)載下,系統(tǒng)在每個(gè)時(shí)間單位內(nèi)所 執(zhí)行的操作的平均數(shù)。吞吐量所測(cè)量的東西與響應(yīng)時(shí)間相同,但是它更適合于評(píng)估批處 理程序,如數(shù)據(jù)庫(kù)和 Web 服務(wù)等。通常,這個(gè)數(shù)字越大越好。

有時(shí),也可能會(huì)發(fā)生過(guò)度優(yōu)化的情況。例如,在許多情況下,用戶認(rèn)為響應(yīng)時(shí)間小于 0.1 秒就是一瞬間的事了。在這種情況下,即使將響應(yīng)時(shí)間從 0.1 秒改善為了 1 毫秒,也不會(huì) 增加任何價(jià)值,盡管響應(yīng)速度提升了 100 倍。

3.2.3 你只能改善你能夠測(cè)量的

優(yōu)化一個(gè)函數(shù)、子系統(tǒng)、任務(wù)或是測(cè)試用例永遠(yuǎn)不等同于改善整個(gè)程序的性能。由于測(cè)試 時(shí)的設(shè)置在許多方面都與處理客戶數(shù)據(jù)的正式產(chǎn)品不同,在所有環(huán)境中都取得在測(cè)試過(guò)程 中測(cè)量到的性能改善結(jié)果是幾乎不可能的。盡管某個(gè)任務(wù)在程序中負(fù)責(zé)大部分的邏輯處 理,但是使其變得更快可能仍然無(wú)法使整個(gè)程序變得更快。 例如,一個(gè)數(shù)據(jù)庫(kù)開(kāi)發(fā)人員通過(guò)執(zhí)行 1000 次某個(gè)特定的查詢語(yǔ)句分析了數(shù)據(jù)庫(kù)性能,然 后基于分析結(jié)果進(jìn)行了優(yōu)化,但這可能并不會(huì)提升整個(gè)數(shù)據(jù)庫(kù)的速度,而是只提升了該查 詢語(yǔ)句的速度。這也可能會(huì)提升其他查詢語(yǔ)句的速度,但它可能不會(huì)改善刪除或更新查 詢、建立索引或是數(shù)據(jù)庫(kù)可以進(jìn)行的其他處理的速度。

3.3 分析程序執(zhí)行

分析器是一個(gè)可以生成另外一個(gè)程序的執(zhí)行時(shí)間的統(tǒng)計(jì)結(jié)果的程序。分析器可以輸出一份 包含每個(gè)語(yǔ)句或函數(shù)的執(zhí)行頻度、每個(gè)函數(shù)的累積執(zhí)行時(shí)間的報(bào)表。

許多編譯器套件,如 Windows 上的 Visual Studio 和 Linux 上的 GCC 都帶有分析器,可以 幫助我們找到程序中的熱點(diǎn)。微軟曾經(jīng)只在價(jià)格昂貴的 Visual Studio 版本中提供了分析 器,但是自 Visual Studio 2015 社區(qū)版開(kāi)始,微軟開(kāi)始向開(kāi)發(fā)者提供免費(fèi)的分析器。當(dāng)然, 在 Windows 上還有其他開(kāi)源的分析器以及對(duì)應(yīng)早期的 Visual Studio 版本的分析器。

有幾種方式可以實(shí)現(xiàn)一個(gè)分析器。一種可以同時(shí)支持 Windows 和 Linux 的方法如下。

  • 程序員設(shè)置一個(gè)特殊的可以分析程序中所有函數(shù)的編譯選項(xiàng),重新編譯一次程序,讓 程序變?yōu)榭煞治龅臓顟B(tài)。這涉及在每個(gè)函數(shù)的開(kāi)始和結(jié)束處添加一些額外的匯編語(yǔ)言 指令。
  • 程序員將可分析的程序鏈接到分析庫(kù)上。
  • 每次這個(gè)可分析的程序運(yùn)行時(shí)都會(huì)在磁盤上生成一張分析表(profiling table)。
  • 分析器讀取分析表,然后生成一系列可閱讀的文字或圖形報(bào)告。
  • 通過(guò)將優(yōu)化前的程序鏈接至分析庫(kù)上使其變?yōu)榭煞治鰻顟B(tài)。分析庫(kù)中的例程會(huì)以非常高 的頻率中斷程序的執(zhí)行,記錄指令指針的值。
  • 每次可分析的程序運(yùn)行時(shí)都會(huì)在磁盤上生成一張分析表。
  • 分析器讀取分析表,然后生成一系列可閱讀的文字或圖形報(bào)告。
  • 分析器的輸出結(jié)果可能會(huì)有多種形式。一種形式是一份標(biāo)記有每行代碼的執(zhí)行次數(shù)的源代 碼清單。另一種形式是一份由函數(shù)名和該函數(shù)被調(diào)用的次數(shù)組成的清單。第三種形式同樣 也是函數(shù)清單,不過(guò)里面記錄的是每個(gè)函數(shù)的累計(jì)執(zhí)行時(shí)間和在每個(gè)函數(shù)中進(jìn)行的函數(shù)調(diào) 用。還有一種形式是一份函數(shù)和在每個(gè)函數(shù)中花費(fèi)的總時(shí)間的清單,但不包括調(diào)用其他函 數(shù)的時(shí)間、調(diào)用系統(tǒng)代碼的時(shí)間和等待事件的時(shí)間。

    分析器的分析功能都是量身設(shè)計(jì)的,它自身的性能開(kāi)銷非常小,因此它對(duì)程序整體運(yùn)行時(shí) 間的影響也很小。通常,程序中每個(gè)操作的執(zhí)行速度只會(huì)被降低幾個(gè)百分點(diǎn)。第一種方法 的分析結(jié)果會(huì)非常精確,代價(jià)是更高的間接成本和禁用了某些優(yōu)化。第二種方法的測(cè)量結(jié) 果是近似值,而且可能會(huì)遺漏一些非頻繁地被調(diào)用的函數(shù),但是它的優(yōu)點(diǎn)是可以直接運(yùn)行 于正式產(chǎn)品之上。

    分析器的最大優(yōu)點(diǎn)是它直接顯示出了代碼中最熱點(diǎn)的函數(shù)。優(yōu)化過(guò)程被簡(jiǎn)化為列出需要調(diào)查 的函數(shù)的清單,確認(rèn)各個(gè)函數(shù)優(yōu)化的可能性,修改代碼,然后重新運(yùn)行代碼得到一份新的分 析結(jié)果。如此反復(fù),直至沒(méi)有特別熱點(diǎn)的函數(shù)或是你無(wú)能為力了為止。由于分析結(jié)果中的熱 點(diǎn)函數(shù)從定義上來(lái)說(shuō)就是代碼中發(fā)生大量計(jì)算的地方,因此,通常這個(gè)過(guò)程是直截了當(dāng)?shù)摹?/p>

    以我個(gè)人的分析經(jīng)驗(yàn)來(lái)看,對(duì)調(diào)試構(gòu)建(debug build)的分析結(jié)果和對(duì)正式構(gòu)建(release build)的分析結(jié)果是一樣的。在某種意義上,調(diào)試構(gòu)建更易于分析,因?yàn)槠渲邪械?函數(shù),包括內(nèi)聯(lián)函數(shù),而正式構(gòu)建則會(huì)隱藏這些被頻繁調(diào)用的內(nèi)聯(lián)函數(shù)。

    專業(yè)優(yōu)化提示
    在 Windows 上分析調(diào)試構(gòu)建的一個(gè)問(wèn)題是,調(diào)試構(gòu)建所鏈接的是調(diào)試版本的運(yùn)行時(shí) 庫(kù)。調(diào)試版本的內(nèi)存管理器函數(shù)會(huì)執(zhí)行一些額外的測(cè)試,以便更好地報(bào)告重復(fù)釋放的 內(nèi)存和內(nèi)存泄漏問(wèn)題。這些額外測(cè)試的開(kāi)銷會(huì)顯著地增加某些函數(shù)的性能開(kāi)銷。有一 個(gè)環(huán)境變量可以讓調(diào)試器不要使用調(diào)試內(nèi)存管理器:進(jìn)入控制面板→系統(tǒng)屬性→高級(jí) 系統(tǒng)設(shè)置→環(huán)境變量→系統(tǒng)變量,然后添加一個(gè)叫作 _NO_DEBUG_HEAP 的新變量并設(shè)定 其值為 1。

    使用分析器是一種幫助我們找到要優(yōu)化的代碼的非常好的方式,但也有它的問(wèn)題。

    • 分析器無(wú)法告訴你有更高效的算法可以解決當(dāng)前的計(jì)算性能問(wèn)題。去優(yōu)化一個(gè)低效的算 法只是浪費(fèi)時(shí)間。
    • 對(duì)于會(huì)執(zhí)行許多不同任務(wù)的待優(yōu)化的程序,分析器無(wú)法給出明確的結(jié)果。例如,一個(gè) SQL 數(shù)據(jù)庫(kù)在執(zhí)行 insert 語(yǔ)句時(shí)和在執(zhí)行 select 語(yǔ)句時(shí)所運(yùn)行的代碼是不一樣的。因 此,當(dāng)使用 insert 加載數(shù)據(jù)庫(kù)時(shí)的熱點(diǎn)代碼,可能在數(shù)據(jù)庫(kù)執(zhí)行 select 語(yǔ)句的時(shí)候完 全不會(huì)被運(yùn)行。除非在分析時(shí)會(huì)進(jìn)行大量計(jì)算,否則請(qǐng)?jiān)跍y(cè)試中混合加載數(shù)據(jù)庫(kù)操作和 查詢數(shù)據(jù)庫(kù)操作,使執(zhí)行 insert 語(yǔ)句的代碼在分析結(jié)果中不那么突出。因此,要想容易地找出最熱點(diǎn)的函數(shù),請(qǐng)盡量一次僅優(yōu)化一個(gè)任務(wù)。這對(duì)于分析整個(gè)程 序中的一個(gè)子系統(tǒng)在測(cè)試套件上的運(yùn)行情況非常有幫助。不過(guò),如果每次只優(yōu)化一個(gè)任 務(wù),那么也會(huì)引入另外一種不確定性:即它不一定會(huì)改善程序的整體性能。而實(shí)際上當(dāng) 程序運(yùn)行多個(gè)任務(wù)時(shí),優(yōu)化的效果可能就體現(xiàn)得不那么明顯了。
    • 當(dāng)遇到 IO 密集型程序或是多線程程序時(shí),分析器的結(jié)果中可能會(huì)含有誤導(dǎo)信息,因?yàn)?分析器減去了系統(tǒng)調(diào)用的時(shí)間和等待事件的時(shí)間。不計(jì)算這些時(shí)間在理論上是完全合理 的,因?yàn)槌绦虿⒉恍枰獮檫@些等待時(shí)間負(fù)責(zé)。但是結(jié)果卻是分析器可以告訴我們程序做 了多少事情,而不是花了多少實(shí)際時(shí)間去做這些事情。有些分析器不僅統(tǒng)計(jì)了函數(shù)調(diào)用 的次數(shù),還計(jì)算出了每個(gè)函數(shù)的調(diào)用時(shí)間。如果函數(shù)調(diào)用次數(shù)非常多,意味著分析器可 能隱藏了實(shí)際時(shí)間。

    分析器并不完美。有些優(yōu)化可能性可能不會(huì)被分析出來(lái),而且程序員在理解分析器的輸出 結(jié)果時(shí)也可能會(huì)有問(wèn)題。不過(guò),對(duì)于許多程序來(lái)說(shuō),分析器的分析結(jié)果已經(jīng)足夠好了,不 需要再使用其他的優(yōu)化方法了。

    3.4 測(cè)量長(zhǎng)時(shí)間運(yùn)行的代碼

    如果程序只是運(yùn)行一個(gè)計(jì)算密集型的任務(wù),那么分析器會(huì)自動(dòng)地告訴我們程序中的熱點(diǎn)在 哪里。不過(guò)如果程序要做許多不同的處理,可能在分析器看來(lái),沒(méi)有任何一個(gè)函數(shù)是熱 點(diǎn)。程序還有可能會(huì)花費(fèi)大量的時(shí)間等待 I/O 或是外部事件,這樣降低了程序的性能,增 加了程序的實(shí)際運(yùn)行時(shí)間。在這種情況下,我們需要測(cè)量程序中各個(gè)部分的時(shí)間,然后試 著減少其中低效部分的運(yùn)行時(shí)間。

    開(kāi)發(fā)人員通過(guò)不斷地縮小長(zhǎng)時(shí)間運(yùn)行的任務(wù)的范圍直至定位其中一段代碼花費(fèi)了太長(zhǎng)時(shí) 間,感覺(jué)不對(duì)勁這種方式來(lái)查找代碼中的熱點(diǎn)。在找出這些可疑代碼后,開(kāi)發(fā)人員會(huì)在測(cè) 試套件中對(duì)小的子系統(tǒng)或是獨(dú)立的函數(shù)進(jìn)行優(yōu)化實(shí)驗(yàn)。

    測(cè)量運(yùn)行時(shí)間是一種測(cè)試關(guān)于“如何減少某個(gè)特定函數(shù)的性能開(kāi)銷”的假設(shè)的有效方式。

    一般,我們很難意識(shí)到可以通過(guò)編程在計(jì)算機(jī)上實(shí)現(xiàn)秒表功能。你可以非常方便地使用手 機(jī)或是手提電腦在工作日的 6:45 叫醒你,或是在早上 10 點(diǎn)的站立會(huì)議前 5 分鐘提醒你 參加會(huì)議。但是在現(xiàn)代計(jì)算機(jī)上測(cè)量亞微秒級(jí)的運(yùn)行時(shí)間卻是有點(diǎn)難度的,特別是因?yàn)樵?普通的 Window/PC 平臺(tái)上存在沒(méi)有可以穩(wěn)定地工作于不同型號(hào)的硬件和不同的軟件版本 上的高精度計(jì)時(shí)器的歷史遺留問(wèn)題。

    因此,作為一名開(kāi)發(fā)人員,你需要隨時(shí)準(zhǔn)備好制作一個(gè)自己的秒表,而且必須知道它們以 后可能會(huì)發(fā)生變化。為了使這成為可能,接下來(lái)我會(huì)討論如何測(cè)量時(shí)間以及有哪些工具可 用于在計(jì)算機(jī)上測(cè)量時(shí)間。

    3.4.1 一點(diǎn)關(guān)于測(cè)量時(shí)間的知識(shí)

    淺學(xué)害人。

    ? ——亞歷山大 ? 蒲柏,“批評(píng)論”(http://poetry.eserver.org/ essay-oncriticism.html), 1774 年

    一次完美的測(cè)量是指精確地得到大小、重量或者在本書(shū)中是某個(gè)事件每次持續(xù)的時(shí)間。完 美的測(cè)量就像是將弓箭不斷地精準(zhǔn)地射中靶心一樣。這種箭術(shù)只存在于故事書(shū)中,測(cè)量也 是一樣的。

    真正的測(cè)量實(shí)驗(yàn)(就像真正的弓箭)必須能夠應(yīng)對(duì)可變性(variation):可能破壞完美測(cè) 量的誤差源??勺冃杂袃煞N類型:隨機(jī)的和系統(tǒng)的。隨機(jī)的可變性對(duì)每次測(cè)量的影響都不 同,就像一陣風(fēng)導(dǎo)致弓箭偏離飛行線路一樣。系統(tǒng)的可變性對(duì)每次測(cè)量的影響是相似的, 就像一位弓箭手的姿勢(shì)會(huì)影響他每一次射箭都偏向靶子的左邊一樣。

    可變性自身也是可以測(cè)量的。衡量一次測(cè)量過(guò)程中的可變性的屬性被稱為精確性(precision) 和正確性(trueness)。這兩種屬性組合成的直觀特性稱為準(zhǔn)確性(accuracy)。

  • 精確性、正確性和準(zhǔn)確性
  • 很明顯,對(duì)測(cè)量感到興奮的科學(xué)家就相關(guān)的專業(yè)用語(yǔ)展開(kāi)了喋喋不休的爭(zhēng)論。你只需在維 基百科上查找一下“準(zhǔn)確性”這個(gè)詞,就會(huì)發(fā)現(xiàn)關(guān)于究竟應(yīng)該使用哪些詞來(lái)解釋已經(jīng)達(dá)成 一致的概念有多少爭(zhēng)議了。我選擇使用 1994 版的 ISO 5725-1 中的上下文來(lái)解釋術(shù)語(yǔ):“測(cè) 量方法和結(jié)果中的準(zhǔn)確性(正確性和精確性)——卷 1:通用原則和定義”(1994)。

    如果測(cè)量不受隨機(jī)可變性的影響,它就是精確的。也就是說(shuō),如果反復(fù)地測(cè)量同一現(xiàn)象, 而且這些測(cè)量值之間非常接近,那么測(cè)量就是精確的。一系列精確的測(cè)量中可能仍然包含 系統(tǒng)的可變性。盡管一位弓箭手將一組弓箭射到了偏離靶心的一塊區(qū)域中,但我們?nèi)匀豢?以說(shuō)這是精確的,盡管不太準(zhǔn)確。他射中的靶子的樣子可能如圖 3-2 所示。

    ? 圖 3-2:高精確性(但低正確性)的射箭結(jié)果

    如果測(cè)量一個(gè)事件(比如一個(gè)函數(shù)的運(yùn)行時(shí)間)10 次,而且 10 次的結(jié)果完全相同,我們 可以認(rèn)為測(cè)量是精確的。(像在任何實(shí)驗(yàn)中一樣,我應(yīng)當(dāng)會(huì)對(duì)此持懷疑態(tài)度,直到找到足 夠的證據(jù)為止。)如果其中只有 6 次結(jié)果相同,3 次結(jié)果略微有些不同,1 次結(jié)果的差異非 常大,那么測(cè)量就是不夠精確的。

    如果測(cè)量不受系統(tǒng)可變性的影響,它就是正確的。也就是說(shuō),如果反復(fù)地測(cè)量同一現(xiàn)象, 而且所有測(cè)量結(jié)果的平均值接近實(shí)際值,那可以認(rèn)為測(cè)量是正確的。每次獨(dú)立的測(cè)量可能 受到隨機(jī)可變性的影響,所以測(cè)量結(jié)果可能會(huì)更接近或是偏離實(shí)際值。正確性并不受弓箭手的技能影響。在圖 3-3 中,將四箭的平均值看作是一把箭的話,那么它應(yīng)當(dāng)是正中靶心 的。而且,就環(huán)數(shù)(離靶心的距離)而言,這四箭具有相同的準(zhǔn)確性。

    圖 3-3:弓箭手的箭找到了正確的靶心

    測(cè)量的準(zhǔn)確性是一個(gè)取決于每次獨(dú)立的測(cè)量結(jié)果與實(shí)際值有多接近的非正式的概念。與實(shí) 際值的差異由隨機(jī)可變性與系統(tǒng)可變性兩部分組成。只有同時(shí)具有精確性和正確性的測(cè)量 才是準(zhǔn)確的測(cè)量。

  • 測(cè)量時(shí)間
  • 本書(shū)中涉及的軟件性能測(cè)量要么是測(cè)量持續(xù)時(shí)間(兩個(gè)事件之間的時(shí)間),要么是測(cè)量速 率(單位時(shí)間內(nèi)事件的數(shù)量,與持續(xù)時(shí)間相對(duì))。用于測(cè)量持續(xù)時(shí)間的工具是時(shí)鐘。

    所有時(shí)鐘的工作原理都是周期性地計(jì)數(shù)。某些時(shí)鐘的計(jì)數(shù)會(huì)表示為時(shí)、分、秒,有些則是 直接顯示時(shí)標(biāo)的次數(shù)。但是時(shí)鐘(除了日晷外)是并不會(huì)直接測(cè)量時(shí)、分、秒的。它們只 會(huì)對(duì)時(shí)標(biāo)進(jìn)行計(jì)數(shù),然后只有將時(shí)標(biāo)計(jì)數(shù)值與秒基準(zhǔn)的時(shí)鐘進(jìn)行比較后才能校準(zhǔn)時(shí)鐘,顯 示出時(shí)、分、秒。

    周期性地改變的東西受到可變性的影響也會(huì)出現(xiàn)誤差。有些可變性是隨機(jī)的,有些可變性 則是系統(tǒng)的。

    • 日晷利用了地球的周期性旋轉(zhuǎn)。從定義上說(shuō),一次完整的旋轉(zhuǎn)是一天。地球并非完美的 時(shí)鐘,不僅是因?yàn)橹芷谔L(zhǎng),而且我們發(fā)現(xiàn)由于大陸在它表面上緩慢地移動(dòng),它的旋轉(zhuǎn) 速度時(shí)快時(shí)慢(微秒級(jí)別)。這種可變性是隨機(jī)的;來(lái)自月球和太陽(yáng)的潮汐力會(huì)降低地 球的整體旋轉(zhuǎn)速率。這種可變性是系統(tǒng)的。
    • 老式時(shí)鐘會(huì)對(duì)鐘擺有規(guī)律的擺動(dòng)計(jì)數(shù)。齒輪會(huì)隨著鐘擺驅(qū)動(dòng)指針旋轉(zhuǎn)來(lái)顯示時(shí)間。鐘擺 擺動(dòng)的間隔可以手動(dòng)調(diào)整,這樣所顯示的時(shí)間可以與地球旋轉(zhuǎn)同步。鐘擺擺動(dòng)的周期取 決于鐘擺的重量和它的長(zhǎng)度,這樣就可以根據(jù)需要讓擺動(dòng)得更快或是更慢。這種可變性 是系統(tǒng)的;而即使在最開(kāi)始鐘擺的擺動(dòng)非常精準(zhǔn),但摩擦、氣壓和累積的灰塵都會(huì)對(duì)擺 動(dòng)造成影響。這些都是隨機(jī)可變性因素。
    • 電子時(shí)鐘使用它的交流電源的周期性的 60Hz 正弦波驅(qū)動(dòng)同步電機(jī)。齒輪會(huì)下分基本振 蕩和驅(qū)動(dòng)指針來(lái)顯示時(shí)間。電子時(shí)鐘也并非完美的時(shí)鐘,因?yàn)楦鶕?jù)慣例(不是自然法 則),交流電源的周期只有 60Hz(在美國(guó))。當(dāng)負(fù)荷過(guò)高時(shí),電力公司會(huì)先降低振蕩周期,稍后又提高振蕩周期,這樣電子時(shí)鐘并不會(huì)走慢。所以,在炎熱夏日的午后電子時(shí)鐘的 一秒可能會(huì)比涼爽夜晚的一秒快(雖然我們總是對(duì)此表示懷疑)。這種可變性是隨機(jī)的。 將一個(gè)為美國(guó)用戶制造的電子時(shí)鐘插入到歐洲 50Hz 的交流電源插座中,它會(huì)走得慢。 與氣溫引起的隨機(jī)可變性相比,這種由歐洲電源插座引起的可變性是系統(tǒng)的。
    • 數(shù)字腕表采用石英晶體的誘導(dǎo)振動(dòng)作為基本振動(dòng)。邏輯電路會(huì)下分基本振動(dòng)并驅(qū)動(dòng)時(shí)間 顯示。石英晶體的振動(dòng)周期取決于它的大小、溫度以及加載的電壓。石英晶體的大小的 影響是系統(tǒng)的可變性,而溫度和電壓的可變性則是隨機(jī)的。

    時(shí)標(biāo)計(jì)數(shù)值肯定是一個(gè)無(wú)符號(hào)的值。不可能存在 -5 次時(shí)標(biāo)。我之所以在這里提醒大家這 個(gè)看似非常明顯的事實(shí),是因?yàn)檎缟院髸?huì)向大家展示的,許多開(kāi)發(fā)人員實(shí)現(xiàn)計(jì)時(shí)函數(shù)時(shí) 選擇有符號(hào)類型來(lái)表示持續(xù)時(shí)間。我不知道為什么他們這么做。我那十幾歲的兒子應(yīng)該會(huì) 說(shuō):“這沒(méi)什么大不了?!?/p>

  • 測(cè)量分辨率
  • 測(cè)量的分辨率是指測(cè)量所呈現(xiàn)出的單位的大小。

    一位弓箭手只要將弓箭射在指定環(huán)內(nèi)的任意位置,所得到的分?jǐn)?shù)都是相同的。靶心并非是 無(wú)限小的點(diǎn),而是一個(gè)給定直徑的圓環(huán)(請(qǐng)參見(jiàn)圖 3-4)。一支箭要么設(shè)在靶心,或是九 環(huán)、八環(huán)等。每一環(huán)的寬度就是射箭得分的分辨率。

    圖 3-4:分辨率:一支箭設(shè)在一環(huán)中任意地方的得分是相同的

    時(shí)間測(cè)量的有效分辨率會(huì)受到潛在波動(dòng)的持續(xù)時(shí)間的限制。時(shí)間測(cè)量結(jié)果可以是一次或者 兩次時(shí)標(biāo),但不能是這兩者之間。這些時(shí)標(biāo)之間的間隔就是時(shí)鐘的有效分辨率。

    觀察人員可能會(huì)察覺(jué)到一個(gè)走得很慢的時(shí)鐘的兩次時(shí)標(biāo)之間發(fā)生的事情,例如鐘擺的一次 擺動(dòng)。這只是說(shuō)明在人類腦海中有一個(gè)更快的時(shí)鐘(雖然沒(méi)有那么準(zhǔn)確),他們會(huì)將這個(gè) 時(shí)鐘的時(shí)間與鐘擺的時(shí)間進(jìn)行比較。觀察人員如果想測(cè)量那些不可感知的持續(xù)時(shí)間,例如 毫秒級(jí)別,只能用時(shí)鐘的時(shí)標(biāo)。

    在測(cè)量的準(zhǔn)確性與它的分辨率之間是沒(méi)有任何必需的關(guān)聯(lián)的。例如,假設(shè)我記錄了我每天 的工作,那么我可以報(bào)告說(shuō)我花了兩天來(lái)編寫(xiě)本節(jié)內(nèi)容。在這個(gè)例子中,測(cè)量的有效分辨 率是“天”。如果我想把這個(gè)時(shí)間換成秒,那么可以報(bào)告說(shuō)成我花了 172 800 秒來(lái)編寫(xiě)本節(jié)

    內(nèi)容。但除非我手頭上有一個(gè)秒表,否則以秒為單位進(jìn)行報(bào)告會(huì)讓人誤認(rèn)為比之前更加準(zhǔn) 確,或是給人一種沒(méi)有吃飯和睡覺(jué)的錯(cuò)覺(jué)。

    測(cè)量結(jié)果的單位可能會(huì)比有效分辨率小,因?yàn)閱挝徊攀菢?biāo)準(zhǔn)。我有一個(gè)可以以華氏溫度為 單位顯示溫度的烤箱。恒溫器控制著烤箱,但是有效分辨率只有 5°F。所以在烤箱加熱的 過(guò)程中,顯示屏上顯示的溫度會(huì)是 300°F,接著是 305°F、310°F、315°F 等。以一度為單 位顯示溫度應(yīng)該比恒溫器的單位更合理。有效分辨率只有 5°F 只是表示測(cè)量的最低有效位 只能是 0 或者 5。

    當(dāng)讀者知道他們身邊廉價(jià)的溫度計(jì)、尺子和其他測(cè)量設(shè)備的有效分辨率后可能會(huì)感到吃驚 和失望,因?yàn)檫@些設(shè)備的顯示分辨率是 1 個(gè)單位或是 1/10 單位。

  • 用多個(gè)時(shí)鐘測(cè)量
  • 只有一塊表的人知道現(xiàn)在的時(shí)間,而擁有兩塊表的人卻永遠(yuǎn)不能確定現(xiàn)在的時(shí)間。

    ? ——多認(rèn)為該名言出自 Lee Segall

    當(dāng)兩個(gè)事件在同一個(gè)地點(diǎn)發(fā)生時(shí),很容易通過(guò)一個(gè)時(shí)鐘的時(shí)標(biāo)計(jì)數(shù)來(lái)測(cè)量事件的經(jīng)過(guò)時(shí) 間。但是如果這兩個(gè)事件發(fā)生在相距很遠(yuǎn)的不同地點(diǎn),可能就需要兩個(gè)時(shí)鐘來(lái)測(cè)量時(shí)間。 而兩個(gè)不同時(shí)鐘的時(shí)標(biāo)次數(shù)無(wú)法直接比較。

    人類想到了一個(gè)辦法,那就是通過(guò)與國(guó)際協(xié)調(diào)時(shí)間(Coordinated Universal Time)同步。 國(guó)際協(xié)調(diào)時(shí)間與經(jīng)度 0 度的天文學(xué)上的午夜同步,而經(jīng)度 0 度這條線穿過(guò)了英格蘭格林威 治皇家天文臺(tái)中的一塊漂亮的牌匾(請(qǐng)參見(jiàn)圖 3-5)。這樣就可以將一個(gè)以時(shí)標(biāo)計(jì)數(shù)值表示 的時(shí)間轉(zhuǎn)換為以時(shí)分秒表示的相對(duì) UTC(Universal Time Coorinated,國(guó)際協(xié)調(diào)間,由 法國(guó)和英國(guó)的時(shí)鐘專家商定的一個(gè)既不是法式拼寫(xiě)也不是英式拼寫(xiě)的縮寫(xiě))午夜的時(shí)間。

    圖 3-5:英格蘭格林威治皇家天文學(xué)館的本初子午線的標(biāo)記(攝影: ?var Arnfj?re Bjarmason, license CC BY-SA 3.0)

    如果兩個(gè)時(shí)鐘都與 UTC 完美地同步了,那么其中一個(gè)時(shí)鐘的相對(duì) UTC 時(shí)間可以直接與另 外一個(gè)相比較。但是當(dāng)然,完全的同步是不可能的。兩個(gè)時(shí)鐘都有各自獨(dú)立的可變性因 素,導(dǎo)致它們與 UTC 之間以及它們互相之間產(chǎn)生誤差。

    3.4.2 用計(jì)算機(jī)測(cè)量時(shí)間

    要想在計(jì)算機(jī)上制作一個(gè)時(shí)鐘需要一個(gè)周期性的振動(dòng)源——最好有很好的精確性和正確 性——以及一種讓軟件獲取振動(dòng)源的時(shí)標(biāo)的方法。要想專門為了計(jì)時(shí)而制造一臺(tái)計(jì)算機(jī)是 很容易的。不過(guò),多數(shù)現(xiàn)在流行的計(jì)算機(jī)體系結(jié)構(gòu)在設(shè)計(jì)時(shí)都沒(méi)有考慮過(guò)要提供很好的時(shí) 鐘。我將會(huì)結(jié)合 PC 體系結(jié)構(gòu)和微軟的 Windows 操作系統(tǒng)講解問(wèn)題所在。Linux 和嵌入式 平臺(tái)上也存在類似的問(wèn)題。

    PC 時(shí)鐘電路核心部分的晶體振蕩器的基本精度是 100PPM,即 0.01%,或者每天約 8 秒的 誤差。雖然這個(gè)精度只比數(shù)字腕表的精度高一點(diǎn)點(diǎn),但對(duì)性能測(cè)量來(lái)說(shuō)已經(jīng)足夠了,因?yàn)?對(duì)于極其非正式的測(cè)量結(jié)果,精確到幾個(gè)百分點(diǎn)就可以了。廉價(jià)的嵌入式處理器的時(shí)鐘電 路的精確度較低,但是最大的問(wèn)題并非周期性振動(dòng)的振動(dòng)源,更困難的是如何讓程序得到 可靠的時(shí)標(biāo)計(jì)數(shù)值。

  • 硬件時(shí)標(biāo)計(jì)數(shù)器的發(fā)展
  • 起初的 IBM PC 是不包含任何硬件時(shí)標(biāo)計(jì)數(shù)器的。它確實(shí)有一個(gè)記錄一天之中的時(shí)間的 時(shí)鐘,軟件也可以讀取這個(gè)時(shí)間。最早的微軟的 C 運(yùn)行時(shí)庫(kù)復(fù)制了 ANSI C 庫(kù),提供了 time_t time(time_t*) 函數(shù)。該函數(shù)會(huì)返回一個(gè)距離 UTC 時(shí)間 1970 年 1 月 1 日 0:00 的秒 數(shù)。舊版本的 time() 函數(shù)返回的是一個(gè) 32 位有符號(hào)整數(shù),但是在經(jīng)歷了 Y2K3 之后,它被 修改成了一個(gè) 64 位的有符號(hào)整數(shù)。

    起初的 IBM PC 會(huì)使用來(lái)自交流電源的周期性的中斷來(lái)喚醒內(nèi)核去進(jìn)行任務(wù)切換或是進(jìn)行 其他內(nèi)核操作。在北美,這個(gè)周期是 16.67 毫秒,因?yàn)榻涣麟娫词?60Hz 的。如果交流電 源是 50Hz 的話,這個(gè)周期就是 20 毫秒。

    自 Windows 98(可能更早)以來(lái),微軟的 C 運(yùn)行時(shí)提供了 ANSI C 函數(shù) clock_t clock()。 該函數(shù)會(huì)返回一個(gè)有符號(hào)形式的時(shí)標(biāo)計(jì)數(shù)器。常量 CLOCKS_PER_SEC 指定了每秒鐘的時(shí)標(biāo)的 次數(shù)。返回值為 -1 表示 clock() 不可用。clock() 會(huì)基于交流電源的周期性中斷記錄時(shí)標(biāo)。 clock() 在 Windows 上的實(shí)現(xiàn)方式與 ANSI 所規(guī)定的不同,在 Windows 上它所測(cè)量的是經(jīng) 過(guò)時(shí)間而非 CPU 時(shí)間 4 。最近,clock() 被根據(jù) GetSystemTimeAsfileTime() 重新實(shí)現(xiàn)了。在 2015 年時(shí)它的時(shí)標(biāo)是 1 毫秒,分辨率也是 1 毫秒。這使得它成了 Windows 上一個(gè)優(yōu)秀的 毫秒級(jí)別的時(shí)鐘。

    自 Windows 2000 開(kāi)始,可以通過(guò)調(diào)用 DWORD GetTickCount() 來(lái)實(shí)現(xiàn)基于 A/C 電源中斷的 軟件時(shí)標(biāo)計(jì)數(shù)器。GetTickCount() 的時(shí)標(biāo)計(jì)數(shù)值取決于 PC 的硬件,可能會(huì)遠(yuǎn)比 1 毫秒長(zhǎng)。 GetTickCount() 會(huì)進(jìn)行一次將時(shí)標(biāo)轉(zhuǎn)換為毫秒的計(jì)算來(lái)消除部分不確定性。這個(gè)方法的一 個(gè)升級(jí)版是 ULONGLONG GetTickCount64(),它會(huì)以 64 位無(wú)符號(hào)整數(shù)的形式返回相同的時(shí)標(biāo)計(jì)數(shù)值,這樣可以測(cè)量更長(zhǎng)的處理時(shí)間。雖然沒(méi)有辦法知道當(dāng)前的中斷周期,但下面這對(duì) 函數(shù)可以縮短和然后恢復(fù)周期:

    MMRESULT timeBeginPeriod(UINT) MMRESULT timeEndPeriod(UINT)

    這兩個(gè)函數(shù)作用于全局變量上,會(huì)影響所有的進(jìn)程和其他函數(shù),如取決于交流電源的中斷 周期的 Sleep()。另外一個(gè)函數(shù) DWORD timeGetTime() 可以通過(guò)另一種方法獲取相同的時(shí)標(biāo) 計(jì)數(shù)值。

    自奔騰體系結(jié)構(gòu)后,英特爾提供了一個(gè)叫作時(shí)間戳計(jì)數(shù)器(Time Stamp Counter,TSC)的 硬件寄存器。TSC 是一個(gè)從處理器時(shí)鐘中計(jì)算時(shí)標(biāo)數(shù)的 64 位寄存器。RDTSC 指令可以非常 快地訪問(wèn)該寄存器。

    自 Windows 2000 問(wèn)世后,可以通過(guò)調(diào)用函數(shù) BOOL Query PerformanceCounter(LARGE_ INTEGER*) 來(lái)讀取 TSC,這將會(huì)產(chǎn)生一次特殊的不帶分辨率的時(shí)標(biāo)計(jì)數(shù)??梢酝ㄟ^(guò)調(diào)用 BOOL QueryPerformanceFrequency(LARGE_INTEGER*) 來(lái)獲得分辨率,它會(huì)返回每秒鐘時(shí)標(biāo)的 頻率。LARGE_INTEGER 是一個(gè)帶有有符號(hào)格式的 64 位整數(shù)的結(jié)構(gòu)體,因?yàn)樵诋?dāng)時(shí)引入了以 上這些函數(shù)的 Visual Studio 中還沒(méi)有原生的 64 位有符號(hào)整數(shù)類型。

    初始版本的 QueryPerformanceCounter() 的一個(gè)問(wèn)題是,它的時(shí)標(biāo)速率取決于處理器的時(shí)鐘。不同處理器和主板的處理器時(shí)鐘不同。在當(dāng)時(shí),老式的 PC,特別是那些使用超微半導(dǎo)體公司(Advanced Micro Devices,AMD)處理器的 PC 是沒(méi)有 TSC 的。在當(dāng)時(shí)沒(méi)有 TSC 可用的情況下,QueryPerformanceCounter() 會(huì)返回 GetTickCount() 返回的低分辨率的時(shí)標(biāo)計(jì)數(shù)值。

    在 Windows 2000 中還新增加了一個(gè) void GetSystemTimeAsfileTime(fiLETIME*) 函數(shù),它 會(huì)返回一個(gè)自 1601 年 1 月 1 日 00:00 UTC 開(kāi)始計(jì)算的以 100 納秒為時(shí)標(biāo)的計(jì)數(shù)值。其中, fiLETIME 也是一個(gè)帶有 64 位整數(shù)的結(jié)構(gòu)體,不過(guò)這次是無(wú)符號(hào)的形式。盡管該時(shí)標(biāo)計(jì)數(shù) 器顯示出來(lái)的分辨率看起來(lái)非常高,有些實(shí)現(xiàn)卻使用了與 GetTickCount() 所使用的低分辨 率計(jì)數(shù)器相同的計(jì)數(shù)器。

    很快,QueryPerformanceCounter() 的更多問(wèn)題暴露出來(lái)了。有些處理器實(shí)現(xiàn)了可變時(shí)鐘頻率來(lái)管理功耗。這會(huì)導(dǎo)致時(shí)標(biāo)周期發(fā)生了變化。在擁有多個(gè)獨(dú)立處理器的多處理器系統(tǒng) 中,QueryPerformanceCounter() 返回的值取決于線程運(yùn)行于哪個(gè)處理器之上。處理器開(kāi) 始實(shí)現(xiàn)指令重排序之后,導(dǎo)致 RDTSC 指令可能會(huì)發(fā)生延遲,降低使用了 TSC 的軟件的 準(zhǔn)確性。

    為了解決這些問(wèn)題,Windows Vista 為 QueryPerformanceCounter() 使用了一種不同的計(jì) 數(shù)器,稱為 Advanced Configuration and Power Interface(ACPI)電源管理計(jì)時(shí)器。使用 這個(gè)計(jì)數(shù)器雖然能夠解決多處理器的同步問(wèn)題,但是卻顯著地增加了延遲。與此同時(shí), 英特爾重新指定了 TSC 為最大且不變的時(shí)鐘頻率。此外,英特爾還增加了不可重排序的 RDTSCP 指令。

    自 Windows 8 開(kāi)始,Windows 提供了一種基于 TSC 的、可靠的、高分辨率的硬件時(shí)標(biāo)計(jì) 數(shù)。只要該系統(tǒng)運(yùn)行于 Windows 8 或者之后的版本上,void GetSystemTimePreciseAsfileT ime(fiLETIME*) 就可以生成一個(gè)固定頻率和亞微秒準(zhǔn)確度的高分辨率時(shí)標(biāo)。

    一句話總結(jié)本堂歷史課的內(nèi)容就是,**PC 從來(lái)都不是設(shè)計(jì)作為時(shí)鐘的,因此它們提供的時(shí) 標(biāo)計(jì)數(shù)器是不可靠的。**如果以過(guò)去 35 年的歷史為鑒,未來(lái)的處理器和操作系統(tǒng)可能依然 無(wú)法提供穩(wěn)定的、高分辨率的時(shí)標(biāo)計(jì)數(shù)值。

    歷代 PC 都提供的唯一可靠的時(shí)標(biāo)計(jì)數(shù)器就是 GetTickCount() 返回的時(shí)標(biāo)計(jì)數(shù)器了,盡管它也有缺點(diǎn)。clock() 返回的毫秒級(jí)的時(shí)標(biāo)更好,而且近 10 年生產(chǎn)的 PC 應(yīng)該都是支持該函數(shù)的。如果只考慮 Windows 8 及之后的版本和新的處理器的話, GetSystemTimePreciseAsfileTime() 返回的 100 納秒級(jí)別的時(shí)標(biāo)計(jì)數(shù)器是非常精確的。不 過(guò),就我個(gè)人的經(jīng)驗(yàn)來(lái)看,對(duì)時(shí)間測(cè)量來(lái)說(shuō)毫秒級(jí)別的準(zhǔn)確性已經(jīng)足夠了。

  • 返轉(zhuǎn)
  • 返轉(zhuǎn)(wraparound)是指當(dāng)時(shí)鐘的時(shí)標(biāo)計(jì)數(shù)器值到達(dá)最大值后,如果再增加就變?yōu)?0 的 過(guò)程。12 小時(shí)制的模擬時(shí)鐘在每天的正午和午夜各會(huì)進(jìn)行一次返轉(zhuǎn)。Windows 98 在連續(xù) 運(yùn)行 49 天后會(huì)因 32 位毫秒時(shí)標(biāo)計(jì)數(shù)器的返轉(zhuǎn)而掛起(請(qǐng)參見(jiàn) Q216641)。當(dāng)兩位數(shù)的年 份返轉(zhuǎn)時(shí)會(huì)發(fā)生 Y2K 問(wèn)題?,斞湃諝v在 2012 年返轉(zhuǎn),因?yàn)楝斞湃苏J(rèn)為那就是世界末日。 UNIX 時(shí)間戳(自 UTC1970 年 1 月 1 日 00:00 起的帶符號(hào)的 32 位秒數(shù))會(huì)在 2038 年 1 月 發(fā)生返轉(zhuǎn),這可能會(huì)稱為某些“歷史悠久”的嵌入式系統(tǒng)的“世界末日”。返轉(zhuǎn)的問(wèn)題出在缺少額外的位去記錄數(shù)據(jù),導(dǎo)致下次時(shí)間增加后的數(shù)值比上次時(shí)間的數(shù)值小。會(huì)返轉(zhuǎn)的時(shí)鐘僅適用于測(cè)量持續(xù)時(shí)間小于返轉(zhuǎn)間隔的時(shí)間。

    例如,在 Windows 上,GetTickCount() 函數(shù)會(huì)返回一個(gè)分辨率為 1 毫秒的 32 位無(wú)符號(hào)的 整數(shù)值作為時(shí)標(biāo)計(jì)數(shù)值。那么,GetTickCount() 的返回值會(huì)每 49 天返轉(zhuǎn)一次。也就是說(shuō), GetTickCount() 適用于測(cè)量那些所需時(shí)間小于 49 天的操作。如果一個(gè)程序在某個(gè)操作開(kāi)始時(shí)和結(jié)束時(shí)分別調(diào)用了 GetTickCount(),兩個(gè)返回值之間的差值就是兩次調(diào)用之間經(jīng)過(guò)的毫秒數(shù)。例如:

    DWORD start = GetTickCount();DoBigTask(); DWORD end = GetTickCount(); cout << "Startup took " << end-start << " ms" << endl;

    C++ 實(shí)現(xiàn)無(wú)符號(hào)算術(shù)的方式去確保了即使在發(fā)生返轉(zhuǎn)時(shí)也可以得到正確的結(jié)果。 GetTickCount() 對(duì)于記住自程序啟動(dòng)后所經(jīng)過(guò)的時(shí)間是比較低效的。許多“歷史悠久”的 服務(wù)器可以持續(xù)運(yùn)行數(shù)個(gè)月甚至數(shù)年。返轉(zhuǎn)的問(wèn)題在于,由于缺少位數(shù)去記錄返轉(zhuǎn)的次 數(shù),end-start 的結(jié)果中可能體現(xiàn)不出發(fā)生了返轉(zhuǎn),或是體現(xiàn)出一個(gè)或者多個(gè)返轉(zhuǎn)。 自 Windows Vista 開(kāi)始,微軟加入了 GetTickCount64() 函數(shù),它會(huì)返回一個(gè) 64 位無(wú)符號(hào)且 顯示分辨率為 1 毫秒的時(shí)標(biāo)計(jì)數(shù)值。GetTickCount64() 的結(jié)果只有在數(shù)百萬(wàn)年后才會(huì)發(fā)生返轉(zhuǎn)。這就意味著,幾乎不會(huì)有人能夠見(jiàn)證返轉(zhuǎn)發(fā)生了。

  • 分辨率不是準(zhǔn)確性
  • 在 Windows 上,GetTickCount() 會(huì)返回一個(gè)無(wú)符號(hào)的 32 位整數(shù)值。如果一個(gè)程序在某個(gè)操作開(kāi)始和結(jié)束時(shí)分別調(diào)用了 GetTickCount(),兩個(gè)返回值之間的差值就是兩次調(diào)用之間 經(jīng)過(guò)的毫秒單位的執(zhí)行時(shí)間。因此,GetTickCount() 的分辨率是 1 毫秒。

    例如,下面這段代碼通過(guò)在循環(huán)中反復(fù)調(diào)用 Foo(),在 Windows 上測(cè)量了名為 Foo() 的函數(shù)的相對(duì)性能。通過(guò)在代碼塊開(kāi)始和結(jié)束時(shí)得到的時(shí)標(biāo)計(jì)數(shù)值,我們可以計(jì)算出循環(huán)處理 所花費(fèi)的時(shí)間:

    DWORD start = GetTickCount(); for (unsigned i = 0; i < 1000; ++i) {Foo(); } DWORD end = GetTickCount(); cout << "1000 calls to Foo() took " << end-start << "ms" << endl;

    如果 Foo() 中包含了大量的計(jì)算,那么這段代碼的輸出結(jié)果可能如下:

    1000 calls to Foo() took 16ms

    不幸的是,從微軟網(wǎng)站中關(guān)于 GetTickCount() 的文檔(https://msdn.microsoft.com/en-us/ library/windows/desktop/ms724408(v=vs.85).aspx)來(lái)看,調(diào)用 GetTickCount() 的準(zhǔn)確性 可能是 10 毫秒或 15.67 毫秒。也就是說(shuō),如果連續(xù)調(diào)用兩次 GetTickCount(),那么結(jié)果之間的差值可能是 0 或者 1 毫秒,也可能是 10、15 或 16 毫秒。因此,測(cè)量的基礎(chǔ)精度 是 15 毫秒,額外的分辨率毫無(wú)價(jià)值。之前代碼塊的輸出結(jié)果可能會(huì)是 10ms、20ms 或精 確的 16ms。

    GetTickCount() 特別讓人沮喪的一點(diǎn)是,除了分辨率是 1 毫秒外,無(wú)法確保在兩臺(tái) Windows 計(jì)算機(jī)中該函數(shù)是以某種方式或是相同方式實(shí)現(xiàn)的。

    我在 Windows 上測(cè)試了許多計(jì)時(shí)函數(shù),試圖找出它們?cè)谀骋慌_(tái)計(jì)算機(jī)(基于 i7 處理器的 Surface 3 平板電腦)的某個(gè)操作系統(tǒng)(Windows 8.1)上的可用分辨率。示例代碼 3-1 中的 測(cè)試循環(huán)地調(diào)用了測(cè)量時(shí)間的函數(shù),并檢查這些連續(xù)的函數(shù)調(diào)用的返回值之間的差值。如果時(shí)標(biāo)的可用分辨率大于函數(shù)調(diào)用的延遲,那么這些連續(xù)的函數(shù)調(diào)用的返回值將要么相同,要么它們之間的差值是若干個(gè)基礎(chǔ)時(shí)標(biāo),單位是函數(shù)的分辨率。我計(jì)算了非零差值的 平均值,以排除操作系統(tǒng)偷用時(shí)間片段去執(zhí)行其他任務(wù)的誤差。

    代碼清單 3-1 測(cè)量 GetTickCount() 的時(shí)標(biāo)

    unsigned nz_count = 0, nz_sum = 0; ULONG last, next; for (last = GetTickCount(); nz_count < 100; last = next) {next = GetTickCount();if (next != last) {nz_count += 1;nz_sum += (next - last);} } std::cout << "GetTickCount() mean resolution "<< (double)nz_sum / nz_count<< " ticks" << std::endl;

    我將測(cè)量結(jié)果總結(jié)在了表 3-1 中。

    表3-1 :在i7的Surface Pro 3(Windows 8.1)上測(cè)量的時(shí)標(biāo)結(jié)果:

    函數(shù)時(shí)標(biāo)
    time()1 秒
    GetTickCount()15.6 毫秒
    GetTickCount64()15.6 毫秒
    timeGetTime()15.6 毫秒
    clock()1.0 毫秒
    GetSystemTimeAsFileTime()0.9 毫秒
    GetSystemTimePreciseAsFileTime()約 450 納秒
    QueryPerformanceCounter()約 450 納秒

    需要特別注意的是 GetSystemTimeAsfileTime() 函數(shù)。它的顯示分辨率是 100 納秒,但 是看起來(lái)卻似乎是基于同樣低分辨率的 1 毫秒時(shí)標(biāo)的 clock() 實(shí)現(xiàn)的,而 GetSystemTime PreciseAsfileTime() 看起來(lái)則是用 QueryPerformanceCounter() 實(shí)現(xiàn)的。

    現(xiàn)代計(jì)算機(jī)的基礎(chǔ)時(shí)鐘周期已經(jīng)短至了數(shù)百皮秒(100 皮秒是 10-10 秒)。它們可以以幾納秒的速度執(zhí)行指令。但是在這些 PC 上卻沒(méi)有提供可訪問(wèn)的皮秒級(jí)或是納秒級(jí)的時(shí)標(biāo)計(jì)數(shù)器。在 PC 上,可使用的最快的時(shí)標(biāo)計(jì)數(shù)器的分辨率是 100 納秒級(jí)的,而且它們的基礎(chǔ)準(zhǔn)確性可能遠(yuǎn)比它們的分辨率更低。這就導(dǎo)致不太可能測(cè)量函數(shù)的一次調(diào)用的持續(xù)時(shí)間。讀者可以參見(jiàn) 3.4.3 節(jié)看看如何應(yīng)對(duì)這個(gè)問(wèn)題。

  • 延遲
  • 延遲是指從發(fā)出命令讓活動(dòng)開(kāi)始到它真正開(kāi)始之間的時(shí)間。延遲是從丟下一枚硬幣到井水 中到聽(tīng)見(jiàn)井水濺落之間的時(shí)間(請(qǐng)參見(jiàn)圖 3-6)。它也是發(fā)令員鳴槍至選手出發(fā)之間的時(shí)間。

    圖 3-6:延遲:從丟下一枚硬幣到井水中到聽(tīng)見(jiàn)井水濺落之間的時(shí)間

    就計(jì)算機(jī)上的時(shí)間測(cè)量而言,之所以會(huì)有延遲是因?yàn)閱?dòng)時(shí)鐘、運(yùn)行實(shí)驗(yàn)和停止時(shí)鐘是一 系列的操作。整個(gè)測(cè)量過(guò)程可以分解為以下五個(gè)階段。

    • (1) “啟動(dòng)時(shí)鐘”涉及調(diào)用函數(shù)從操作系統(tǒng)中獲取一個(gè)時(shí)標(biāo)計(jì)數(shù)。這個(gè)調(diào)用的時(shí)間大于 0。 在函數(shù)調(diào)用過(guò)程中,會(huì)實(shí)際地從處理器寄存器中獲取時(shí)標(biāo)計(jì)數(shù)器的值。這個(gè)值就是開(kāi)始 時(shí)間。我們稱其為間隔 t1。
    • (2) 在讀取時(shí)標(biāo)計(jì)數(shù)器的值后,它仍然必須被返回和賦值給一個(gè)變量。這些動(dòng)作也需要花費(fèi) 時(shí)間。實(shí)際的時(shí)鐘已經(jīng)在計(jì)時(shí)的過(guò)程中了,但是時(shí)標(biāo)計(jì)數(shù)值還沒(méi)有增加。我們稱其為間 隔 t2。
    • (3) 測(cè)量實(shí)驗(yàn)開(kāi)始,然后結(jié)束。我們稱其為間隔 t3。
    • (4) “停止時(shí)鐘”涉及另外一個(gè)函數(shù)調(diào)用去獲取一個(gè)時(shí)標(biāo)計(jì)數(shù)值。雖然實(shí)驗(yàn)已經(jīng)結(jié)束了,但 是在函數(shù)運(yùn)行至讀取時(shí)標(biāo)計(jì)數(shù)值的過(guò)程中,計(jì)時(shí)器仍然在計(jì)時(shí)。我們稱其為間隔 t4。
    • (5) 讀取時(shí)標(biāo)計(jì)數(shù)器的值后,它仍然必須被返回和賦值給一個(gè)變量。這時(shí),雖然時(shí)鐘仍然在 計(jì)時(shí),但是由于已經(jīng)讀取了時(shí)標(biāo)計(jì)數(shù)器的值,因此測(cè)量結(jié)果并不會(huì)繼續(xù)錯(cuò)誤地累加。我 們稱其為間隔 t5。

    因此,雖然實(shí)際上測(cè)量時(shí)間應(yīng)當(dāng)是 t3,但測(cè)量到的值卻更長(zhǎng)一些,是 t2+t3+t4。因此,延遲就 是 t2+t4。如果延遲對(duì)相對(duì)實(shí)驗(yàn)運(yùn)行時(shí)間的比例很大,實(shí)驗(yàn)員必須從實(shí)驗(yàn)結(jié)果中減去延遲。

    假設(shè)獲取一次時(shí)標(biāo)計(jì)數(shù)值的時(shí)間是 1 微秒(μs),而且時(shí)標(biāo)計(jì)數(shù)值是由當(dāng)時(shí)執(zhí)行的最后一條 指令獲得的。在以下這段偽代碼中,直到第一次函數(shù)調(diào)用的最后一條指令調(diào)用 get_tick() 才開(kāi)始測(cè)量時(shí)間,因此在測(cè)量活動(dòng)之前是沒(méi)有延遲的。在測(cè)試的最后調(diào)用 get_tick() 的延 遲則被計(jì)算到了測(cè)量結(jié)果中:

    start = get_tick() // 測(cè)量開(kāi)始之前的1μs延遲沒(méi)有影響 do_activity() stop = get_tick() // 測(cè)量開(kāi)始之后的1μs延遲被計(jì)算到測(cè)量結(jié)果中 duration = stop-start

    如果被測(cè)量的活動(dòng)的執(zhí)行時(shí)間是 1 微秒,那么測(cè)量結(jié)果就是 2 微秒,誤差達(dá)將會(huì)到 100%;而如果被測(cè)量的活動(dòng)的執(zhí)行時(shí)間是 1 毫秒,那么測(cè)量結(jié)果就是 1.001 微秒,誤差 只有 0.1%。

    如果同一個(gè)函數(shù)既在實(shí)驗(yàn)前被調(diào)用了,也在實(shí)驗(yàn)后被調(diào)用了,那么有 t1=t4 和 t2=t5。也就是 說(shuō),延遲就是計(jì)時(shí)函數(shù)的執(zhí)行時(shí)間。

    我在 Windows 上測(cè)試了計(jì)時(shí)函數(shù)的調(diào)用延遲,也就是它們的執(zhí)行時(shí)間。代碼清單 3-2 展示 了一個(gè)典型的用于計(jì)算 GetSystemTimeAsfile() 函數(shù)的時(shí)間的測(cè)試套件。

    代碼清單 3-2 Windows 計(jì)時(shí)函數(shù)的延遲

    ULONG start = GetTickCount(); LARGE_INTEGER count; for (counter_t i = 0; i < nCalls; ++i)QueryPerformanceCounter(&count); ULONG stop = GetTickCount(); std::cout << stop - start<< "ms for 100m QueryPerformanceCounter() calls"<< std::endl;

    表3-2:Windows計(jì)時(shí)函數(shù)的延遲(2013,i7,Win 8.1)

    函數(shù)時(shí)標(biāo)
    GetSystemTimeAsFileTime()2.8 納秒
    GetTickCount()3.8 納秒
    GetTickCount64()6.7 納秒
    QueryPerformanceCounter()8.0 納秒
    clock()13 納秒
    time()15 納秒
    TimeGetTime()17 納秒
    GetSystemTimePreciseAsFileTime()22 納秒

    測(cè)試結(jié)果中非常有趣的地方是,在我的 i7 平板電腦上,所有的延遲都在若干納秒的范圍 內(nèi)。所以,這些函數(shù)調(diào)用是相當(dāng)高效的。這就意味著延遲不會(huì)對(duì)在循環(huán)中連續(xù)調(diào)用函數(shù)約 1 秒的測(cè)量結(jié)果的準(zhǔn)確性造成影響。不過(guò),對(duì)于那些讀取相同的低分辨率時(shí)標(biāo)的函數(shù),這 些時(shí)間開(kāi)銷的差距仍然在 10 倍左右。GetSystemTimePreciseAsfileTime() 的延遲最高,而 這個(gè)最高的延遲相對(duì)于它的時(shí)標(biāo)占到了大約 5%。延遲問(wèn)題在慢速處理器上更嚴(yán)重。

  • 非確定性行為
  • 計(jì)算機(jī)是帶有大量?jī)?nèi)部狀態(tài)的異常復(fù)雜的裝置,其中絕大多數(shù)狀態(tài)對(duì)開(kāi)發(fā)人員是不可見(jiàn)的。 執(zhí)行函數(shù)會(huì)改變計(jì)算機(jī)的狀態(tài)(例如高速緩存中的內(nèi)容),這樣每次重復(fù)執(zhí)行指令時(shí),情況 都會(huì)與前一條指令不同。因此,內(nèi)部狀態(tài)的不可控的變化是測(cè)量中的一個(gè)隨機(jī)變化源。

    而且,操作系統(tǒng)對(duì)任務(wù)的安排也是不可預(yù)測(cè)的,這樣在測(cè)量過(guò)程中,在處理器和內(nèi)存總線上運(yùn)行的其他活動(dòng)會(huì)發(fā)生變化。這會(huì)降低測(cè)量的準(zhǔn)確性。

    操作系統(tǒng)甚至可能會(huì)暫停執(zhí)行正在被測(cè)量的代碼,將 CPU 時(shí)間分配給其他程序。但是在暫停過(guò)程中,時(shí)標(biāo)計(jì)數(shù)器仍然在計(jì)時(shí)。這會(huì)導(dǎo)致與操作系統(tǒng)沒(méi)有將 CPU 時(shí)間分配給其他 程序相比,測(cè)量出的執(zhí)行時(shí)間變大了。這是一種會(huì)對(duì)測(cè)量造成更大影響的隨機(jī)變化源。

    3.4.3 克服測(cè)量障礙

    那么,這到底有多糟糕呢?我們能讓計(jì)算機(jī)完全用于計(jì)時(shí)嗎?我們需要做什么才能實(shí)現(xiàn)計(jì) 算機(jī)計(jì)時(shí)呢?在本節(jié)中,我將對(duì)我個(gè)人使用 3.4.4 節(jié)中的 stopclass 類來(lái)為本書(shū)測(cè)試函數(shù)的 經(jīng)驗(yàn)進(jìn)行總結(jié)。

  • 別為小事煩惱
  • 好消息是測(cè)量誤差只要在幾個(gè)百分點(diǎn)以內(nèi)就足夠指引我們進(jìn)行性能優(yōu)化了。換種方式說(shuō), 如果希望從性能優(yōu)化中獲得線性改善效果,誤差只需要有兩位有效數(shù)字就可以了。即對(duì)于循環(huán)執(zhí)行某個(gè)函數(shù) 1000 毫秒的實(shí)驗(yàn)來(lái)說(shuō),大約 10 毫秒。我們將可能的誤差源整理在了表 3-3 中。

    表3-3:各變化源對(duì)在Windows上測(cè)量1秒時(shí)間的影響度

    變化源影響度
    時(shí)標(biāo)計(jì)數(shù)器函數(shù)延遲< 0.00001
    基本時(shí)鐘穩(wěn)定性< 0.01
    時(shí)標(biāo)計(jì)數(shù)器的可用分辨率< 0.1
  • 測(cè)量相對(duì)性能
  • 優(yōu)化后代碼的運(yùn)行時(shí)間與優(yōu)化前代碼的運(yùn)行時(shí)間的比率被稱為相對(duì)性能。相對(duì)性能有眾多 優(yōu)點(diǎn),其一是它們抵消了系統(tǒng)可變性,因?yàn)閮纱螠y(cè)量受到的可變性影響是一樣的。同時(shí), 相對(duì)性能是一個(gè)百分比,比“多少毫秒”這種測(cè)量結(jié)果更加直觀。

  • 通過(guò)測(cè)量模塊測(cè)試改善可重復(fù)性模塊測(cè)試
  • 模塊測(cè)試,即使用預(yù)錄入的輸入數(shù)據(jù)進(jìn)行的子系統(tǒng)測(cè)試,可以讓分析運(yùn)行或性能測(cè)量變得 具有可重復(fù)性。許多組織都有自己的模塊測(cè)試擴(kuò)展庫(kù),還可以為性能調(diào)優(yōu)加入新的測(cè)試。 對(duì)于性能調(diào)優(yōu),有一個(gè)常見(jiàn)的擔(dān)憂:“我的代碼就像一個(gè)大的毛線球,而且我沒(méi)有為其編 寫(xiě)任何測(cè)試用例。我必須在最新的輸入數(shù)據(jù)或最新的數(shù)據(jù)庫(kù)上測(cè)試它的性能,但這些數(shù)據(jù) 經(jīng)常會(huì)發(fā)生變化。我得不到一致的或者可重復(fù)的測(cè)量結(jié)果。我應(yīng)當(dāng)怎么辦呢?” 我沒(méi)有任何辦法來(lái)解決這種問(wèn)題。如果我用一組可重復(fù)的 Mock 輸入數(shù)據(jù)來(lái)測(cè)試模塊或是 子系統(tǒng),那么在這些測(cè)試中反映出來(lái)的性能改善效果通常適用于最新的測(cè)試數(shù)據(jù)。如果我 通過(guò)一次大型但不可重復(fù)的測(cè)試找到了熱點(diǎn)函數(shù),那么通過(guò)模塊測(cè)試用例去改善這些熱點(diǎn) 函數(shù),所得到的性能提升效果通常也適用于最新的測(cè)試數(shù)據(jù)。每位開(kāi)發(fā)人員都知道為什么 他們應(yīng)當(dāng)構(gòu)建由松耦合的模塊組合而成的軟件系統(tǒng);每位開(kāi)發(fā)人員都知道為什么他們應(yīng)當(dāng) 維護(hù)優(yōu)秀的測(cè)試用例庫(kù)。優(yōu)化只是應(yīng)當(dāng)這樣做的又一個(gè)理由。

  • 根據(jù)指標(biāo)優(yōu)化性能
  • 開(kāi)發(fā)人員仍然有一線希望可以基于不可預(yù)測(cè)的最新數(shù)據(jù)優(yōu)化性能。這種方式就是不測(cè)量臨 界響應(yīng)時(shí)間等值,而是收集指標(biāo)、代碼統(tǒng)計(jì)數(shù)據(jù)(例如中間值和方差),或是響應(yīng)時(shí)間的 指數(shù)平滑平均數(shù)。由于這些統(tǒng)計(jì)數(shù)字是從大量的獨(dú)立事件中得到的,因此這些數(shù)字的持續(xù) 改善表明對(duì)代碼的修改是成功的。

    以下是在根據(jù)指標(biāo)優(yōu)化性能時(shí)可能遇到的一些問(wèn)題。

    • 代碼統(tǒng)計(jì)必須基于大量事件才有效。當(dāng)執(zhí)行“修改 / 測(cè)試 / 評(píng)估”這樣的循環(huán)改善過(guò)程時(shí), 與使用固定的輸入數(shù)據(jù)進(jìn)行直接測(cè)量相比,根據(jù)指標(biāo)優(yōu)化性能更加耗費(fèi)時(shí)間。
    • 相比于分析代碼和測(cè)量運(yùn)行時(shí)間,收集指標(biāo)需要更完善的基礎(chǔ)設(shè)施。通常都需要持久化 的存儲(chǔ)設(shè)備來(lái)存放統(tǒng)計(jì)數(shù)據(jù)。而存儲(chǔ)這些數(shù)據(jù)的時(shí)間開(kāi)銷非常大,會(huì)對(duì)性能產(chǎn)生影響。 收集指標(biāo)的系統(tǒng)必須設(shè)計(jì)得足夠靈活,可以支持多種實(shí)驗(yàn)。
    • 盡管有行之有效的方法去驗(yàn)證或是推翻基于統(tǒng)計(jì)的假設(shè),但是這種方法需要開(kāi)發(fā)人員能 夠妥當(dāng)?shù)貞?yīng)對(duì)一些統(tǒng)計(jì)的復(fù)雜性。
  • 通過(guò)取多次迭代的平均值來(lái)提高準(zhǔn)確性
  • 在實(shí)驗(yàn)中通過(guò)取多次測(cè)量的平均值可以提高單次測(cè)量的準(zhǔn)確性。當(dāng)開(kāi)發(fā)人員在循環(huán)中測(cè)量函數(shù) 調(diào)用的時(shí)間,或是讓程序處理那些會(huì)讓它多次執(zhí)行某個(gè)函數(shù)的輸入數(shù)據(jù)時(shí),就是在取平均值。

    對(duì)一個(gè)函數(shù)調(diào)用進(jìn)行多次迭代測(cè)量的一個(gè)優(yōu)點(diǎn)是可以抵消隨機(jī)變化性。這種情況下,高速 緩存的狀態(tài)幾乎會(huì)聚集于一個(gè)值,讓我們可以在每次迭代測(cè)量的結(jié)果之間進(jìn)行公平合理的 比較。經(jīng)過(guò)一段足夠長(zhǎng)的時(shí)間間隔后,隨機(jī)調(diào)度程序的行為對(duì)原函數(shù)和優(yōu)化后函數(shù)的影響 是一樣的。盡管同樣的函數(shù)在另一個(gè)更大型程序中的絕對(duì)時(shí)間是不一樣的,但是通過(guò)測(cè)量 相對(duì)性能仍然能夠準(zhǔn)確地反映出性能改善的程度。 另外一個(gè)優(yōu)點(diǎn)是可以使用現(xiàn)成的但不精確的時(shí)標(biāo)計(jì)數(shù)器。現(xiàn)在,計(jì)算機(jī)的處理速度已經(jīng)足 夠在 1 秒內(nèi)處理數(shù)千次甚至數(shù)百萬(wàn)次迭代了。

  • 通過(guò)提高優(yōu)先級(jí)減少操作系統(tǒng)的非確定性行為
  • 通過(guò)提高測(cè)量進(jìn)程的優(yōu)先級(jí),可以減小操作系統(tǒng)使用 CPU 時(shí)間片段去執(zhí)行測(cè)量程序以外 的處理的幾率。在 Windows 上,可以通過(guò)調(diào)用 SetPriorityClass() 函數(shù)來(lái)設(shè)置進(jìn)程的優(yōu)先 級(jí),而 SetThreadPriority() 函數(shù)則可以用來(lái)設(shè)置線程的優(yōu)先級(jí)。下面這段代碼提高了當(dāng) 前進(jìn)程和線程的優(yōu)先級(jí):

    SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);

    在測(cè)量結(jié)束后,通常應(yīng)當(dāng)將進(jìn)程和線程恢復(fù)至正常優(yōu)先級(jí):

    SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);
  • 非確定性發(fā)生了就克服它
  • 我為性能優(yōu)化而測(cè)量性能的方式是極度非正式的。其中沒(méi)有深?yuàn)W的統(tǒng)計(jì)學(xué)知識(shí)。我的測(cè)試 只運(yùn)行幾秒鐘,而不是幾小時(shí)。但我認(rèn)為并不需要為這種非正式的方式感到愧疚。這些方 法可以將測(cè)量結(jié)果轉(zhuǎn)換為開(kāi)發(fā)人員可以理解的相對(duì)于程序整體運(yùn)行時(shí)間的性能改善結(jié)果, 因此,我知道我一定是在正確的優(yōu)化道路上前進(jìn)。

    如果我以兩種不同的方式運(yùn)行相同的測(cè)量實(shí)驗(yàn),得到的結(jié)果的差異可能在 0.1% 至 1% 之 間。這毫無(wú)疑問(wèn)與我的 PC 的初始狀態(tài)不同有關(guān)。我沒(méi)有辦法控制這些狀態(tài),因此我并不擔(dān) 心。如果差異比較大,我就會(huì)讓測(cè)試程序運(yùn)行得時(shí)間更長(zhǎng)一些。由于這也會(huì)讓我的測(cè)試 / 調(diào) 試周期變長(zhǎng),所以除非萬(wàn)不得已,否則我不會(huì)這么做。

    即使我發(fā)現(xiàn)兩次測(cè)量結(jié)果之間的差異達(dá)到了幾個(gè)百分點(diǎn),在單次測(cè)量中測(cè)量結(jié)果的相對(duì)變 化看起來(lái)仍然小于 1%。也就是說(shuō),通過(guò)在相同的測(cè)試中測(cè)量一個(gè)函數(shù)的兩種變化,我甚至 能看出發(fā)生了相當(dāng)微妙的變化。

    我會(huì)盡量在一臺(tái)沒(méi)有播放視頻、升級(jí) Java 或是壓縮大文件的“安靜”的計(jì)算機(jī)上測(cè)量時(shí) 間。在測(cè)量過(guò)程中,我也會(huì)盡量不移動(dòng)鼠標(biāo)或是切換窗口。特別是當(dāng) PC 中只有一個(gè)處理 器時(shí),這一點(diǎn)非常重要。但是當(dāng)使用現(xiàn)代多核處理器時(shí),我發(fā)現(xiàn)即使我忘記了上面這些注 意事項(xiàng),測(cè)量結(jié)果也不會(huì)有什么大的變化。

    如果在測(cè)量時(shí)間時(shí)調(diào)用了某個(gè)函數(shù) 10 000 次,這段代碼和相關(guān)的數(shù)據(jù)會(huì)被存儲(chǔ)在高速緩存 中。當(dāng)為一個(gè)實(shí)時(shí)系統(tǒng)測(cè)量最差情況下的絕對(duì)時(shí)間時(shí),這會(huì)有影響。但是現(xiàn)在我是在一個(gè) 內(nèi)核本身就充滿了非確定性的系統(tǒng)上測(cè)量相對(duì)時(shí)間。而且,我所測(cè)試的函數(shù)是我的分析器 指出的熱點(diǎn)函數(shù)。因此,即使是當(dāng)正式版本在運(yùn)行時(shí),它們也會(huì)被緩存于高速緩存中。這樣,迭代測(cè)試就確確實(shí)實(shí)地重現(xiàn)了真實(shí)運(yùn)行狀態(tài)。

    如果修改后的函數(shù)看起來(lái)快了 1%,那么通常不值得進(jìn)行修改。根據(jù)阿姆達(dá)爾定律,該函數(shù)優(yōu)化結(jié)果對(duì)整個(gè)程序的運(yùn)行時(shí)間的貢獻(xiàn)會(huì)變得微不足道。速度提高 10% 是臨界值,而速 度提高 100% 則對(duì)縮短整個(gè)程序的運(yùn)行時(shí)間有非常大的幫助。只進(jìn)行有明顯效果的性能改 善可以將開(kāi)發(fā)人員從對(duì)方法論的擔(dān)憂中解放出來(lái)。

    3.4.4 創(chuàng)建stopwatch類

    我會(huì)用一個(gè) stopwatch 類來(lái)測(cè)量程序中部分代碼的執(zhí)行時(shí)間并分析代碼。這個(gè)類的工作 方式非常像一塊機(jī)械秒表。初始化秒表或是調(diào)用它的 start() 成員函數(shù)后,秒表將開(kāi)始 計(jì)時(shí);調(diào)用秒表的 stop() 成員函數(shù)或是銷毀秒表類的實(shí)例后,秒表將停止計(jì)時(shí)并顯示出計(jì)時(shí)結(jié)果。

    編寫(xiě)一個(gè) stopwatch 類并不難,網(wǎng)上也有很多現(xiàn)成的代碼。代碼清單 3-3 展示了我所使用 的 stopwatch 類。

    代碼清單 3-3 stopwatch 類

    template <typename T> class basic_stopwatch : T {typedef typename T BaseTimer; public:// 創(chuàng)建一個(gè)秒表,開(kāi)始計(jì)時(shí)一項(xiàng)程序活動(dòng)(可選)explicit basic_stopwatch(bool start);explicit basic_stopwatch(char const* activity = "Stopwatch",bool start=true);basic_stopwatch(std::ostream& log,char const* activity="Stopwatch",bool start=true);// 停止并銷毀秒表~basic_stopwatch();// 得到上一次計(jì)時(shí)時(shí)間(上一次停止時(shí)的時(shí)間)unsigned LapGet() const;// 判斷:如果秒表正在運(yùn)行,則返回truebool IsStarted() const;// 顯示累計(jì)時(shí)間,一直運(yùn)行,設(shè)置/返回上次計(jì)時(shí)時(shí)間unsigned Show(char const* event="show");// 啟動(dòng)(重啟)秒表,設(shè)置/返回上次計(jì)時(shí)時(shí)間unsigned Start(char const* event_namee="start");// 停止正在計(jì)時(shí)的秒表,設(shè)置/返回上次計(jì)時(shí)時(shí)間unsigned Stop(char const* event_name="stop"); private: // 成員變量char const* m_activity; // "activity"字符串unsigned m_lap; // 上次計(jì)時(shí)時(shí)間(上一次停止時(shí)的時(shí)間)std::ostream& m_log; // 用于記錄事件的流 };

    這段代碼只是重新定義了類。為了使性能最優(yōu)化,成員函數(shù)將會(huì)被內(nèi)聯(lián)展開(kāi)。

    stopwatch 的類型模板參數(shù) T 的值的類是一個(gè)更加簡(jiǎn)單的計(jì)時(shí)器,它提供了依賴于操作系 統(tǒng)和 C++ 標(biāo)準(zhǔn)的函數(shù)去訪問(wèn)時(shí)標(biāo)計(jì)數(shù)器。我編寫(xiě)過(guò)多個(gè)版本的 TimerBase 類,去測(cè)試各 種不同的時(shí)標(biāo)計(jì)數(shù)器的實(shí)現(xiàn)方式。在有些現(xiàn)代 C++ 處理器上,T 的值的類可以使用 C++ 庫(kù),或是可以直接從操作系統(tǒng)中得到時(shí)標(biāo)。 代碼清單 3-4 展示的 TimerBase 類使 用了在 C++11 及之后的版本中提供的 C++ 庫(kù)。

    代碼清單 3-4 使用了 的 TimerBase 類

    # include <chrono> using namespace std::chrono; class TimerBase { public:// 清除計(jì)時(shí)器TimerBase() : m_start(system_clock::time_point::min()) { }// 清除計(jì)時(shí)器void Clear() {m_start = system_clock::time_point::min();}// 如果計(jì)時(shí)器正在計(jì)時(shí),則返回truebool IsStarted() const {return (m_start.time_since_epoch() != system_clock::duration(0));}// 啟動(dòng)計(jì)時(shí)器void Start() {m_start = system_clock::now();}// 得到自計(jì)時(shí)開(kāi)始后的毫秒值unsigned long GetMs() {if (IsStarted()) {system_clock::duration diff;diff = system_clock::now() - m_start;return (unsigned)(duration_cast<milliseconds>(diff).count());}return 0;}private:system_clock::time_point m_start; };

    這種實(shí)現(xiàn)方式的優(yōu)點(diǎn)是在不同操作系統(tǒng)之間具有可移植性,但是它需要用到 C++11。

    代碼清單 3-5 中的 TimerBase 類與這個(gè)類的功能相同,不過(guò)其中使用的是在 Windows 上和 Linux 上都可以使用的 clock() 函數(shù)。

    代碼清單 3-5 使用了 clock() 的 TimerBase 類

    class TimerBaseClock { public: 46 | 第 3 章// 清除計(jì)時(shí)器TimerBaseClock() { m_start = -1; }// 清除計(jì)時(shí)器void Clear() { m_start = -1; }// 如果計(jì)時(shí)器正在計(jì)時(shí),則返回truebool IsStarted() const { return (m_start != -1); }// 啟動(dòng)計(jì)時(shí)器void Start() { m_start = clock(); }// 得到自計(jì)時(shí)開(kāi)始后的毫秒值unsigned long GetMs() {clock_t now;if (IsStarted()) {now = clock();clock_t dt = (now - m_start);return (unsigned long)(dt * 1000 / CLOCKS_PER_SEC);}return 0;} private:clock_t m_start; };

    這種實(shí)現(xiàn)方式的優(yōu)點(diǎn)是在不同 C++ 版本和不同操作系統(tǒng)之間具有可移植性,缺點(diǎn)是在 Linux 上和 Windows 上,clock() 函數(shù)的測(cè)量結(jié)果略有不同。

    代碼清單 3-6 中的 TimerBase 類可以工作于舊版本的 Windows 上和 Linux 上。如果是在 Windows 上,那么還必須顯式地提供 gettimeofday() 函數(shù),因?yàn)樗炔粚儆?Windows API,也不屬于 C 標(biāo)準(zhǔn)庫(kù)。

    代碼清單 3-6 使用了 gettimeofday() 的 TimerBase

    # include <chrono> using namespace std::chrono; class TimerBaseChrono { public:// 清除計(jì)時(shí)器TimerBaseChrono() :m_start(system_clock::time_point::min()) {}// 清除計(jì)時(shí)器void Clear() {m_start = system_clock::time_point::min();}// 如果計(jì)時(shí)器正在計(jì)時(shí),則返回truebool IsStarted() const {return (m_start != system_clock::time_point::min());}// 啟動(dòng)計(jì)時(shí)器void Start() {m_start = std::chrono::system_clock::now();}// 得到自計(jì)時(shí)開(kāi)始后的毫秒值unsigned long GetMs() {if (IsStarted()) {system_clock::duration diff;diff = system_clock::now() - m_start;return (unsigned)(duration_cast<milliseconds>(diff).count());}return 0;} private:std::chrono::system_clock::time_point m_start; }; 這種實(shí)現(xiàn)方式在不同的 C++ 版本和不同操作系統(tǒng)之間具有可移植性。但當(dāng)運(yùn)行于 Windows 上時(shí),需要實(shí)現(xiàn) `gettimeofday()` 函數(shù)。

    stopwatch 類的最簡(jiǎn)單的用法用到了 RAII(Resource Acquisition Is Initialization,資源獲取 就是初始化 5 )慣用法。程序會(huì)在由大括號(hào)包圍的語(yǔ)句塊中的開(kāi)頭處初始化 stopwatch 類, stopwatch 類的默認(rèn)操作是開(kāi)始計(jì)時(shí)。當(dāng) stopwatch 在語(yǔ)句塊結(jié)束前被析構(gòu)時(shí),它會(huì)輸出最 終計(jì)時(shí)結(jié)果。程序可以在執(zhí)行過(guò)程中通過(guò)調(diào)用 stopwatch 類的 show() 成員函數(shù)輸出中間計(jì) 時(shí)結(jié)果。這樣,開(kāi)發(fā)人員就可以只使用一個(gè)計(jì)時(shí)器來(lái)分析幾個(gè)互相聯(lián)系的代碼塊。例如:

    {Stopwatch sw("activity");DoActivity(); }

    這段代碼將會(huì)在標(biāo)準(zhǔn)輸出中打印出以下結(jié)果 :

    activity: start activity: stop 1234mS

    stopwatch 在運(yùn)行時(shí)不會(huì)產(chǎn)生間接開(kāi)銷。開(kāi)始計(jì)時(shí)和停止計(jì)時(shí)的延遲包括了獲取當(dāng)前時(shí)間 的系統(tǒng)調(diào)用的開(kāi)銷,如果調(diào)用了 show() 成員函數(shù)輸出計(jì)時(shí)結(jié)果,那么還要加上產(chǎn)生輸出的 開(kāi)銷。如果是測(cè)試那些需要花費(fèi)數(shù)十毫秒或者更長(zhǎng)時(shí)間的任務(wù),那么這個(gè)延遲可以忽略。 但是如果開(kāi)發(fā)人員試圖測(cè)試微秒級(jí)別的活動(dòng)的時(shí)間,那么間接開(kāi)銷的比重將會(huì)顯著增大, 測(cè)量結(jié)果的準(zhǔn)確度也因此會(huì)降低。

    測(cè)量運(yùn)行時(shí)間的最大缺點(diǎn)可能是需要直覺(jué)和經(jīng)驗(yàn)去解釋這些結(jié)果。在通過(guò)多次測(cè)量縮小了 查找熱點(diǎn)代碼的范圍后,開(kāi)發(fā)人員必須接著檢查代碼或者進(jìn)行實(shí)驗(yàn)找出和移除熱點(diǎn)代碼。 檢查代碼時(shí)需要依靠開(kāi)發(fā)人員自己的經(jīng)驗(yàn)或是本書(shū)中概述的啟發(fā)式規(guī)則。這些規(guī)則的優(yōu)點(diǎn) 是可以幫助你找出那些長(zhǎng)時(shí)間運(yùn)行的代碼,缺點(diǎn)則是無(wú)法明確地指出最熱點(diǎn)的代碼。

    3.4.5 使用測(cè)試套件測(cè)量熱點(diǎn)函數(shù)

    一旦通過(guò)分析器或是運(yùn)行時(shí)分析找出了一個(gè)候選的待優(yōu)化函數(shù),一種簡(jiǎn)單的改善它的方法 是構(gòu)建一個(gè)測(cè)試套件,在其中多次調(diào)用該函數(shù)。這樣可以將該函數(shù)的運(yùn)行時(shí)間增大為一 個(gè)可測(cè)量的值,同時(shí)還可以抵消因后臺(tái)任務(wù)、上下文切換等對(duì)運(yùn)行時(shí)間造成的影響。采 用“修改 - 編譯 - 運(yùn)行”的迭代方式去獨(dú)立地測(cè)量一個(gè)函數(shù),會(huì)比采用“修改 - 編譯 - 運(yùn) 行”,然后運(yùn)行分析器并解析它的輸出更快。本書(shū)中的許多例子都會(huì)使用這種技巧。 這個(gè)計(jì)時(shí)測(cè)試套件(代碼清單 3-7)只是先調(diào)用了 stopwatch,然后循環(huán)中調(diào)用了 10000 次 需要被測(cè)量的函數(shù)。

    代碼清單 3-7 計(jì)時(shí)測(cè)試套件

    typedef unsigned counter_t; counter_t const iterations = 10000;... {Stopwatch sw("function_to_be_timed()");for (counter_t i = 0; i < iterations; ++i) {result = function_to_be_timed();} }

    迭代次數(shù)需要憑經(jīng)驗(yàn)估計(jì)。如果 stopwatch 使用的時(shí)標(biāo)計(jì)數(shù)器的有效分辨率是大約 10 毫 秒,那么測(cè)試套件在桌面處理器上的運(yùn)行時(shí)間應(yīng)當(dāng)在幾百到幾千毫秒。

    這里,我使用了 counter_t 來(lái)替代 unsigned 或 unsigned long,這是因?yàn)閷?duì)于一些非常短 小的函數(shù),該變量的類型可能需要是 64 位 unsigned long long。相比于回過(guò)頭來(lái)重新修改 所有類型名稱,我更習(xí)慣于使用 typedef。這是對(duì)優(yōu)化過(guò)程自身的一種優(yōu)化。

    最外層的一組大括號(hào)非常重要,因?yàn)樗x了 sw(也就是 Stopwatch 類的實(shí)例)的存在范 圍。由于 stopwatch 使用了 RAII 慣用法,sw 的構(gòu)造函數(shù)會(huì)得到第一次時(shí)標(biāo)計(jì)數(shù)值,而它 的析構(gòu)函數(shù)則會(huì)得到最后一次時(shí)標(biāo)計(jì)數(shù)值并將結(jié)果放入到標(biāo)準(zhǔn)輸出流中。

    3.5 評(píng)估代碼開(kāi)銷來(lái)找出熱點(diǎn)代碼

    經(jīng)驗(yàn)告訴我分析代碼和測(cè)量運(yùn)行時(shí)間是幫助找出需要優(yōu)化的代碼的兩種有效方法。分析器 會(huì)指出某個(gè)函數(shù)被頻繁地調(diào)用了或是在程序總運(yùn)行時(shí)間中所占的比率很大。但它不太可能 指出某個(gè)具體的 C++ 語(yǔ)句可以優(yōu)化。分析代碼的成本也可能是非常高的。測(cè)量時(shí)間也可能會(huì)表明一大段代碼很 慢,但不會(huì)指出其中存在的具體問(wèn)題。

    開(kāi)發(fā)人員下一步需要做的是,對(duì)指出的代碼塊中的每條語(yǔ)句的開(kāi)銷進(jìn)行評(píng)估。這一步就像 是證明一條定理一樣,并不需要太精確。大多數(shù)情況下,只需大致觀察一下這些語(yǔ)句就能得到它們的開(kāi)銷,然后從中找出性能開(kāi)銷大的語(yǔ)句和語(yǔ)法結(jié)構(gòu)。

    3.5.1 評(píng)估獨(dú)立的C++語(yǔ)句的開(kāi)銷

    正如 2.2.1 節(jié)中所講述的,訪問(wèn)內(nèi)存的時(shí)間開(kāi)銷遠(yuǎn)比執(zhí)行其他指令的開(kāi)銷大。在烤箱和咖 啡機(jī)所使用的簡(jiǎn)單微處理器中,執(zhí)行一條指令所花費(fèi)的時(shí)間大致包含從內(nèi)存中讀取指令的 每個(gè)字節(jié)所需要的時(shí)間,加上讀取指令的輸入數(shù)據(jù)所需的時(shí)間,再加上寫(xiě)指令結(jié)果的時(shí) 間。相比之下,隱藏于內(nèi)存訪問(wèn)時(shí)間之下的解碼和執(zhí)行指令的時(shí)間就顯得微不足道了。

    在桌面級(jí)微處理器上,情況就更加復(fù)雜了。許多處于不同階段的指令會(huì)被同時(shí)執(zhí)行。讀取 指令流的開(kāi)銷可以忽略。不過(guò),訪問(wèn)指令所操作的數(shù)據(jù)的開(kāi)銷則無(wú)法忽略。正是由于這個(gè) 原因,讀寫(xiě)數(shù)據(jù)的開(kāi)銷可以近似地看作所有級(jí)別的微處理器上的執(zhí)行指令的相對(duì)開(kāi)銷。

    有一條有效的規(guī)則能夠幫助我們?cè)u(píng)估一條 C++ 語(yǔ)句的開(kāi)銷有多大,那就是計(jì)算該語(yǔ)句對(duì) 內(nèi)存的讀寫(xiě)次數(shù)。例如,有一條語(yǔ)句 a = b + c;,其中 a、b 和 c 都是整數(shù),b 和 c 的值 必須從內(nèi)存中讀取,而且它們的和必須寫(xiě)入至內(nèi)存中的位置 a。因此,這條語(yǔ)句的開(kāi)銷是 三次內(nèi)存訪問(wèn)。這個(gè)次數(shù)不依賴于微處理器的指令集。這是語(yǔ)句不可避免的、必然會(huì)發(fā)生 的開(kāi)銷。

    再比如,r = *p + a[i]; 這條語(yǔ)句訪問(wèn)內(nèi)存的次數(shù)如下:一次訪問(wèn)用于讀取 i,一次讀取 a[i],一次讀取 p,一次讀取 *p 所指向的數(shù)據(jù),一次將結(jié)果寫(xiě)入至 r。也就是說(shuō) , 總共進(jìn) 行了 5 次訪問(wèn)。7.2.1 節(jié)中講解了函數(shù)調(diào)用訪問(wèn)內(nèi)存的開(kāi)銷。

    理解這是一條啟發(fā)式規(guī)則是非常重要的。在實(shí)際的硬件中,獲取執(zhí)行語(yǔ)句的指令會(huì)發(fā)生額 外的內(nèi)存訪問(wèn)。不過(guò),由于這些訪問(wèn)是順序的,所以它們可能非常高效。而且這些額外的 開(kāi)銷與訪問(wèn)數(shù)據(jù)的開(kāi)銷是成比例的。編譯器可能會(huì)在優(yōu)化時(shí)通過(guò)復(fù)用之前的計(jì)算或是發(fā)揮 代碼靜態(tài)分析的優(yōu)勢(shì)來(lái)省略一些內(nèi)存訪問(wèn)。單位時(shí)間內(nèi)的開(kāi)銷也取決于 C++ 語(yǔ)句要訪問(wèn)的 內(nèi)容是否在高速緩存中。

    但是其他因素是等價(jià)的,有影響的是訪問(wèn)語(yǔ)句要用到的數(shù)據(jù)需要多少次讀寫(xiě)內(nèi)存。這條啟 發(fā)式規(guī)則并不完美,但這是所有你能做的了,除非你想去查看編譯器輸出的冗長(zhǎng)無(wú)味、收 效甚微的匯編代碼。

    3.5.2 評(píng)估循環(huán)的開(kāi)銷

    由于每條 C++ 語(yǔ)句都只會(huì)進(jìn)行幾次內(nèi)存訪問(wèn),通常情況下熱點(diǎn)代碼都不會(huì)是一條單獨(dú)的 語(yǔ)句,除非受其他因素的作用,讓其頻繁地執(zhí)行。這些因素之一就是該語(yǔ)句出現(xiàn)在了循環(huán) 中。這樣,合計(jì)開(kāi)銷就是該語(yǔ)句的開(kāi)銷乘以該語(yǔ)句被執(zhí)行的次數(shù)了。

    如果你很幸運(yùn),可能會(huì)偶然找到這樣的代碼。分析器可能會(huì)指出一條單獨(dú)的語(yǔ)句被執(zhí)行了 100 萬(wàn)次,或者其他的熱點(diǎn)函數(shù)包含以下這樣的循環(huán):

    for (int i=1; i<1000000; ++i) {do_something_expensive();if (mostly_true) {do_more_stuff();even_more();} }

    這個(gè)循環(huán)中的語(yǔ)句很明顯會(huì)被執(zhí)行 100 萬(wàn)次,因此它是熱點(diǎn)語(yǔ)句??雌饋?lái)你需要花點(diǎn)精力 去優(yōu)化。

  • 評(píng)估嵌套循環(huán)中的循環(huán)次數(shù)
  • 當(dāng)一個(gè)循環(huán)被嵌套在另一個(gè)循環(huán)里面的時(shí)候,代碼塊的循環(huán)次數(shù)是內(nèi)層循環(huán)的次數(shù)乘以外 層循環(huán)的次數(shù)。例如:

    for (int i=0; i<100; ++i) {for (int j=0; j<50; ++j) {fiddle(a[i][j]);} }

    在這里,代碼塊的循環(huán)次數(shù)是 100*50=5000。

    上面的代碼塊非常直接。但是實(shí)際上這里可能有無(wú)數(shù)種變化。例如,當(dāng)進(jìn)行數(shù)學(xué)運(yùn)算時(shí), 在有些重要的情況下會(huì)對(duì)三角矩陣進(jìn)行循環(huán)計(jì)算。而且有時(shí)候,代碼編寫(xiě)得非常糟糕,需要花費(fèi)很大氣力才能看清嵌套循環(huán)的輪廓。

    嵌套循環(huán)可能并非一眼就能看出來(lái)。如果一個(gè)循環(huán)調(diào)用了一個(gè)函數(shù),而這個(gè)函數(shù)中又包含 了另外一個(gè)循環(huán),那么內(nèi)層循環(huán)就是嵌套循環(huán)。正如我們稍后會(huì)在 7.1.8 節(jié)中看到的,有 時(shí)在外層循環(huán)中重復(fù)地調(diào)用函數(shù)的開(kāi)銷也是可以消除的。

    內(nèi)存循環(huán)可能被嵌入在標(biāo)準(zhǔn)庫(kù)函數(shù)中,特別是處理字符串或字符的 I/O 函數(shù)。如果這些 函數(shù)被重復(fù)調(diào)用的次數(shù)非常多,那么可能值得去重新實(shí)現(xiàn)標(biāo)準(zhǔn)函數(shù)庫(kù)中的函數(shù)來(lái)回避調(diào) 用開(kāi)銷。

  • 評(píng)估循環(huán)次數(shù)為變量的循環(huán)的開(kāi)銷
  • 不是所有循環(huán)中的循環(huán)次數(shù)都是很明確的。許多循環(huán)處理會(huì)不斷重復(fù)直至滿足某個(gè)條件為 止,比如有些循環(huán)會(huì)重復(fù)地處理字符,直至找到空格為止;還有些循環(huán)則會(huì)重復(fù)地處理數(shù) 字,直到遇到非數(shù)字為止。這種循環(huán)的重復(fù)次數(shù)也是可以估算出來(lái)的。當(dāng)然,只需要大致 地估算一下即可,例如每個(gè)數(shù)字的平均位數(shù)是 5,或是每個(gè)單詞的平均字母數(shù)是 6。估算 的目的是找出可能需要優(yōu)化的代碼。

  • 識(shí)別出隱式循環(huán)
  • 響應(yīng)事件的程序(例如 Windows UI 程序)在最外層都會(huì)有一個(gè)隱式循環(huán)。這個(gè)循環(huán)甚至 在程序中是看不到的,因?yàn)樗浑[藏在了框架中。如果這個(gè)框架以最大速率接收事件的 話,那么每當(dāng)事件處理器取得程序控制權(quán),或是在事件分發(fā)前,抑或是在事件分發(fā)過(guò)程中都會(huì)被執(zhí)行的代碼,以及最頻繁地被分發(fā)的事件中的代碼都可能是熱點(diǎn)代碼。

  • 識(shí)別假循環(huán)
  • 不是所有的 while 或者 do 語(yǔ)句都是循環(huán)語(yǔ)句。我就曾經(jīng)遇到過(guò)使用 do 語(yǔ)句幫助控制流程 的代碼。下面這段示例代碼還有更好的實(shí)現(xiàn)方式,不過(guò)使用了更復(fù)雜的 if-then-else 邏 輯的話,這種慣用法就有其用武之地了。下面這個(gè)“循環(huán)”只會(huì)被執(zhí)行一次。當(dāng)它遇到 while(0) 后就會(huì)退出:

    do {if (!operation1())break;if (!operation2(x,y,z))break; } while(0);

    這種慣用法也時(shí)常被用于將幾條語(yǔ)句“打包”為 C 風(fēng)格的宏。

    3.6 其他找出熱點(diǎn)代碼的方法

    如果開(kāi)發(fā)人員熟悉需要優(yōu)化的代碼,可以選擇僅憑直覺(jué)去推測(cè)影響程序整體運(yùn)行時(shí)間的代 碼塊在哪里,然后做實(shí)驗(yàn)去驗(yàn)證對(duì)這些代碼的修改是否可以提高程序整體性能。 我不建議選擇這種方法,除非整個(gè)項(xiàng)目只有你一個(gè)人。通過(guò)使用分析器或是計(jì)時(shí)器分析代 碼,開(kāi)發(fā)人員可以向同事和經(jīng)理展示他們?cè)谛阅軆?yōu)化工作中取得的進(jìn)展。如果你僅憑直覺(jué) 進(jìn)行優(yōu)化,也不發(fā)表結(jié)果,有時(shí)甚至即使你發(fā)表了結(jié)果,團(tuán)隊(duì)成員也會(huì)質(zhì)疑你的方法,使 你無(wú)法專心于你的工作。他們也應(yīng)該如此。這是因?yàn)樗麄兎植磺迥愕降资窃谑怯媚愀叨葘?業(yè)的直覺(jué)進(jìn)行優(yōu)化還是只是在碰運(yùn)氣。

    總結(jié)

    以上是生活随笔為你收集整理的C++ 性能优化篇三《测量性能》的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

    欧洲欧美人成视频在线 | 亚洲中文字幕成人无码 | 国产精品亚洲五月天高清 | 亚洲无人区一区二区三区 | 国产无套内射久久久国产 | 亚洲精品国产a久久久久久 | 日本www一道久久久免费榴莲 | 男女下面进入的视频免费午夜 | а√天堂www在线天堂小说 | 精品 日韩 国产 欧美 视频 | 国内老熟妇对白xxxxhd | 99久久精品国产一区二区蜜芽 | 久久久久久a亚洲欧洲av冫 | 免费看男女做好爽好硬视频 | 少妇厨房愉情理9仑片视频 | 亚洲熟妇自偷自拍另类 | 亚洲精品成a人在线观看 | 日韩人妻系列无码专区 | 成熟女人特级毛片www免费 | 国产绳艺sm调教室论坛 | 国产精品久久精品三级 | 欧美亚洲国产一区二区三区 | 婷婷五月综合激情中文字幕 | 亚洲国产欧美日韩精品一区二区三区 | 日韩人妻无码一区二区三区久久99 | 国产suv精品一区二区五 | 兔费看少妇性l交大片免费 | 久久国产36精品色熟妇 | 色噜噜亚洲男人的天堂 | 色婷婷欧美在线播放内射 | 久久国产精品_国产精品 | 俺去俺来也在线www色官网 | 国内精品久久毛片一区二区 | 无码精品国产va在线观看dvd | 131美女爱做视频 | 一个人看的www免费视频在线观看 | 免费无码的av片在线观看 | 亚洲中文字幕久久无码 | 国产真实夫妇视频 | 扒开双腿吃奶呻吟做受视频 | 亚洲码国产精品高潮在线 | 性开放的女人aaa片 | 精品人妻人人做人人爽夜夜爽 | 亚洲国产成人a精品不卡在线 | 国产一区二区三区四区五区加勒比 | 伊人久久大香线蕉亚洲 | 少妇无码av无码专区在线观看 | 久久午夜无码鲁丝片 | 人妻无码久久精品人妻 | 免费男性肉肉影院 | 日日天日日夜日日摸 | 亚洲国产午夜精品理论片 | 国产人妻精品一区二区三区 | 国产成人精品一区二区在线小狼 | 中文字幕av日韩精品一区二区 | 欧美大屁股xxxxhd黑色 | 99视频精品全部免费免费观看 | 欧美zoozzooz性欧美 | 亚洲熟女一区二区三区 | 97久久精品无码一区二区 | 中文字幕 人妻熟女 | 18精品久久久无码午夜福利 | 亚洲国产综合无码一区 | 男人的天堂av网站 | 一本久道久久综合婷婷五月 | 亚洲人交乣女bbw | 日本一卡二卡不卡视频查询 | 国内精品一区二区三区不卡 | 狂野欧美性猛交免费视频 | 国产综合在线观看 | 国产特级毛片aaaaaaa高清 | 影音先锋中文字幕无码 | 久久午夜无码鲁丝片午夜精品 | 乌克兰少妇xxxx做受 | 亚洲午夜无码久久 | 高潮毛片无遮挡高清免费 | 亚洲日本在线电影 | 正在播放东北夫妻内射 | 大胆欧美熟妇xx | 亚洲欧美色中文字幕在线 | 日本肉体xxxx裸交 | 国产suv精品一区二区五 | 日日摸夜夜摸狠狠摸婷婷 | 最新版天堂资源中文官网 | 三级4级全黄60分钟 | 无码人妻精品一区二区三区不卡 | 日韩成人一区二区三区在线观看 | 99视频精品全部免费免费观看 | 国产人妻大战黑人第1集 | 欧美丰满少妇xxxx性 | 99麻豆久久久国产精品免费 | 无码国产乱人伦偷精品视频 | 国产精品99久久精品爆乳 | 国产尤物精品视频 | 亚洲第一无码av无码专区 | 天天做天天爱天天爽综合网 | 人妻少妇精品视频专区 | 午夜成人1000部免费视频 | 国产色精品久久人妻 | 欧美喷潮久久久xxxxx | 亚洲综合无码久久精品综合 | 国内精品人妻无码久久久影院 | 亚洲日本在线电影 | 亚洲成色在线综合网站 | 色欲综合久久中文字幕网 | 特大黑人娇小亚洲女 | 国产精品成人av在线观看 | 曰韩少妇内射免费播放 | 麻豆国产人妻欲求不满谁演的 | 国产精品无码成人午夜电影 | 中文字幕人妻丝袜二区 | 香蕉久久久久久av成人 | 国产av久久久久精东av | 国产免费久久久久久无码 | 日本熟妇乱子伦xxxx | 亚洲中文字幕va福利 | 一区二区三区高清视频一 | 亚洲va中文字幕无码久久不卡 | 成 人 免费观看网站 | 日韩精品成人一区二区三区 | 无码av岛国片在线播放 | 又紧又大又爽精品一区二区 | 久久久久久国产精品无码下载 | 97精品人妻一区二区三区香蕉 | 亚洲中文字幕成人无码 | 国内丰满熟女出轨videos | 娇妻被黑人粗大高潮白浆 | 久久天天躁夜夜躁狠狠 | 亚洲国产精品久久久天堂 | 久久精品中文闷骚内射 | 无码纯肉视频在线观看 | 国产午夜无码精品免费看 | 少妇厨房愉情理9仑片视频 | 强奷人妻日本中文字幕 | 久久99精品久久久久久动态图 | 中文字幕乱码人妻二区三区 | 永久免费精品精品永久-夜色 | 18禁黄网站男男禁片免费观看 | 99riav国产精品视频 | 欧美老熟妇乱xxxxx | 成人性做爰aaa片免费看不忠 | 国产福利视频一区二区 | 欧美精品免费观看二区 | 日本肉体xxxx裸交 | 成年女人永久免费看片 | 久久久久久久人妻无码中文字幕爆 | 亚洲精品一区二区三区在线 | yw尤物av无码国产在线观看 | 性欧美牲交xxxxx视频 | 在线а√天堂中文官网 | 国产精品久久久午夜夜伦鲁鲁 | 欧洲精品码一区二区三区免费看 | 欧美日韩在线亚洲综合国产人 | 搡女人真爽免费视频大全 | 中文字幕乱妇无码av在线 | 男女猛烈xx00免费视频试看 | 中文字幕无码免费久久9一区9 | 极品尤物被啪到呻吟喷水 | 亚洲一区二区三区含羞草 | 色综合久久网 | 成人无码影片精品久久久 | 夜先锋av资源网站 | 牲交欧美兽交欧美 | 久久视频在线观看精品 | 无码人妻精品一区二区三区下载 | 亚洲男人av香蕉爽爽爽爽 | 久久无码中文字幕免费影院蜜桃 | 亚洲中文字幕乱码av波多ji | 亚洲精品欧美二区三区中文字幕 | 国产av人人夜夜澡人人爽麻豆 | 久久久久成人精品免费播放动漫 | 国产精品嫩草久久久久 | 精品人人妻人人澡人人爽人人 | 国产人妻人伦精品1国产丝袜 | 无码人妻av免费一区二区三区 | 精品国产成人一区二区三区 | 国产乡下妇女做爰 | 亚洲va中文字幕无码久久不卡 | 3d动漫精品啪啪一区二区中 | 四虎国产精品免费久久 | 欧美性猛交内射兽交老熟妇 | av香港经典三级级 在线 | 日本爽爽爽爽爽爽在线观看免 | 久久亚洲中文字幕精品一区 | 色婷婷欧美在线播放内射 | 国产婷婷色一区二区三区在线 | 国产激情一区二区三区 | 国产免费久久久久久无码 | 丁香花在线影院观看在线播放 | 免费观看黄网站 | 国产一区二区不卡老阿姨 | 国产性生大片免费观看性 | 国产色精品久久人妻 | 亚洲呦女专区 | 国产一区二区三区日韩精品 | 久久99精品久久久久久动态图 | 熟妇人妻激情偷爽文 | 国产色精品久久人妻 | 日韩精品无码免费一区二区三区 | 女人和拘做爰正片视频 | 国产色视频一区二区三区 | 免费无码的av片在线观看 | 99视频精品全部免费免费观看 | 久久熟妇人妻午夜寂寞影院 | 一个人看的www免费视频在线观看 | 欧美一区二区三区视频在线观看 | 狠狠躁日日躁夜夜躁2020 | 中文字幕+乱码+中文字幕一区 | 东北女人啪啪对白 | 精品国产成人一区二区三区 | 国产午夜无码精品免费看 | 欧美 亚洲 国产 另类 | av在线亚洲欧洲日产一区二区 | 午夜嘿嘿嘿影院 | 又大又硬又黄的免费视频 | 乱人伦人妻中文字幕无码 | 亚洲色大成网站www国产 | 欧美激情综合亚洲一二区 | 亚洲国产精品成人久久蜜臀 | 欧洲欧美人成视频在线 | 亚无码乱人伦一区二区 | 97夜夜澡人人爽人人喊中国片 | 国产成人精品一区二区在线小狼 | 亚洲欧洲日本综合aⅴ在线 | 一本色道久久综合狠狠躁 | 久久精品人人做人人综合试看 | 无码av中文字幕免费放 | 国产97在线 | 亚洲 | 色综合天天综合狠狠爱 | 又紧又大又爽精品一区二区 | √天堂资源地址中文在线 | 国内精品一区二区三区不卡 | 樱花草在线播放免费中文 | 亚洲人成网站免费播放 | 日韩精品无码一本二本三本色 | 男人的天堂av网站 | 日韩精品无码一本二本三本色 | 欧美精品国产综合久久 | 亚无码乱人伦一区二区 | 无码av中文字幕免费放 | 波多野结衣高清一区二区三区 | 国产精品国产三级国产专播 | 国产成人无码av一区二区 | 色综合久久网 | 久久午夜无码鲁丝片秋霞 | 强伦人妻一区二区三区视频18 | 国产精品美女久久久久av爽李琼 | 奇米综合四色77777久久 东京无码熟妇人妻av在线网址 | 我要看www免费看插插视频 | 乱中年女人伦av三区 | 丰腴饱满的极品熟妇 | yw尤物av无码国产在线观看 | 国产精品福利视频导航 | 亚洲区欧美区综合区自拍区 | 学生妹亚洲一区二区 | 国产精品二区一区二区aⅴ污介绍 | av在线亚洲欧洲日产一区二区 | 性欧美大战久久久久久久 | 久久亚洲中文字幕精品一区 | 色综合久久网 | 男女猛烈xx00免费视频试看 | 成在人线av无码免观看麻豆 | √8天堂资源地址中文在线 | 亚洲欧美日韩成人高清在线一区 | 精品国产精品久久一区免费式 | 色狠狠av一区二区三区 | 在线成人www免费观看视频 | 少妇人妻偷人精品无码视频 | 蜜臀aⅴ国产精品久久久国产老师 | 欧美日韩一区二区综合 | 日本xxxx色视频在线观看免费 | 国产小呦泬泬99精品 | 国产av人人夜夜澡人人爽麻豆 | 2020久久超碰国产精品最新 | 久久精品无码一区二区三区 | 亚洲男人av香蕉爽爽爽爽 | 国产乱人伦av在线无码 | 乌克兰少妇xxxx做受 | 国产精品久久久久久亚洲影视内衣 | 亚洲成av人综合在线观看 | 老司机亚洲精品影院无码 | 国产一精品一av一免费 | 亚洲精品综合一区二区三区在线 | 亚洲欧美综合区丁香五月小说 | 国产成人无码一二三区视频 | 伊在人天堂亚洲香蕉精品区 | 中文毛片无遮挡高清免费 | 中文无码精品a∨在线观看不卡 | 波多野42部无码喷潮在线 | 疯狂三人交性欧美 | 国产精品第一区揄拍无码 | 人妻少妇精品无码专区动漫 | 久久99精品国产麻豆蜜芽 | 精品亚洲韩国一区二区三区 | 亚洲阿v天堂在线 | 亚洲午夜无码久久 | 亚洲人成网站免费播放 | 天堂无码人妻精品一区二区三区 | 少妇厨房愉情理9仑片视频 | 男女下面进入的视频免费午夜 | 女高中生第一次破苞av | 日日天日日夜日日摸 | 国产亚洲精品久久久久久久久动漫 | 国产精品a成v人在线播放 | 亚洲综合在线一区二区三区 | 欧美第一黄网免费网站 | 人人澡人人妻人人爽人人蜜桃 | 国产区女主播在线观看 | 日韩精品一区二区av在线 | 精品久久久久久人妻无码中文字幕 | 人妻熟女一区 | 精品久久综合1区2区3区激情 | 成 人 网 站国产免费观看 | 国产精品人妻一区二区三区四 | 国产午夜视频在线观看 | 狠狠色噜噜狠狠狠狠7777米奇 | 精品国产一区二区三区四区在线看 | 国产成人无码av在线影院 | 狠狠色噜噜狠狠狠狠7777米奇 | 丁香花在线影院观看在线播放 | 少妇性l交大片欧洲热妇乱xxx | 亚洲男女内射在线播放 | 国产精品久久久久久亚洲毛片 | 国产精品无码永久免费888 | 欧美xxxxx精品 | 国产成人无码av一区二区 | 久久国内精品自在自线 | 亚洲色偷偷偷综合网 | 成熟人妻av无码专区 | 一个人看的www免费视频在线观看 | 18禁黄网站男男禁片免费观看 | 国产精品99爱免费视频 | 熟妇人妻激情偷爽文 | 亚洲欧美日韩国产精品一区二区 | 国产人妻人伦精品1国产丝袜 | 亚洲小说图区综合在线 | 久久 国产 尿 小便 嘘嘘 | 啦啦啦www在线观看免费视频 | 亚洲精品久久久久久久久久久 | 免费人成网站视频在线观看 | 无码人中文字幕 | 俺去俺来也在线www色官网 | 亚洲精品成a人在线观看 | 亚洲第一无码av无码专区 | 午夜精品久久久内射近拍高清 | 一二三四社区在线中文视频 | 亚洲啪av永久无码精品放毛片 | 亚洲人成人无码网www国产 | 国精产品一区二区三区 | 人妻尝试又大又粗久久 | 久久精品人妻少妇一区二区三区 | 久久国产精品二国产精品 | 精品午夜福利在线观看 | 欧美乱妇无乱码大黄a片 | 人妻少妇精品无码专区二区 | 亚洲狠狠婷婷综合久久 | 麻豆国产人妻欲求不满 | 亚洲欧美色中文字幕在线 | 欧洲vodafone精品性 | 67194成是人免费无码 | 国产色xx群视频射精 | 亚洲综合色区中文字幕 | 亚洲无人区一区二区三区 | 欧美老妇交乱视频在线观看 | 领导边摸边吃奶边做爽在线观看 | 嫩b人妻精品一区二区三区 | 亚洲综合伊人久久大杳蕉 | 午夜不卡av免费 一本久久a久久精品vr综合 | 欧美日韩亚洲国产精品 | 红桃av一区二区三区在线无码av | 亚洲中文字幕无码中字 | 国产精品国产自线拍免费软件 | a片免费视频在线观看 | 中文精品久久久久人妻不卡 | 噜噜噜亚洲色成人网站 | 无遮挡国产高潮视频免费观看 | 亚洲精品www久久久 | 亚洲精品一区二区三区婷婷月 | 国产网红无码精品视频 | 中文字幕av伊人av无码av | 综合激情五月综合激情五月激情1 | 亚洲无人区午夜福利码高清完整版 | 精品久久久中文字幕人妻 | 大肉大捧一进一出好爽视频 | 欧美丰满熟妇xxxx性ppx人交 | 又大又黄又粗又爽的免费视频 | 亚洲成熟女人毛毛耸耸多 | 人妻互换免费中文字幕 | 中文亚洲成a人片在线观看 | 大地资源中文第3页 | 人妻少妇精品无码专区二区 | 成人影院yy111111在线观看 | 扒开双腿吃奶呻吟做受视频 | 98国产精品综合一区二区三区 | 国产精品久久久久无码av色戒 | 免费乱码人妻系列无码专区 | 午夜福利一区二区三区在线观看 | v一区无码内射国产 | 久久久久久av无码免费看大片 | 男人的天堂av网站 | 欧美日韩一区二区免费视频 | 最近免费中文字幕中文高清百度 | 久精品国产欧美亚洲色aⅴ大片 | 久久视频在线观看精品 | 精品水蜜桃久久久久久久 | 午夜肉伦伦影院 | 中文字幕无码视频专区 | 大乳丰满人妻中文字幕日本 | 亚洲精品一区二区三区大桥未久 | 99久久人妻精品免费一区 | 国产精品18久久久久久麻辣 | 熟女少妇在线视频播放 | 四虎永久在线精品免费网址 | 日本高清一区免费中文视频 | 噜噜噜亚洲色成人网站 | 熟妇人妻激情偷爽文 | 又湿又紧又大又爽a视频国产 | 亚洲欧洲中文日韩av乱码 | 国产乱子伦视频在线播放 | 天海翼激烈高潮到腰振不止 | 国产精品亚洲lv粉色 | 少女韩国电视剧在线观看完整 | 男女作爱免费网站 | 荫蒂添的好舒服视频囗交 | 国产婷婷色一区二区三区在线 | 俺去俺来也在线www色官网 | 亚洲无人区午夜福利码高清完整版 | 亚洲人成网站免费播放 | 久久精品人人做人人综合 | 九月婷婷人人澡人人添人人爽 | 亚洲精品国产a久久久久久 | 色老头在线一区二区三区 | 国产精品亚洲lv粉色 | 国产精品亚洲综合色区韩国 | 国产在线aaa片一区二区99 | 欧美国产亚洲日韩在线二区 | 国产精品亚洲专区无码不卡 | 国产人妻精品午夜福利免费 | www成人国产高清内射 | 精品久久久久久亚洲精品 | 亚洲国产午夜精品理论片 | 欧美一区二区三区 | 妺妺窝人体色www婷婷 | 亚洲gv猛男gv无码男同 | 精品欧洲av无码一区二区三区 | 国产精品嫩草久久久久 | 日日碰狠狠丁香久燥 | 牲欲强的熟妇农村老妇女视频 | 中文字幕无码免费久久99 | 欧美精品一区二区精品久久 | 久久久久亚洲精品中文字幕 | 无码人妻少妇伦在线电影 | 天堂亚洲免费视频 | 麻豆国产人妻欲求不满谁演的 | 蜜桃无码一区二区三区 | 人人澡人人透人人爽 | 国产两女互慰高潮视频在线观看 | 麻花豆传媒剧国产免费mv在线 | 国产精品人人爽人人做我的可爱 | 丰满岳乱妇在线观看中字无码 | aⅴ在线视频男人的天堂 | 精品少妇爆乳无码av无码专区 | 亚洲中文字幕久久无码 | av香港经典三级级 在线 | 日韩精品一区二区av在线 | 日韩av无码一区二区三区 | 亚洲成a人片在线观看无码 | 国产成人午夜福利在线播放 | 麻豆蜜桃av蜜臀av色欲av | 日本精品人妻无码77777 天堂一区人妻无码 | 久久精品国产一区二区三区肥胖 | 亚洲狠狠婷婷综合久久 | 色欲人妻aaaaaaa无码 | 国产偷抇久久精品a片69 | а天堂中文在线官网 | 久精品国产欧美亚洲色aⅴ大片 | 久久久久亚洲精品男人的天堂 | 久久国产精品精品国产色婷婷 | 香蕉久久久久久av成人 | 午夜免费福利小电影 | 亚洲热妇无码av在线播放 | 日本护士毛茸茸高潮 | 欧美怡红院免费全部视频 | 国产sm调教视频在线观看 | 国产精品亚洲综合色区韩国 | 亚洲自偷精品视频自拍 | 中文字幕 亚洲精品 第1页 | 夜先锋av资源网站 | 亚洲人亚洲人成电影网站色 | 亚洲国产欧美国产综合一区 | 人妻少妇精品视频专区 | 青青久在线视频免费观看 | 亚洲一区二区三区四区 | 成人欧美一区二区三区黑人免费 | 久久zyz资源站无码中文动漫 | 精品国产乱码久久久久乱码 | 国产超碰人人爽人人做人人添 | 成人综合网亚洲伊人 | 一本色道久久综合狠狠躁 | 国产精品永久免费视频 | 性欧美疯狂xxxxbbbb | 色 综合 欧美 亚洲 国产 | 久久无码中文字幕免费影院蜜桃 | 国产免费久久精品国产传媒 | 亚洲精品欧美二区三区中文字幕 | 亚洲精品综合一区二区三区在线 | 无遮无挡爽爽免费视频 | 丰满人妻被黑人猛烈进入 | 亚洲日韩乱码中文无码蜜桃臀网站 | 欧美日韩人成综合在线播放 | 亚洲aⅴ无码成人网站国产app | 丰满人妻被黑人猛烈进入 | 欧美野外疯狂做受xxxx高潮 | 狠狠色噜噜狠狠狠7777奇米 | 大地资源网第二页免费观看 | 亚洲gv猛男gv无码男同 | 亚洲色偷偷男人的天堂 | 国产香蕉97碰碰久久人人 | 国产办公室秘书无码精品99 | 台湾无码一区二区 | 奇米影视7777久久精品人人爽 | 4hu四虎永久在线观看 | 日本熟妇大屁股人妻 | 成人影院yy111111在线观看 | 无码人妻精品一区二区三区不卡 | 中文无码成人免费视频在线观看 | 亚洲gv猛男gv无码男同 | 久久97精品久久久久久久不卡 | 欧美亚洲国产一区二区三区 | 中文字幕无码av激情不卡 | 成人女人看片免费视频放人 | 白嫩日本少妇做爰 | 成在人线av无码免观看麻豆 | 乌克兰少妇xxxx做受 | 国产成人精品视频ⅴa片软件竹菊 | 精品国产麻豆免费人成网站 | 领导边摸边吃奶边做爽在线观看 | 97精品国产97久久久久久免费 | 男人的天堂av网站 | 国产熟妇高潮叫床视频播放 | 欧美人与动性行为视频 | 欧美喷潮久久久xxxxx | 久久熟妇人妻午夜寂寞影院 | a在线亚洲男人的天堂 | 久久综合香蕉国产蜜臀av | 亚洲日本一区二区三区在线 | 色一情一乱一伦一区二区三欧美 | 网友自拍区视频精品 | 欧美性黑人极品hd | 成 人影片 免费观看 | 国产午夜福利亚洲第一 | 日日天干夜夜狠狠爱 | 在线亚洲高清揄拍自拍一品区 | 久久久中文久久久无码 | 亚洲熟悉妇女xxx妇女av | 久久久av男人的天堂 | 国产国产精品人在线视 | 久久久久久久久888 | 色偷偷人人澡人人爽人人模 | 国产精品久久久久久无码 | 国产亚洲人成a在线v网站 | 人妻互换免费中文字幕 | 亚洲一区二区三区在线观看网站 | 日本欧美一区二区三区乱码 | 3d动漫精品啪啪一区二区中 | 少妇久久久久久人妻无码 | 内射爽无广熟女亚洲 | 国产熟妇高潮叫床视频播放 | 久久99久久99精品中文字幕 | 人人妻人人澡人人爽人人精品 | 精品国产青草久久久久福利 | 中文无码成人免费视频在线观看 | 国产熟女一区二区三区四区五区 | 欧美国产日产一区二区 | 国产人妻人伦精品1国产丝袜 | 日本乱偷人妻中文字幕 | 99精品久久毛片a片 | 亚洲爆乳无码专区 | 夜精品a片一区二区三区无码白浆 | 日本精品少妇一区二区三区 | 67194成是人免费无码 | 精品亚洲韩国一区二区三区 | 扒开双腿吃奶呻吟做受视频 | 双乳奶水饱满少妇呻吟 | 一本色道久久综合亚洲精品不卡 | 国产精品久久久久久久9999 | 人人爽人人澡人人高潮 | 又黄又爽又色的视频 | 国产精品亚洲一区二区三区喷水 | 亚洲精品成人av在线 | 欧美第一黄网免费网站 | 亚洲中文字幕在线观看 | 欧美三级不卡在线观看 | 中文字幕人妻丝袜二区 | 国产偷抇久久精品a片69 | 国内少妇偷人精品视频免费 | 波多野42部无码喷潮在线 | 国产成人无码av一区二区 | 日本精品人妻无码77777 天堂一区人妻无码 | 日韩欧美中文字幕在线三区 | 欧美丰满熟妇xxxx | 狠狠综合久久久久综合网 | 正在播放东北夫妻内射 | 国产特级毛片aaaaaaa高清 | 亚洲 激情 小说 另类 欧美 | 国产疯狂伦交大片 | 国内丰满熟女出轨videos | 国产超级va在线观看视频 | 色欲久久久天天天综合网精品 | 97资源共享在线视频 | 水蜜桃av无码 | 日本一区二区更新不卡 | 婷婷丁香六月激情综合啪 | 久久五月精品中文字幕 | 亚洲欧美综合区丁香五月小说 | 99在线 | 亚洲 | 欧美人妻一区二区三区 | 久久天天躁狠狠躁夜夜免费观看 | 国产成人一区二区三区在线观看 | 激情内射日本一区二区三区 | 鲁一鲁av2019在线 | 色婷婷香蕉在线一区二区 | 日韩人妻无码中文字幕视频 | 久久这里只有精品视频9 | 人妻少妇精品无码专区动漫 | 一本一道久久综合久久 | 久久精品中文字幕大胸 | 亚洲国精产品一二二线 | 女高中生第一次破苞av | 欧美xxxxx精品 | 久久久中文久久久无码 | 正在播放老肥熟妇露脸 | 免费看少妇作爱视频 | 四虎国产精品免费久久 | 天天躁日日躁狠狠躁免费麻豆 | 国产乡下妇女做爰 | 少妇性l交大片欧洲热妇乱xxx | 国内精品久久久久久中文字幕 | 国产精品对白交换视频 | 国产精品久久久久久亚洲影视内衣 | 国产精品国产三级国产专播 | 久久久中文字幕日本无吗 | 亚洲一区av无码专区在线观看 | 中文字幕av无码一区二区三区电影 | 小sao货水好多真紧h无码视频 | 国产成人无码a区在线观看视频app | 欧美成人高清在线播放 | 成在人线av无码免观看麻豆 | 中文字幕无线码免费人妻 | 四虎国产精品免费久久 | 中文字幕乱妇无码av在线 | 亚洲伊人久久精品影院 | 久久久精品456亚洲影院 | 日韩欧美群交p片內射中文 | 色一情一乱一伦 | 欧美性猛交xxxx富婆 | 亚洲日韩一区二区 | 日日摸夜夜摸狠狠摸婷婷 | 日本肉体xxxx裸交 | 麻豆国产人妻欲求不满 | 日本熟妇人妻xxxxx人hd | 日日橹狠狠爱欧美视频 | 天堂а√在线地址中文在线 | 中文字幕精品av一区二区五区 | 国产乱人无码伦av在线a | 国产精品亚洲综合色区韩国 | 亚洲欧美综合区丁香五月小说 | 国产明星裸体无码xxxx视频 | 国产精品久久久av久久久 | 色偷偷人人澡人人爽人人模 | 国产va免费精品观看 | 免费无码午夜福利片69 | 国产精品第一国产精品 | 国产精品视频免费播放 | 97色伦图片97综合影院 | 无码精品人妻一区二区三区av | 亚洲理论电影在线观看 | 国产一精品一av一免费 | 精品国产乱码久久久久乱码 | 精品一区二区不卡无码av | 精品偷自拍另类在线观看 | 欧美老熟妇乱xxxxx | 色情久久久av熟女人妻网站 | 又大又黄又粗又爽的免费视频 | 国产高潮视频在线观看 | 黑人粗大猛烈进出高潮视频 | 国产又粗又硬又大爽黄老大爷视 | √天堂资源地址中文在线 | 国产成人无码av片在线观看不卡 | 亚洲国产精品一区二区第一页 | www国产亚洲精品久久网站 | 高清国产亚洲精品自在久久 | 激情内射日本一区二区三区 | 天天av天天av天天透 | 激情综合激情五月俺也去 | 免费男性肉肉影院 | 国产凸凹视频一区二区 | 久久国产精品_国产精品 | 日日干夜夜干 | 一本大道伊人av久久综合 | 18禁黄网站男男禁片免费观看 | 成人av无码一区二区三区 | 国产精品久久久久久亚洲影视内衣 | 亚洲中文字幕av在天堂 | 在教室伦流澡到高潮hnp视频 | 无遮挡国产高潮视频免费观看 | 亚洲成色www久久网站 | 99精品久久毛片a片 | 中文字幕乱妇无码av在线 | 人人妻人人澡人人爽人人精品 | 国产偷国产偷精品高清尤物 | 装睡被陌生人摸出水好爽 | 久久国产劲爆∧v内射 | 中文字幕乱码亚洲无线三区 | 国产猛烈高潮尖叫视频免费 | 超碰97人人做人人爱少妇 | 黑人大群体交免费视频 | 国产精品二区一区二区aⅴ污介绍 | 国内少妇偷人精品视频免费 | 日本一区二区三区免费高清 | 亚洲日韩av一区二区三区四区 | 亚洲精品中文字幕乱码 | 亚洲精品美女久久久久久久 | 久久久久成人片免费观看蜜芽 | 天堂无码人妻精品一区二区三区 | 曰韩无码二三区中文字幕 | 亚洲精品无码国产 | 成人免费视频在线观看 | 又大又硬又黄的免费视频 | 精品国产精品久久一区免费式 | 久久97精品久久久久久久不卡 | 亚洲国产高清在线观看视频 | 女人高潮内射99精品 | 人人爽人人爽人人片av亚洲 | 中文字幕+乱码+中文字幕一区 | 国产香蕉97碰碰久久人人 | 国产在线无码精品电影网 | 99国产精品白浆在线观看免费 | 欧美精品在线观看 | 97夜夜澡人人爽人人喊中国片 | 久久久久av无码免费网 | 国产色精品久久人妻 | 性做久久久久久久免费看 | 内射白嫩少妇超碰 | 久久人人爽人人爽人人片ⅴ | 骚片av蜜桃精品一区 | 清纯唯美经典一区二区 | 波多野结衣乳巨码无在线观看 | 中文字幕无码日韩专区 | 伊人久久大香线焦av综合影院 | 无码国内精品人妻少妇 | 99久久久无码国产aaa精品 | 国产激情一区二区三区 | 欧美精品免费观看二区 | 亚洲成av人影院在线观看 | 久久无码人妻影院 | 国产精品国产自线拍免费软件 | 99久久精品国产一区二区蜜芽 | 久9re热视频这里只有精品 | 丰满人妻一区二区三区免费视频 | 日韩人妻少妇一区二区三区 | 少妇的肉体aa片免费 | 色窝窝无码一区二区三区色欲 | 日日碰狠狠丁香久燥 | 亚洲码国产精品高潮在线 | 麻豆国产丝袜白领秘书在线观看 | a在线观看免费网站大全 | 丰满妇女强制高潮18xxxx | 无套内谢的新婚少妇国语播放 | 55夜色66夜色国产精品视频 | 亚洲の无码国产の无码影院 | 久久久无码中文字幕久... | 青春草在线视频免费观看 | 久久国产精品_国产精品 | 欧洲熟妇精品视频 | 理论片87福利理论电影 | 久久久久久久女国产乱让韩 | 日韩精品乱码av一区二区 | 婷婷五月综合激情中文字幕 | 精品国产一区二区三区四区在线看 | 国产免费久久久久久无码 | 国产精品视频免费播放 | 午夜熟女插插xx免费视频 | 欧美日韩亚洲国产精品 | 理论片87福利理论电影 | 亚洲人成人无码网www国产 | 亚洲色大成网站www国产 | 欧美激情一区二区三区成人 | 国内精品人妻无码久久久影院 | 欧美一区二区三区 | 天堂亚洲免费视频 | 国产香蕉97碰碰久久人人 | 精品久久久无码人妻字幂 | 日本精品人妻无码77777 天堂一区人妻无码 | 国产成人精品一区二区在线小狼 | 亚洲中文字幕乱码av波多ji | 红桃av一区二区三区在线无码av | 日本熟妇乱子伦xxxx | 精品厕所偷拍各类美女tp嘘嘘 | 日本一区二区三区免费高清 | 人妻天天爽夜夜爽一区二区 | 女人被爽到呻吟gif动态图视看 | 国产一区二区三区日韩精品 | 日日噜噜噜噜夜夜爽亚洲精品 | 精品亚洲成av人在线观看 | 国产69精品久久久久app下载 | 亚洲国产av精品一区二区蜜芽 | 亚洲精品成人福利网站 | 国产人妻人伦精品 | 国产偷抇久久精品a片69 | 国产做国产爱免费视频 | 99麻豆久久久国产精品免费 | 少妇被黑人到高潮喷出白浆 | www国产亚洲精品久久久日本 | 99久久久国产精品无码免费 | 国产激情一区二区三区 | 东京热一精品无码av | 欧美精品免费观看二区 | 亚洲日韩乱码中文无码蜜桃臀网站 | 成人无码视频免费播放 | 国产在线一区二区三区四区五区 | 亚洲综合伊人久久大杳蕉 | 国产精品亚洲一区二区三区喷水 | 中文字幕中文有码在线 | 成熟妇人a片免费看网站 | 亚洲 a v无 码免 费 成 人 a v | 大色综合色综合网站 | 97久久国产亚洲精品超碰热 | 日本一卡二卡不卡视频查询 | 亚洲午夜无码久久 | 久久精品视频在线看15 | 国产疯狂伦交大片 | 国产精品福利视频导航 | 欧美日韩在线亚洲综合国产人 | 中文精品久久久久人妻不卡 | 性欧美videos高清精品 | 久久www免费人成人片 | www国产亚洲精品久久网站 | 午夜福利不卡在线视频 | 久久五月精品中文字幕 | аⅴ资源天堂资源库在线 | 日韩精品乱码av一区二区 | 3d动漫精品啪啪一区二区中 | 国产精品久久国产三级国 | 欧美freesex黑人又粗又大 | 国产口爆吞精在线视频 | 高潮毛片无遮挡高清免费 | 人妻天天爽夜夜爽一区二区 | 中文字幕人妻无码一区二区三区 | 国产精品无码永久免费888 | 亚洲男女内射在线播放 | 国产乱人无码伦av在线a | 少妇无码一区二区二三区 | 色综合久久久无码中文字幕 | 牲欲强的熟妇农村老妇女 | 亚洲男人av天堂午夜在 | 欧美人与善在线com | 少女韩国电视剧在线观看完整 | 国内精品人妻无码久久久影院 | 东京热无码av男人的天堂 | 亚洲熟妇色xxxxx欧美老妇y | 伊人久久大香线蕉午夜 | 麻豆果冻传媒2021精品传媒一区下载 | 99精品久久毛片a片 | 18禁黄网站男男禁片免费观看 | 欧美阿v高清资源不卡在线播放 | 国产精品久久久一区二区三区 | 少妇高潮喷潮久久久影院 | 亚洲毛片av日韩av无码 | 中文字幕乱码中文乱码51精品 | 欧美阿v高清资源不卡在线播放 | 国产精品嫩草久久久久 | 300部国产真实乱 | 东京热无码av男人的天堂 | 国产成人综合美国十次 | 国产亚洲精品精品国产亚洲综合 | 精品偷拍一区二区三区在线看 | 国产在线无码精品电影网 | 亚洲人成影院在线无码按摩店 | 久久久久久av无码免费看大片 | 中文字幕无码av波多野吉衣 | 美女毛片一区二区三区四区 | 国内少妇偷人精品视频 | 成年美女黄网站色大免费全看 | 香蕉久久久久久av成人 | 免费男性肉肉影院 | 亚洲第一无码av无码专区 | 亚洲午夜福利在线观看 | 少女韩国电视剧在线观看完整 | 三级4级全黄60分钟 | 性欧美熟妇videofreesex | 欧美日本精品一区二区三区 | 婷婷色婷婷开心五月四房播播 | 午夜嘿嘿嘿影院 | 亚洲成av人影院在线观看 | 狠狠色丁香久久婷婷综合五月 | 狠狠色欧美亚洲狠狠色www | 天堂一区人妻无码 | aⅴ亚洲 日韩 色 图网站 播放 | 亚洲中文字幕va福利 | 国产精品资源一区二区 | 成人一在线视频日韩国产 | 欧美野外疯狂做受xxxx高潮 | 亚洲日本一区二区三区在线 | 色窝窝无码一区二区三区色欲 | 国产一区二区三区四区五区加勒比 | 澳门永久av免费网站 | 国产麻豆精品一区二区三区v视界 | 综合激情五月综合激情五月激情1 | 中文字幕乱码人妻二区三区 | 亚洲小说图区综合在线 | 少妇一晚三次一区二区三区 | 狠狠色噜噜狠狠狠7777奇米 | 亚洲色www成人永久网址 | 无码午夜成人1000部免费视频 | 亚洲成av人片在线观看无码不卡 | 丰满肥臀大屁股熟妇激情视频 | 国产熟妇高潮叫床视频播放 | 亚洲综合另类小说色区 | 国产精品对白交换视频 | 欧美 丝袜 自拍 制服 另类 | 国产精品久久久久久亚洲影视内衣 | 黑人巨大精品欧美一区二区 | 亚洲 另类 在线 欧美 制服 | 天堂а√在线地址中文在线 | 中文字幕无码人妻少妇免费 | 国产精品人人妻人人爽 | а√资源新版在线天堂 | 动漫av一区二区在线观看 | 熟妇人妻激情偷爽文 | 欧美黑人巨大xxxxx | 夜先锋av资源网站 | 国内揄拍国内精品人妻 | 国产在线精品一区二区高清不卡 | 少妇无套内谢久久久久 | 亚洲成色在线综合网站 | 久久精品99久久香蕉国产色戒 | av人摸人人人澡人人超碰下载 | 亚洲人成网站在线播放942 | 熟女少妇在线视频播放 | 亚洲欧美国产精品专区久久 | 日韩欧美中文字幕公布 | 黑森林福利视频导航 | 中文精品无码中文字幕无码专区 | 亚洲色无码一区二区三区 | 亚洲欧洲无卡二区视頻 | 国产精品无码成人午夜电影 | 亚洲乱码日产精品bd | 影音先锋中文字幕无码 | 色综合久久88色综合天天 | 中文字幕无码av波多野吉衣 | 亚洲自偷自拍另类第1页 | 日日摸日日碰夜夜爽av | 中文字幕av日韩精品一区二区 | 久久久久免费看成人影片 | 少妇太爽了在线观看 | 亚洲成熟女人毛毛耸耸多 | 麻豆果冻传媒2021精品传媒一区下载 | 亚洲熟悉妇女xxx妇女av | 蜜臀av在线观看 在线欧美精品一区二区三区 | 狂野欧美性猛xxxx乱大交 | 国产女主播喷水视频在线观看 | 免费网站看v片在线18禁无码 | 亚洲综合久久一区二区 | 精品无人区无码乱码毛片国产 | 欧美丰满老熟妇xxxxx性 | 欧美zoozzooz性欧美 | 精品国精品国产自在久国产87 | 性色欲情网站iwww九文堂 | 又粗又大又硬毛片免费看 | 蜜臀aⅴ国产精品久久久国产老师 | 国产精品人人爽人人做我的可爱 | 国产亚洲美女精品久久久2020 | 日日天日日夜日日摸 | 国精品人妻无码一区二区三区蜜柚 | 欧美丰满熟妇xxxx | 亚洲中文字幕成人无码 | 亚洲a无码综合a国产av中文 | 国产精品久久精品三级 | 欧美自拍另类欧美综合图片区 | 狠狠色欧美亚洲狠狠色www | 巨爆乳无码视频在线观看 | 麻花豆传媒剧国产免费mv在线 | 少妇无码一区二区二三区 | 美女扒开屁股让男人桶 | 亚洲国产精品美女久久久久 | 狂野欧美激情性xxxx | 国产艳妇av在线观看果冻传媒 | 亚洲欧美色中文字幕在线 | 天下第一社区视频www日本 | 免费男性肉肉影院 | 人人爽人人澡人人高潮 | 中文精品无码中文字幕无码专区 | 熟女少妇在线视频播放 | 丝袜足控一区二区三区 | 一本久久a久久精品vr综合 | 国产亚洲精品久久久久久久久动漫 | 色欲久久久天天天综合网精品 | 大色综合色综合网站 | 国产亚洲精品久久久久久久 | 亚洲大尺度无码无码专区 | 香蕉久久久久久av成人 | 成熟女人特级毛片www免费 | 欧美激情一区二区三区成人 | 在线看片无码永久免费视频 | 精品国产av色一区二区深夜久久 | 国产在线精品一区二区高清不卡 | 四虎国产精品免费久久 | 又大又紧又粉嫩18p少妇 | 2019nv天堂香蕉在线观看 | 日韩成人一区二区三区在线观看 | 国产精品久久久久久亚洲毛片 | 熟女俱乐部五十路六十路av | 人人妻人人澡人人爽欧美精品 | 国产精品第一区揄拍无码 | 3d动漫精品啪啪一区二区中 | 久久99精品久久久久久动态图 | 国产精品福利视频导航 | 精品国偷自产在线视频 | 思思久久99热只有频精品66 | а天堂中文在线官网 | 久久久国产一区二区三区 | 乱中年女人伦av三区 | 国产成人无码av在线影院 | 99精品国产综合久久久久五月天 | 国产高清av在线播放 | 蜜桃视频插满18在线观看 | 亚洲色偷偷男人的天堂 | 亚洲欧美日韩成人高清在线一区 | √天堂资源地址中文在线 | 国产深夜福利视频在线 | 亚洲狠狠色丁香婷婷综合 | 大地资源中文第3页 | 亚洲日本va午夜在线电影 | 乱中年女人伦av三区 | 漂亮人妻洗澡被公强 日日躁 | 无码人妻久久一区二区三区不卡 | 初尝人妻少妇中文字幕 | 性欧美videos高清精品 | 人人妻人人澡人人爽欧美一区九九 | 日本精品高清一区二区 | 国产精品福利视频导航 | 亚洲国产精品一区二区第一页 | 欧美日本精品一区二区三区 | 狂野欧美激情性xxxx | 伊人色综合久久天天小片 | 亚洲最大成人网站 | 国产精品永久免费视频 | 日韩成人一区二区三区在线观看 | 国产97在线 | 亚洲 | 免费无码午夜福利片69 | 婷婷五月综合缴情在线视频 | 天天躁夜夜躁狠狠是什么心态 | 国产亚洲精品久久久久久久久动漫 | 久久99精品久久久久久动态图 | 亚洲无人区午夜福利码高清完整版 | www国产精品内射老师 | 亚洲乱码日产精品bd | 久激情内射婷内射蜜桃人妖 | 76少妇精品导航 | 亚洲国产欧美国产综合一区 | 丝袜人妻一区二区三区 | 久久成人a毛片免费观看网站 | 亚洲精品一区二区三区在线 | 中文字幕av无码一区二区三区电影 | 亚洲爆乳无码专区 | 亚洲精品欧美二区三区中文字幕 | 免费看少妇作爱视频 | 国产疯狂伦交大片 | 精品人妻人人做人人爽 | 国产疯狂伦交大片 | 久久久成人毛片无码 | 少妇性l交大片欧洲热妇乱xxx | 精品乱子伦一区二区三区 | 成人aaa片一区国产精品 | 熟女少妇人妻中文字幕 | 性色av无码免费一区二区三区 | 欧美三级不卡在线观看 | 超碰97人人做人人爱少妇 | 久久精品国产99精品亚洲 | 国产成人精品视频ⅴa片软件竹菊 | 狠狠综合久久久久综合网 | 对白脏话肉麻粗话av | 性啪啪chinese东北女人 | 亚洲日韩av片在线观看 | 国产精品视频免费播放 | 女人和拘做爰正片视频 | 奇米影视888欧美在线观看 | 国产精品va在线观看无码 | 天天躁日日躁狠狠躁免费麻豆 | 四虎永久在线精品免费网址 | 精品少妇爆乳无码av无码专区 | 亚洲精品鲁一鲁一区二区三区 | 兔费看少妇性l交大片免费 | 性欧美疯狂xxxxbbbb | 老熟妇仑乱视频一区二区 | 久久亚洲a片com人成 | 久久久精品人妻久久影视 | www成人国产高清内射 | 色婷婷综合激情综在线播放 | 黑森林福利视频导航 | 亚洲国产精品无码久久久久高潮 | 亚洲一区二区三区四区 | 国产九九九九九九九a片 | 麻豆av传媒蜜桃天美传媒 | 国产精品二区一区二区aⅴ污介绍 | 国产精品高潮呻吟av久久 | 国产精品久久久久影院嫩草 | 国产精品内射视频免费 | 牛和人交xxxx欧美 | 内射白嫩少妇超碰 | 国内丰满熟女出轨videos | 在线播放无码字幕亚洲 | 性欧美牲交在线视频 | 77777熟女视频在线观看 а天堂中文在线官网 | 1000部啪啪未满十八勿入下载 | 女人被男人躁得好爽免费视频 | 亚洲人成网站在线播放942 | 欧美午夜特黄aaaaaa片 | 精品一区二区三区无码免费视频 | 日韩精品乱码av一区二区 | 久久国产精品偷任你爽任你 | 亚洲精品一区二区三区婷婷月 | 久久精品国产99久久6动漫 | 亚洲欧美色中文字幕在线 | 色老头在线一区二区三区 | 亚洲色大成网站www国产 | 波多野结衣乳巨码无在线观看 | 亚无码乱人伦一区二区 | 国产真实夫妇视频 | 国产网红无码精品视频 | 成在人线av无码免观看麻豆 | 强伦人妻一区二区三区视频18 | 中国女人内谢69xxxxxa片 | 久久熟妇人妻午夜寂寞影院 | 樱花草在线播放免费中文 | 美女极度色诱视频国产 | 国产精品自产拍在线观看 | 久久精品无码一区二区三区 | 中文字幕av无码一区二区三区电影 | 爱做久久久久久 | av人摸人人人澡人人超碰下载 | 高清国产亚洲精品自在久久 | 国产麻豆精品一区二区三区v视界 | 99久久婷婷国产综合精品青草免费 | 免费网站看v片在线18禁无码 | 成人亚洲精品久久久久软件 | 国产成人一区二区三区别 | 无人区乱码一区二区三区 | 欧美性猛交xxxx富婆 | 精品久久久久久亚洲精品 | 日日摸夜夜摸狠狠摸婷婷 | 国产明星裸体无码xxxx视频 | 欧美性生交活xxxxxdddd | 熟女少妇在线视频播放 | 高潮毛片无遮挡高清免费视频 | 精品午夜福利在线观看 | 亚洲阿v天堂在线 | 国产小呦泬泬99精品 | 熟女俱乐部五十路六十路av | 搡女人真爽免费视频大全 | 亚洲中文字幕成人无码 | 高潮毛片无遮挡高清免费 | 久久久国产精品无码免费专区 | 日本又色又爽又黄的a片18禁 | 丝袜足控一区二区三区 | 天天摸天天透天天添 | 一本无码人妻在中文字幕免费 | 夜夜高潮次次欢爽av女 | 久久五月精品中文字幕 | 国产精品久久国产精品99 | 亚洲欧美日韩国产精品一区二区 | 精品无码成人片一区二区98 | 免费看少妇作爱视频 | 国产成人综合在线女婷五月99播放 | 日本www一道久久久免费榴莲 | 国精产品一品二品国精品69xx | 人妻天天爽夜夜爽一区二区 | 377p欧洲日本亚洲大胆 | 亚洲色在线无码国产精品不卡 | 激情五月综合色婷婷一区二区 | √天堂中文官网8在线 | 欧美性猛交xxxx富婆 | 久久久久99精品成人片 | 无套内射视频囯产 | 香蕉久久久久久av成人 | 国产午夜视频在线观看 | 国产精品亚洲一区二区三区喷水 | 久久久久99精品国产片 | 国产热a欧美热a在线视频 | 精品国产av色一区二区深夜久久 | 精品乱码久久久久久久 | 亚洲综合无码久久精品综合 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 18无码粉嫩小泬无套在线观看 | 中文字幕人成乱码熟女app | 99久久99久久免费精品蜜桃 | 高潮毛片无遮挡高清免费视频 | 久久国产精品偷任你爽任你 | 国产精品理论片在线观看 | 国产又爽又猛又粗的视频a片 | 麻豆国产97在线 | 欧洲 | 国内精品一区二区三区不卡 | 波多野结衣aⅴ在线 | 久久综合香蕉国产蜜臀av | 一本久道高清无码视频 | 亚洲日本一区二区三区在线 | 亚洲娇小与黑人巨大交 | 亚洲天堂2017无码中文 | 娇妻被黑人粗大高潮白浆 | 99久久久无码国产精品免费 | 青青青爽视频在线观看 | 亚洲中文字幕在线无码一区二区 | 无码人妻av免费一区二区三区 | 成人欧美一区二区三区黑人免费 | 夜先锋av资源网站 | 精品一区二区三区波多野结衣 | 动漫av一区二区在线观看 | 中文字幕乱码人妻无码久久 | 久久97精品久久久久久久不卡 | 亚洲日韩中文字幕在线播放 | 99久久精品国产一区二区蜜芽 | 青草视频在线播放 | 秋霞特色aa大片 | 国産精品久久久久久久 | 欧美野外疯狂做受xxxx高潮 | 国产日产欧产精品精品app | 午夜熟女插插xx免费视频 | 国产精品第一国产精品 | 亚洲一区二区三区播放 | 青草青草久热国产精品 | 伊在人天堂亚洲香蕉精品区 | 在线精品亚洲一区二区 | 亚洲成av人影院在线观看 | 男女下面进入的视频免费午夜 | 无码人妻精品一区二区三区不卡 | 亚洲精品欧美二区三区中文字幕 | 久久久久久久人妻无码中文字幕爆 | 日本精品少妇一区二区三区 | 久久国产精品精品国产色婷婷 | 精品乱码久久久久久久 | 亚洲精品午夜无码电影网 | 国内揄拍国内精品人妻 | yw尤物av无码国产在线观看 | 久久99热只有频精品8 | 日日噜噜噜噜夜夜爽亚洲精品 | 国产精品成人av在线观看 | 亚洲中文字幕乱码av波多ji | 国产偷自视频区视频 | 欧美放荡的少妇 | 国产av剧情md精品麻豆 | 日韩亚洲欧美精品综合 | 亚洲人成网站色7799 | 亚洲啪av永久无码精品放毛片 | 亚洲中文字幕在线观看 | 好男人社区资源 | 色窝窝无码一区二区三区色欲 | 国产免费久久久久久无码 | 久久久久99精品国产片 | 未满小14洗澡无码视频网站 | 日日碰狠狠丁香久燥 | 久久伊人色av天堂九九小黄鸭 | 免费播放一区二区三区 | 日本熟妇人妻xxxxx人hd | 亚洲综合在线一区二区三区 | 成人一在线视频日韩国产 | av香港经典三级级 在线 | 久久精品国产日本波多野结衣 | 日韩在线不卡免费视频一区 | 疯狂三人交性欧美 | 国产成人无码av在线影院 | 中文精品久久久久人妻不卡 | 日本精品久久久久中文字幕 | 精品人人妻人人澡人人爽人人 | 亚洲一区av无码专区在线观看 | 精品成人av一区二区三区 | 极品嫩模高潮叫床 | 又粗又大又硬又长又爽 | 夫妻免费无码v看片 | 牲欲强的熟妇农村老妇女视频 | 久久人人爽人人爽人人片av高清 | 国产精品内射视频免费 | 亚洲精品一区二区三区在线观看 | 奇米影视7777久久精品 | 男人的天堂2018无码 | yw尤物av无码国产在线观看 | 午夜精品久久久内射近拍高清 | 伊人久久大香线蕉av一区二区 | 成人无码视频在线观看网站 | 欧美freesex黑人又粗又大 | 精品无人国产偷自产在线 | 国产人成高清在线视频99最全资源 | 无码乱肉视频免费大全合集 | 好男人社区资源 | 国产色视频一区二区三区 | 久久亚洲精品中文字幕无男同 | 成 人 网 站国产免费观看 | 日韩人妻少妇一区二区三区 | 精品偷拍一区二区三区在线看 | 日韩av无码中文无码电影 | 99久久精品午夜一区二区 | 女人被爽到呻吟gif动态图视看 | 成熟人妻av无码专区 | 免费看男女做好爽好硬视频 | 国产亚洲精品精品国产亚洲综合 | 久久精品国产亚洲精品 | 强奷人妻日本中文字幕 | 性欧美牲交xxxxx视频 | 夜精品a片一区二区三区无码白浆 | 国产激情无码一区二区 | 国产亲子乱弄免费视频 | 麻豆国产丝袜白领秘书在线观看 | 国产莉萝无码av在线播放 | 亚洲欧美国产精品久久 | 国产精品丝袜黑色高跟鞋 | 性欧美videos高清精品 | 兔费看少妇性l交大片免费 | 无套内射视频囯产 | 亚洲一区二区三区无码久久 | 久久无码中文字幕免费影院蜜桃 | 无码国产乱人伦偷精品视频 | 亚洲成av人片天堂网无码】 | 日韩视频 中文字幕 视频一区 | 熟女体下毛毛黑森林 | 国产三级久久久精品麻豆三级 | 国产成人无码av在线影院 | 日韩欧美群交p片內射中文 | 一本久道久久综合婷婷五月 | 永久免费精品精品永久-夜色 | 精品国产成人一区二区三区 | 清纯唯美经典一区二区 | 久久精品国产99久久6动漫 | 国产精品免费大片 | 日本在线高清不卡免费播放 | 午夜福利电影 | 色欲综合久久中文字幕网 | 亚洲综合无码一区二区三区 | 亚洲中文字幕成人无码 | 国产亚洲精品久久久久久久 | 天天爽夜夜爽夜夜爽 | 黄网在线观看免费网站 | 久久综合激激的五月天 | 少妇被粗大的猛进出69影院 | 色欲av亚洲一区无码少妇 | 天干天干啦夜天干天2017 | 久久99精品国产.久久久久 | 精品乱子伦一区二区三区 | 亚洲 a v无 码免 费 成 人 a v | 国产激情艳情在线看视频 | 国产精品va在线观看无码 | 熟妇人妻无码xxx视频 | 中文无码成人免费视频在线观看 | 亚洲成熟女人毛毛耸耸多 | www国产亚洲精品久久网站 | 国产在线精品一区二区高清不卡 | 东京热男人av天堂 | 亚洲日本在线电影 | 国产精品无套呻吟在线 | 一本久道高清无码视频 | 97精品人妻一区二区三区香蕉 | 熟女少妇在线视频播放 | 亚洲色欲久久久综合网东京热 | 在线а√天堂中文官网 | 中文字幕人妻无码一区二区三区 | 亚洲天堂2017无码中文 | 激情爆乳一区二区三区 | 国产色xx群视频射精 | 国产国语老龄妇女a片 | 精品人人妻人人澡人人爽人人 | 国产女主播喷水视频在线观看 | 在教室伦流澡到高潮hnp视频 | 日本丰满护士爆乳xxxx | 日本www一道久久久免费榴莲 | 黑人巨大精品欧美黑寡妇 | 性欧美牲交在线视频 | 欧美性猛交xxxx富婆 | 亚洲娇小与黑人巨大交 | 亚洲s码欧洲m码国产av | 欧美日韩一区二区三区自拍 | 无码福利日韩神码福利片 | a在线观看免费网站大全 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 久久久久亚洲精品男人的天堂 | 人人爽人人爽人人片av亚洲 | 成人女人看片免费视频放人 | 一本色道婷婷久久欧美 | 波多野结衣av在线观看 | 国产精品第一国产精品 | 无码任你躁久久久久久久 | 国产亚洲日韩欧美另类第八页 | 国产av久久久久精东av | 精品少妇爆乳无码av无码专区 | 西西人体www44rt大胆高清 | 俺去俺来也在线www色官网 | 亚洲色偷偷偷综合网 | 午夜福利一区二区三区在线观看 | 精品国精品国产自在久国产87 | 无码人妻少妇伦在线电影 | 亚洲综合在线一区二区三区 | 欧美亚洲日韩国产人成在线播放 | 乱人伦中文视频在线观看 | 日本乱人伦片中文三区 | 性生交大片免费看女人按摩摩 | 亚洲色www成人永久网址 | 亚洲精品国偷拍自产在线麻豆 | 亚洲毛片av日韩av无码 | 国产情侣作爱视频免费观看 | 图片小说视频一区二区 | 伊人久久大香线焦av综合影院 | 亚洲精品综合五月久久小说 | 精品久久久无码人妻字幂 | 西西人体www44rt大胆高清 | 亚洲自偷自偷在线制服 | 伦伦影院午夜理论片 | 东京热男人av天堂 | 免费人成在线观看网站 | 丰满少妇人妻久久久久久 | 国产精品亚洲lv粉色 | 99re在线播放 | 麻豆av传媒蜜桃天美传媒 | 曰本女人与公拘交酡免费视频 | 人妻天天爽夜夜爽一区二区 | 亚无码乱人伦一区二区 | 99久久亚洲精品无码毛片 | 午夜精品一区二区三区在线观看 | 久久国语露脸国产精品电影 | 奇米影视7777久久精品人人爽 | 狠狠色丁香久久婷婷综合五月 | 天下第一社区视频www日本 | 国产97人人超碰caoprom | 欧美人与牲动交xxxx | 欧美野外疯狂做受xxxx高潮 | 精品欧美一区二区三区久久久 | 久久亚洲国产成人精品性色 | 午夜时刻免费入口 | 人人妻人人澡人人爽欧美一区 | 老司机亚洲精品影院无码 | 亚洲熟妇色xxxxx欧美老妇 | 日本高清一区免费中文视频 | 亚洲一区二区观看播放 | 色一情一乱一伦一视频免费看 | 中国女人内谢69xxxxxa片 | 中文字幕+乱码+中文字幕一区 | 国产精品va在线观看无码 | 人妻少妇精品无码专区二区 | 精品国产一区av天美传媒 | 久久人人爽人人爽人人片ⅴ | 国产av人人夜夜澡人人爽麻豆 | 中文无码成人免费视频在线观看 | 99久久人妻精品免费一区 | 婷婷六月久久综合丁香 | 成 人影片 免费观看 | 激情国产av做激情国产爱 | 日本熟妇人妻xxxxx人hd | 东京无码熟妇人妻av在线网址 | 国产成人综合色在线观看网站 | 久久aⅴ免费观看 | 久久99精品国产.久久久久 | 亚洲国产精品一区二区美利坚 | 欧美日韩色另类综合 | 国产 精品 自在自线 | 女人色极品影院 | 国精产品一品二品国精品69xx | 一本久道久久综合狠狠爱 | 日日鲁鲁鲁夜夜爽爽狠狠 | 午夜无码区在线观看 | 色综合天天综合狠狠爱 | 国产精品久久久一区二区三区 | 亚洲成av人片在线观看无码不卡 | 18无码粉嫩小泬无套在线观看 | 国产内射老熟女aaaa | 国产无av码在线观看 | аⅴ资源天堂资源库在线 | 亚洲一区二区三区在线观看网站 | 99久久久国产精品无码免费 | 国产一精品一av一免费 | 精品国产青草久久久久福利 | 性欧美牲交在线视频 | 成人免费无码大片a毛片 | 色欲av亚洲一区无码少妇 | 狠狠综合久久久久综合网 | 精品国产麻豆免费人成网站 | 亚洲精品综合五月久久小说 | 老司机亚洲精品影院无码 | 99久久久国产精品无码免费 | 色婷婷久久一区二区三区麻豆 | 波多野结衣av一区二区全免费观看 | 日产国产精品亚洲系列 | 又紧又大又爽精品一区二区 | 强辱丰满人妻hd中文字幕 | 精品熟女少妇av免费观看 | 国产口爆吞精在线视频 | 久久综合网欧美色妞网 | 国内揄拍国内精品少妇国语 | 一区二区三区乱码在线 | 欧洲 | 精品无码国产自产拍在线观看蜜 | 日本精品人妻无码免费大全 | 国产激情一区二区三区 | 中文无码伦av中文字幕 | 国产成人无码a区在线观看视频app | 亚洲日本va午夜在线电影 | 久久久久久久久蜜桃 | 国产精品va在线播放 | 亚洲中文字幕在线观看 | 国内揄拍国内精品少妇国语 | 国产激情艳情在线看视频 | 日韩亚洲欧美精品综合 | 亚洲国产精品无码久久久久高潮 | 久久久精品欧美一区二区免费 | 久久国产精品精品国产色婷婷 | 亚洲精品国产精品乱码不卡 | 亚洲综合色区中文字幕 | 亚洲欧洲无卡二区视頻 | 大屁股大乳丰满人妻 | 精品人妻av区 | аⅴ资源天堂资源库在线 | 久久精品国产日本波多野结衣 | 亚洲国产精品久久久久久 | 奇米影视7777久久精品 | 国产肉丝袜在线观看 | 亚洲日本一区二区三区在线 | 日韩欧美群交p片內射中文 | 无码一区二区三区在线观看 | 国产凸凹视频一区二区 | 国产在线一区二区三区四区五区 | 亚洲色偷偷男人的天堂 | 蜜桃av蜜臀av色欲av麻 999久久久国产精品消防器材 | 天天综合网天天综合色 | 亚洲国产午夜精品理论片 | 欧美成人免费全部网站 | а√资源新版在线天堂 | 粗大的内捧猛烈进出视频 | 国产小呦泬泬99精品 | 亚洲成av人片天堂网无码】 | 人人澡人摸人人添 | av香港经典三级级 在线 | 国产成人无码一二三区视频 | 国产超级va在线观看视频 | 成人精品视频一区二区 | 久久久精品成人免费观看 | 国产亚洲美女精品久久久2020 | 日韩成人一区二区三区在线观看 | 无码人妻精品一区二区三区不卡 | 激情五月综合色婷婷一区二区 | 久久久久成人片免费观看蜜芽 | 亚洲人交乣女bbw | 欧洲精品码一区二区三区免费看 | 久久国产劲爆∧v内射 | 精品久久久中文字幕人妻 | 日产精品高潮呻吟av久久 | 亚洲欧洲中文日韩av乱码 | 国产成人无码一二三区视频 | 久久99热只有频精品8 | 国产69精品久久久久app下载 | 日本乱偷人妻中文字幕 | 欧美人与动性行为视频 | 秋霞特色aa大片 | 亚洲熟熟妇xxxx | 久久久无码中文字幕久... | 欧美性色19p | 亚洲の无码国产の无码影院 | 最近免费中文字幕中文高清百度 | 无遮无挡爽爽免费视频 | 亚洲日韩一区二区三区 | 国产口爆吞精在线视频 | 久久国产精品精品国产色婷婷 | 性开放的女人aaa片 | 久久久精品欧美一区二区免费 | 人人妻人人澡人人爽欧美精品 | √8天堂资源地址中文在线 | 永久免费精品精品永久-夜色 | 激情内射日本一区二区三区 | 久久精品中文字幕一区 | 亚洲の无码国产の无码步美 | 日本精品高清一区二区 | 18精品久久久无码午夜福利 | 日本www一道久久久免费榴莲 | 99久久99久久免费精品蜜桃 | 日韩人妻少妇一区二区三区 | 色婷婷综合中文久久一本 | 一本久道久久综合婷婷五月 | 无码人妻丰满熟妇区毛片18 | 欧美自拍另类欧美综合图片区 | 亚洲国产av精品一区二区蜜芽 | 国产一区二区不卡老阿姨 | 久久成人a毛片免费观看网站 | 丰满肥臀大屁股熟妇激情视频 | 亚洲精品综合一区二区三区在线 | 久久国产36精品色熟妇 | 精品一区二区三区波多野结衣 | 国产97在线 | 亚洲 | www成人国产高清内射 | 久久久中文久久久无码 | 久久精品无码一区二区三区 | 中文字幕无码av激情不卡 | 一本无码人妻在中文字幕免费 | 97精品国产97久久久久久免费 | 色婷婷久久一区二区三区麻豆 | 亚洲国产成人a精品不卡在线 | 激情国产av做激情国产爱 | 无码成人精品区在线观看 | 国产亚洲精品久久久久久久 | 欧美肥老太牲交大战 | 无码av岛国片在线播放 | 亚洲精品国产精品乱码不卡 | 久久aⅴ免费观看 | 欧美兽交xxxx×视频 | 国产精品国产自线拍免费软件 | 精品午夜福利在线观看 | 国产婷婷色一区二区三区在线 | 内射巨臀欧美在线视频 | 久久精品女人的天堂av | 久久久国产精品无码免费专区 | 国产区女主播在线观看 | 久久精品成人欧美大片 | 日韩av无码一区二区三区不卡 | 狂野欧美性猛交免费视频 | 成人亚洲精品久久久久 | 又大又硬又爽免费视频 | 性欧美熟妇videofreesex | 国产精品久久久久久久9999 | 亚洲欧美国产精品专区久久 | 日本大乳高潮视频在线观看 | 精品国产青草久久久久福利 | yw尤物av无码国产在线观看 | 中文字幕中文有码在线 | 久久精品丝袜高跟鞋 | 无码国模国产在线观看 | 福利一区二区三区视频在线观看 | 亚洲 a v无 码免 费 成 人 a v | 狂野欧美性猛交免费视频 | 久久午夜无码鲁丝片秋霞 | 国产亚洲美女精品久久久2020 | 亚洲人成网站在线播放942 | 国产精品多人p群无码 | 国产在线无码精品电影网 | 中文字幕色婷婷在线视频 | 亚洲日韩av一区二区三区四区 | 波多野结衣av一区二区全免费观看 | 国产偷自视频区视频 | 丰满人妻被黑人猛烈进入 | 久久亚洲中文字幕无码 | 国内精品一区二区三区不卡 | 亚洲国产欧美在线成人 | 久久视频在线观看精品 | 一本精品99久久精品77 | 精品久久久久久人妻无码中文字幕 | 久久精品一区二区三区四区 | 精品一区二区三区波多野结衣 | 性欧美熟妇videofreesex | 成人免费无码大片a毛片 | 国产亲子乱弄免费视频 | 国产精品久久久久7777 | 激情亚洲一区国产精品 | 无码一区二区三区在线观看 | 精品偷自拍另类在线观看 | 精品 日韩 国产 欧美 视频 | 亚洲色成人中文字幕网站 | 亚洲区小说区激情区图片区 | 丰满妇女强制高潮18xxxx | 亚洲gv猛男gv无码男同 | 日韩在线不卡免费视频一区 | 99久久亚洲精品无码毛片 | 国产麻豆精品一区二区三区v视界 | 乌克兰少妇xxxx做受 | 欧美35页视频在线观看 | 蜜臀av在线播放 久久综合激激的五月天 | 在线播放无码字幕亚洲 | 又色又爽又黄的美女裸体网站 | 一本久久伊人热热精品中文字幕 | 在线a亚洲视频播放在线观看 | 两性色午夜免费视频 | 51国偷自产一区二区三区 | 亚洲男人av天堂午夜在 | 无码av免费一区二区三区试看 | 亚洲精品www久久久 |