代碼優化:優化的最佳方式
已發表: 2022-03-11性能優化是代碼面臨的最大威脅之一。
你可能在想,不是那些人中的另一個人。 我明白。 從詞源來看,任何類型的優化顯然都應該是一件好事,所以自然而然地,你想擅長它。
不僅僅是為了讓自己從人群中脫穎而出,成為一個更好的開發者。 不僅是為了避免在The Daily WTF上成為“Dan”,還因為您認為代碼優化是正確的做法。 你為你的工作感到自豪。
計算機硬件越來越快,軟件越來越容易製作,但無論你只是想能夠做什麼簡單的事情,該死的總是比上一次花費更長的時間。 你對這種現象搖頭(順便說一句,被稱為沃斯定律),並決心逆勢而上。
那是你的高尚,但停止。
停下來!
無論你在編程方面有多麼有經驗,你都面臨著阻礙自己目標的最大危險。
為何如此? 讓我們備份。
首先,什麼是代碼優化?
通常,當我們定義它時,我們假設我們希望代碼性能更好。 我們說代碼優化是編寫或重寫代碼,使程序使用盡可能少的內存或磁盤空間,最大限度地減少其 CPU 時間或網絡帶寬,或充分利用額外的內核。
在實踐中,我們有時會默認使用另一個定義:編寫更少的代碼。
但是,您為此目標編寫的先發製人的壞蛋代碼更有可能成為某人的眼中釘。 誰的? 下一個必須理解您的代碼的倒霉人,甚至可能是您自己。 像你這樣聰明有能力的人可以避免自我破壞:保持你的目標崇高,但重新評估你的手段,儘管它們似乎毫無疑問是直覺的。
所以代碼優化是一個有點模糊的術語。 那是在我們甚至考慮可以優化代碼的其他一些方法之前,我們將在下面。
讓我們先聽聽賢者的建議,一起探索傑克遜著名的代碼優化規則:
- 不要這樣做。
- (僅限專家!)不要這樣做。
1. 不要這樣做:引導完美主義
我將從一個相當令人尷尬的極端示例開始,很久以前,我剛剛涉足 SQL 的美妙世界,吃蛋糕吃吧。 問題是,然後我踩到了蛋糕,不想再吃了,因為它濕了,開始聞起來像腳。
我剛剛在 SQL 的美妙的、吃蛋糕和吃它的世界中沾沾自喜。 問題是,然後我踩到了蛋糕……
等待。 讓我從我剛剛提出並解釋的比喻的車禍中解脫出來。
我在做一個 Intranet 應用程序的研發,我希望有一天它可以成為我工作的小企業的一個完全集成的管理系統。 它將為他們跟踪所有內容,並且與他們當時的系統不同,它永遠不會丟失他們的數據,因為它將由 RDBMS 支持,而不是其他開發人員使用的片狀本土平面文件。 我想從一開始就盡可能智能地設計一切,因為我有一張白紙。 這個系統的想法在我的腦海中像煙花一樣爆炸,我開始設計表格——聯繫人及其用於 CRM、會計模塊、庫存、採購、CMS 和項目管理的許多上下文變體,我很快就會對其進行測試。
這一切都停止了,開發和性能方面,因為......你猜對了,優化。
我看到對象(表示為表格行)在現實世界中可能有許多不同的相互關係,我們可以從跟踪這些關係中受益:我們將保留更多信息,最終可以在整個地方自動化業務分析。 將此視為一個工程問題,我做了一些看起來像是系統靈活性優化的事情。
在這一點上,看好你的臉很重要,因為如果你的手掌受傷了,我將不承擔任何責任。 準備好? 我創建了兩個表: relationship
和一個外鍵引用的表, relationship_type
。 relationship
可以引用整個數據庫中任意兩行,並描述它們之間關係的性質。
天啊。 我剛剛優化了那種該死的靈活性。
太多了,事實上。 現在我遇到了一個新問題:給定的relationship_type
在每個給定的行組合之間自然沒有意義。 雖然一個person
與一個company
有一個employed by
關係可能是有道理的,但這在語義上永遠不可能等同於兩個document
之間的關係。
好的,沒問題。 我們只需將兩列添加到relationship_type
,指定此關係可以應用於哪些表。 (如果您猜到我考慮通過將這兩列移動到引用relationship_type.id
的新表來規範化這一點,那麼這裡的獎勵點,以便在語義上適用於一對以上表的關係不會重複表名。畢竟,如果我需要更改表名並且忘記在所有適用的行中更新它,它可能會產生錯誤!回想起來,至少錯誤會為棲息在我頭骨上的蜘蛛提供食物。)
謝天謝地,在沿著這條路走得太遠之前,我在一場線索風暴中昏迷不醒。 當我醒來時,我意識到我已經或多或少地在自身之上重新實現了 RDBMS 的內部外鍵相關表。 通常我會享受那些以我大搖大擺地宣布“我是如此元”而結束的時刻,但不幸的是,這不是其中之一。 忘記擴展失敗吧——這種設計的可怕膨脹使我仍然很簡單的應用程序的後端幾乎無法使用,它的數據庫幾乎沒有任何測試數據。
讓我們稍等片刻,看看這裡的眾多指標中的兩個。 一是靈活性,這是我的既定目標。 在這種情況下,我的優化,本質上是架構,甚至還為時過早:
(我們將在我最近發表的文章《如何避免過早優化的詛咒》中對此進行更多討論。)儘管如此,我的解決方案由於過於靈活而失敗了。 另一個指標,可擴展性,是我什至還沒有考慮過的指標,但它成功地摧毀了至少同樣引人注目的附帶損害。
沒錯,“哦”。
這對我來說是一個強有力的教訓,告訴我優化是如何完全出錯的。 我的完美主義徹底崩潰:我的聰明才智讓我提出了我做過的最客觀不聰明的解決方案之一。
優化你的習慣,而不是你的代碼
當您發現自己甚至在擁有工作原型和測試套件以證明其正確性之前就傾向於重構時,請考慮在哪裡可以引導這種衝動。 Sudoku 和 Mensa 很棒,但也許可以直接使您的項目受益的東西會更好:
- 安全
- 運行時穩定性
- 清晰度和風格
- 編碼效率
- 測試有效性
- 剖析
- 您的工具包/DE
- 乾燥(不要重複自己)
但請注意:優化其中任何一個特定的結果都會以犧牲其他人為代價。 至少,它是以時間為代價的。
在這裡很容易看出編寫代碼有多少藝術。 對於上述任何一項,我可以告訴你關於它的太多或太少被認為是錯誤的選擇的故事。 誰在做這裡的思考也是上下文的重要組成部分。
例如,關於 DRY:在我的一份工作中,我繼承了一個代碼庫,其中至少 80% 是冗餘語句,因為它的作者顯然不知道如何以及何時編寫函數。 另外 20% 的代碼是令人困惑的自相似。
我的任務是為其添加一些功能。 一個這樣的特性需要在所有要實現的代碼中重複,並且任何未來的代碼都必須仔細複製粘貼才能使用新特性。
顯然,它需要重構只是為了我自己的理智(高價值)和任何未來的開發人員。 但是,因為我是代碼庫的新手,所以我首先編寫了測試,以確保我的重構不會引入任何回歸。 事實上,他們就是這樣做的:我在腳本生成的所有 gobbledygook 輸出中發現了兩個我不會注意到的錯誤。
最後,我認為我做得很好。 重構之後,我用幾行簡單的代碼實現了一個被認為是困難的功能,給我的老闆留下了深刻的印象。 此外,代碼的整體性能要高出一個數量級。 但沒過多久,同樣的老闆告訴我我太慢了,項目應該已經完成了。 翻譯:編碼效率是一個更高的優先級。
當心:優化任何特定 [方面] 都會以犧牲其他人為代價。 至少,它是以時間為代價的。
我仍然認為我在那裡選擇了正確的課程,即使當時我的老闆沒有直接讚賞代碼優化。 如果沒有重構和測試,我認為實際上需要更長的時間才能真正正確——即,專注於編碼速度實際上會阻礙它。 (嘿,這就是我們的主題!)
將此與我在一個小型副項目中所做的一些工作進行對比。 在項目中,我正在嘗試一個新的模板引擎,並希望從一開始就養成良好的習慣,儘管嘗試新的模板引擎並不是項目的最終目標。
當我注意到我添加的幾個塊彼此非常相似,而且每個塊需要引用同一個變量 3 次時,我腦海中的 DRY 鈴聲響起,我開始尋找正確的方法來做我試圖用這個模板引擎做的事情。
事實證明,經過幾個小時無果而終的調試,模板引擎目前無法以我想像的方式實現這一點。 不僅沒有完美的DRY 解決方案; 根本沒有任何DRY 解決方案!
試圖優化我的這一價值,我完全破壞了我的編碼效率和我的幸福感,因為這條彎路讓我的項目失去了那天本來可以取得的進展。
即使那樣,我完全錯了嗎? 有時值得進行一些投資,尤其是在新的技術背景下,更早地而不是晚地了解最佳實踐。 更少的代碼重寫和壞習慣撤消,對吧?
不,我認為即使尋找一種方法來減少代碼中的重複也是不明智的——這與我在之前的軼事中的態度形成鮮明對比。 原因是環境就是一切:我正在一個小型遊戲項目中探索一項新技術,而不是長期安定下來。 一些額外的行和重複不會傷害任何人,但是失去焦點會傷害我和我的項目。
等等,所以尋求最佳實踐可能是一個壞習慣? 有時。 如果我的主要目標是學習新引擎,或者學習一般性的學習,那麼這將是值得花時間的:修補,找到限制,通過研究發現不相關的功能和陷阱。 但我忘記了這不是我的主要目標,這讓我付出了代價。
就像我說的,這是一門藝術。 這種藝術的發展得益於提醒,不要這樣做。 它至少讓你考慮在你工作時哪些價值觀在起作用,哪些價值觀在你的環境中對你最重要。
第二條規則呢? 我們什麼時候可以真正優化?
2. 不要這樣做:有人已經這樣做了
好的,無論是您還是其他人,您都會發現您的架構已經設置好,數據流已經被考慮和記錄,是時候編寫代碼了。
讓我們更進一步:不要編寫代碼。
這本身可能聞起來像過早的優化,但它是一個重要的例外。 為什麼? 為了避免可怕的 NIHS 或“未在此處發明”綜合症——假設您的優先事項包括代碼性能和最小化開發時間。 如果不是,如果您的目標完全以學習為導向,您可以跳過下一部分。
雖然人們可能會出於狂妄自大而重新發明方輪,但我相信像你我這樣誠實、謙遜的人可能會因為不了解我們可用的所有選項而犯這個錯誤。 了解堆棧中每個 API 和工具的每個選項,並隨著它們的發展和發展而掌握它們,這當然是一項艱鉅的工作。
但是,投入這段時間是讓您成為專家的原因,並使您不會成為 CodeSOD 上第 10 億人,因為他們對日期時間計算器或字符串操縱器的迷人表現所留下的破壞痕跡而受到詛咒和嘲笑。
(這個通用模式的一個很好的對比是舊的 Java Calendar
API,但它已經被修復了。)
檢查您的標準庫,檢查您的框架的生態系統,檢查是否已經解決了您的問題的 FOSS
很有可能,您正在處理的概念具有非常標準且眾所周知的名稱,因此快速的互聯網搜索將為您節省大量時間。
例如,我最近準備對棋盤遊戲的 AI 策略進行一些分析。 一天早上醒來,我意識到如果我簡單地使用我記得的某個組合學概念,我正在計劃的分析可以更有效地完成幾個數量級。 此時我對自己弄清楚這個概念的算法不感興趣,我已經知道要搜索的正確名稱了。 然而,我發現經過大約 50 分鐘的研究並嘗試了一些初步代碼,我並沒有設法將我發現的半成品偽代碼變成正確的實現。 (你能相信有一篇博客文章作者假設錯誤的算法輸出,錯誤地實現算法以匹配假設,評論者指出這一點,然後幾年後,它仍然沒有修復?)那時,我的早茶開始,我搜索了[name of concept] [my programming language]
。 30 秒後,我從 GitHub 獲得了可證明正確的代碼,並開始著手我真正想做的事情。 只是變得具體並包括語言,而不是假設我必須自己實現它,這意味著一切。
是時候設計數據結構和實現算法了
…再次,不要打代碼高爾夫。 在實際項目中優先考慮正確性和清晰度。
好的,你已經看過了,沒有任何東西可以解決你的問題,內置到你的工具鏈中,或者在網絡上獲得自由許可。 你推出你自己的。

沒問題。 建議很簡單,按以下順序:
- 設計它,以便向新手程序員解釋它會很簡單。
- 編寫一個符合該設計產生的期望的測試。
- 編寫代碼,以便新手程序員可以輕鬆地從中收集設計。
簡單,但可能很難遵循。 這就是編碼習慣和代碼氣味以及藝術、工藝和優雅發揮作用的地方。 此時您正在做的事情顯然有工程方面的問題,但同樣,不要玩代碼高爾夫。 在實際項目中優先考慮正確性和清晰度。
如果您喜歡視頻,這里或多或少是遵循上述步驟的人之一。 對於不喜歡視頻的人,我總結一下:這是 Google 求職面試中的算法編碼測試。 受訪者首先以易於溝通的方式設計算法。 在編寫任何代碼之前,有一個工作設計所期望的輸出示例。 那麼代碼自然如下。
至於測試本身,我知道在某些圈子裡,測試驅動開發可能會引起爭議。 我認為部分原因是它可能被過度使用,虔誠地追求到犧牲開發時間的地步。 (再一次,從一開始就試圖過多地優化一個變量,這是在自取其辱。)即使是 Kent Beck 也沒有將 TDD 推向如此極端,他發明了極限編程並寫了關於 TDD 的書。 所以從一些簡單的事情開始,以確保你的輸出是正確的。 畢竟,無論如何,您都會在編碼後手動執行此操作,對嗎? (如果您是一位搖滾明星程序員,以至於在第一次編寫代碼後甚至不運行代碼,我深表歉意。在這種情況下,也許您會考慮讓代碼的未來維護者進行測試,這樣您就知道他們不會打破你的真棒實現。)因此,你已經讓計算機為你完成了這項工作,而不是進行手動的視覺差異,並進行適當的測試。
在實現算法和數據結構的相當機械的過程中,避免進行逐行優化,甚至不要考慮使用自定義的低級語言 extern(如果您使用 C 編碼,則使用彙編,如果您使用 C '正在使用 Perl 等進行編碼)。 原因很簡單:如果你的算法被完全替換了——直到過程後期你才知道是否需要這樣做——那麼你的低級優化工作最終將沒有效果。
一個 ECMAScript 示例
在優秀的社區代碼審查網站 exercism.io 上,我最近發現了一個明確建議嘗試優化重複數據刪除或清晰度的練習。 我針對重複數據刪除進行了優化,只是為了展示如果你採取 DRY 會變得多麼荒謬——如上所述,這是一種有益的編碼思維方式——太過分了。 這是我的代碼的樣子:
const zeroPhrase = "No more"; const wallPhrase = " on the wall"; const standardizeNumber = number => { if (number === 0) { return zeroPhrase; } return '' + number; } const bottlePhrase = number => { const possibleS = (number === 1) ? '' : 's'; return standardizeNumber(number) + " bottle" + possibleS + " of beer"; } export default class Beer { static verse(number) { const nextNumber = (number === 0) ? 99 : (number - 1); const thisBottlePhrase = bottlePhrase(number); const nextBottlePhrase = bottlePhrase(nextNumber); let phrase = thisBottlePhrase + wallPhrase + ", " + thisBottlePhrase.toLowerCase() + ".\n"; if (number === 0) { phrase += "Go to the store and buy some more"; } else { const bottleReference = (number === 1) ? "it" : "one"; phrase += "Take " + bottleReference + " down and pass it around"; } return phrase + ", " + nextBottlePhrase.toLowerCase() + wallPhrase + ".\n"; } static sing(start = 99, end = 0) { return Array.from(Array(start - end + 1).keys()).map(offset => { return this.verse(start - offset); }).join('\n'); } }
那裡幾乎沒有任何重複的字符串! 通過這樣寫,我為啤酒歌曲手動實現了一種文本壓縮形式(但僅限於啤酒歌曲)。 究竟有什麼好處? 好吧,假設你想唱關於用罐頭而不是瓶子喝啤酒的歌。 我可以通過將一個單一的bottle
實例更改為can
來實現這一點。
好的!
…正確的?
不,因為那時所有的測試都會中斷。 好的,這很容易解決:我們將在單元測試規範中搜索和替換bottle
。 這與首先對代碼本身執行此操作一樣容易,並且存在無意中破壞事物的風險。
同時,我的變量後來會被奇怪地命名,像bottlePhrase
這樣的東西根本與瓶子沒有任何關係。 避免這種情況的唯一方法是準確預見將要進行的更改類型,並在我的變量名稱中使用更通用的術語,例如vessel
或container
代替bottle
。
以這種方式面向未來的智慧是非常值得懷疑的。 你想要改變任何東西的機率是多少? 如果你這樣做了,你所做的改變會如此方便嗎? 在bottlePhrase
示例中,如果您想本地化為具有兩種以上複數形式的語言怎麼辦? 沒錯,重構時間,代碼可能看起來更糟。
但是,當您的需求確實發生了變化,並且您不只是試圖預測它們時,那麼也許是時候進行重構了。 或者,也許您仍然可以推遲:實際上,您將添加多少船隻類型或本地化? 無論如何,當您需要平衡重複數據刪除和清晰度時,非常值得觀看 Katrina Owen 的演示。
回到我自己的醜陋例子:不用說,重複數據刪除的好處在這裡甚至都沒有得到充分體現。 同時,它的成本是多少?
除了一開始需要更長的時間來編寫之外,現在閱讀、調試和維護也變得不那麼簡單了。 想像一下允許適度重複的可讀性級別。 例如,將四個經文變體中的每一個都拼出來。
但是我們還沒有優化!
現在您的算法已經實現,並且您已經證明它的輸出是正確的,恭喜! 你有底線!
最後,是時候……優化了,對吧? 不,還是不要這樣做。 是時候拿你的基線做一個很好的基準了。 為您的期望設置一個閾值並將其粘貼在您的測試套件中。 然後,如果有什麼東西突然讓這段代碼變慢了——即使它仍然可以工作——你會在它出現之前知道的。
仍然推遲優化,直到您實現了完整的相關用戶體驗。 在那之前,您可能針對的是與您需要的完全不同的代碼部分。
去完成你的應用程序(或組件),如果你還沒有,在你去的時候設置你所有的算法基準。
完成此操作後,這是創建和基準測試涵蓋系統最常見的實際使用場景的端到端測試的好時機。
也許你會發現一切都很好。
或者,也許您已經確定,在現實生活中,某些東西太慢或占用太多內存。
OK,現在可以優化了
只有一種方法可以對此保持客觀。 是時候分解火焰圖和其他分析工具了。 有經驗的工程師可能會或可能不會比新手更頻繁地猜測,但這不是重點:確定的唯一方法是分析。 這始終是優化代碼以提高性能的過程中要做的第一件事。
您可以在給定的端到端測試期間進行概要分析,以了解真正產生最大影響的因素。 (稍後,在部署之後,監控使用模式是一種很好的方式,可以讓您了解系統的哪些方面與未來最相關。)
請注意,您並沒有嘗試完全深入地使用分析器 - 通常,您更多的是尋找函數級分析而不是語句級分析,因為此時您的目標只是找出哪個算法是瓶頸.
現在您已經使用分析來確定係統的瓶頸,現在您可以實際嘗試優化,確信您的優化是值得的。 您還可以證明您的嘗試有多有效(或無效),這要歸功於您在此過程中所做的基線基準測試。
整體技術
首先,請記住盡可能長時間地保持高水平:
你知道嗎? 終極通用優化技巧,適用於所有情況:
— Lars Doucet (@larsiusprime) 2017 年 3 月 30 日
- 少畫一些東西
- 更新更少的東西
在整個算法級別,一種技術是強度降低。 但是,在將循環簡化為公式的情況下,請注意留下評論。 不是每個人都知道或記得每一個組合公式。 另外,在使用數學時要小心:有時你認為可能會降低力量的結果最終不是。 例如,假設x * (y + z)
有一些明確的算法含義。 如果你的大腦在某個時候被訓練過,無論出於何種原因,自動取消對類似術語的分組,你可能會想將其重寫為x * y + x * z
。 一方面,這在讀者和已經存在的清晰算法含義之間設置了障礙。 (更糟糕的是,由於需要額外的乘法運算,它現在實際上效率較低。這就像循環展開只是弄髒了褲子。)無論如何,快速記錄一下你的意圖會有很長的路要走,甚至可能幫助你看到你的在你提交之前自己的錯誤。
無論您是使用公式還是只是用另一種基於循環的算法替換基於循環的算法,您都可以測量差異。
但也許你可以通過改變你的數據結構來獲得更好的性能。 了解您需要對正在使用的結構以及任何替代方案執行的各種操作之間的性能差異。 也許哈希在您的上下文中工作看起來有點混亂,但是優越的搜索時間值得它超過數組嗎? 這些是由您決定的權衡類型。
您可能會注意到,這歸結為在調用便利函數時知道哪些算法正在代表您執行。 所以歸根結底,這實際上與降低強度是一回事。 了解供應商的庫在幕後所做的工作不僅對性能至關重要,而且對避免無意的錯誤也很重要。
微優化
好的,您的系統功能已經完成,但是從用戶體驗的角度來看,性能可以進一步微調。 假設您已經盡了最大努力,那麼是時候考慮一下我們迄今為止一直在避免的優化了。 考慮一下,因為這種優化水平仍然是對清晰度和可維護性的權衡。 但是您已經決定是時候了,所以繼續進行語句級別的分析,現在您處於整個系統的上下文中,這實際上很重要。
就像您使用的庫一樣,在編譯器或解釋器的層面上,為了您的利益,已經投入了無數的工程時間。 (畢竟,編譯器優化和代碼生成本身就是巨大的話題)。 甚至在處理器級別也是如此。 試圖在不知道最低級別發生了什麼的情況下優化代碼就像認為擁有四輪驅動意味著您的車輛也可以更輕鬆地停止。
除此之外,很難給出好的通用建議,因為這實際上取決於您的技術堆棧以及您的分析器所指向的內容。 但是,因為您正在測量,如果解決方案不能從問題上下文中有機地和直觀地呈現給您,您已經處於尋求幫助的絕佳位置。 (睡眠和花時間思考其他事情也有幫助。)
此時,根據上下文和擴展要求,Jeff Atwood 可能會建議簡單地添加硬件,這可能比開發人員的時間更便宜。
也許你不會走那條路。 在這種情況下,探索各種類型的代碼優化技術可能會有所幫助:
- 緩存
- Bit hacks 和那些特定於 64 位環境的
- 循環優化
- 內存層次優化
進一步來說:
- C 和 C++ 中的代碼優化技巧
- Java中的代碼優化技巧
- 優化 .NET 中的 CPU 使用率
- ASP.NET Web 場緩存
- SQL 數據庫調優或特別是調優 Microsoft SQL Server
- 縮放 Scala 的遊戲! 框架
- 高級 WordPress 性能優化
- 使用 JavaScript 原型和範圍鏈進行代碼優化
- 優化 React 性能
- iOS動畫效率
- Android 性能提示
無論如何,我確實為您提供了一些注意事項:
不要為多個不同的目的重用一個變量。 就可維護性而言,這就像在沒有油的情況下運行汽車。 只有在最極端的嵌入式情況下,這才有意義,即使在那些情況下,我認為它不再有意義。 這是編譯器組織的工作。 自己做,然後移動一行代碼,你就引入了一個錯誤。 節省內存的幻想對你來說值得嗎?
不要在不知道原因的情況下使用宏和內聯函數。 是的,函數調用開銷是一種成本。 但是避免它通常會使您的代碼更難調試,有時實際上會使它變慢。 偶爾使用這種技術只是因為它是一個好主意,這就是金鎚的一個例子。
不要手動展開循環。 同樣,這種形式的循環優化幾乎總是通過像編譯這樣的自動化過程得到更好的優化,而不是犧牲代碼的可讀性。
最後兩個代碼優化示例具有諷刺意味的是,它們實際上可能是反性能的。 當然,由於您正在執行基準測試,因此您可以為您的特定代碼證明或反駁這一點。 但即使你看到了性能提升,也請回到藝術方面,看看在可讀性和可維護性方面的收穫是否值得。
這是你的:優化優化
嘗試性能優化可能是有益的。 但是,通常情況下,它做得非常過早,會帶來一連串不良的副作用,而且最諷刺的是,它會導致性能下降。 我希望你對優化的藝術和科學,最重要的是,它的適當背景有了更廣泛的理解。
如果這能幫助我們擺脫從一開始就編寫完美代碼的想法,轉而編寫正確的代碼,我會很高興。 我們必須記住自上而下進行優化,證明瓶頸在哪裡,並在修復它們之前和之後進行測量。 這是優化優化的最佳、最優策略。 祝你好運。