化實戰(zhàn))
MATLAB這個工具說簡單也簡單說難也難。你讓它幫你算個矩陣、畫個圖那確實是輕松愉快但一旦代碼超過幾百行或者你開始處理大規(guī)模數(shù)據(jù)、復(fù)雜算法各種報錯和卡頓就接踵而來。我這幾年用MATLAB寫過不少小工具和原型驗證項目從最開始被報錯整到懷疑人生到后來慢慢摸清楚它的脾氣踩過的坑確實不少。這期間我最大的感受就是MATLAB很像一個固執(zhí)的助教——它要求你按它規(guī)定的思路來一旦你代碼風(fēng)格混亂或者邏輯模糊它就會用一長串英文紅字教你做人。這篇“MATLAB編程全攻略從報錯到優(yōu)化”的東西就是想把我這些年實際解決MATLAB問題、優(yōu)化代碼性能的經(jīng)驗做個系統(tǒng)整理。我會從日常寫碼規(guī)范、常見報錯的類型和根因、調(diào)試工具的使用思路再到性能分析和代碼加速這幾個方向逐一拆開講內(nèi)容偏實戰(zhàn)適合已經(jīng)會用MATLAB基礎(chǔ)語法、但想進一步提升代碼質(zhì)量和運行效率的人也適合正在被各種調(diào)試報錯折磨、想系統(tǒng)性找出一條查錯路線的新手。1. 代碼規(guī)范與整體設(shè)計思路1.1 一段代碼為什么跑得慢、錯得冤很多人在MATLAB里寫代碼基本就是打開編輯器想到哪寫到哪一連串腳本從頭擼到尾。這種寫法不是不行但當(dāng)項目一復(fù)雜問題就全來了。我在處理一個信號處理Demo的時候當(dāng)時寫了一個幾百行的腳本中間改參數(shù)、調(diào)濾波器的截止頻率都要在代碼里找到對應(yīng)行手改數(shù)值改完還要擔(dān)心有沒有忘了初始化某個變量。結(jié)果項目還沒驗收我自己先被繞暈了。后來我強迫自己把腳本按功能拆解成一個個函數(shù)模塊每個函數(shù)一個獨立文件主程序只管數(shù)據(jù)輸入、調(diào)用函數(shù)、展示結(jié)果。這么一拆思路瞬間清晰改一個濾波器模型只需要替換一個函數(shù)文件其他模塊完全不受影響。這里我想強調(diào)的是MATLAB不僅是一門計算語言它的核心優(yōu)勢在于交互式探索和快速原型驗證。如果你把MATLAB當(dāng)成C那種大型工程語言來寫仍然可以靠模塊化設(shè)計來規(guī)避風(fēng)險但如果你把它當(dāng)成一個隨時運行一遍的草稿本那至少要有基本的“干凈”意識——那就是腳本里不要留下大量無用的注釋掉的老代碼不要一上來就寫一個包含所有邏輯的大while循環(huán)更不要在全局變量滿天飛的環(huán)境下調(diào)試。這些壞習(xí)慣往往是各種詭異報錯和性能瓶頸的起點。1.2 規(guī)范化設(shè)計與依賴管理在實際做項目時我一般會遵循一套很簡單的結(jié)構(gòu)主腳本負(fù)責(zé)“流程”核心邏輯都放在函數(shù)或類里。舉個我實際用過的例子某個批量圖像處理的原型驗證項目我一開始是寫一個腳本遍歷所有圖片然后里面直接寫了灰度轉(zhuǎn)換、濾波、閾值分割的代碼。結(jié)果就是每換一張測試圖就要重新跑一遍全部代碼中間有一個步驟單獨調(diào)參數(shù)浪費的時間極多。后來我改成四個函數(shù)文件一個負(fù)責(zé)讀取圖像一個負(fù)責(zé)預(yù)處理一個負(fù)責(zé)核心分割算法一個負(fù)責(zé)可視化結(jié)果。主腳本里就是幾行調(diào)用代碼。這樣做的好處首先是可以獨立測試每個環(huán)節(jié)某個環(huán)節(jié)出錯了報錯信息直接指向?qū)?yīng)函數(shù)定位問題從幾分鐘縮短到幾秒鐘。其次是可以復(fù)用代碼后續(xù)處理不同批次的數(shù)據(jù)只需在主腳本里改路徑和參數(shù)。最后也是最重要的這種寫法能讓你用上MATLAB的profiling工具和斷點調(diào)試因為你可以單步跟蹤核心算法函數(shù)而不是在一個巨大腳本里翻來翻去。另外關(guān)于依賴管理MATLAB社區(qū)雖然沒有像一些編程語言那樣成熟統(tǒng)一的包管理器但工具箱的路徑管理還是要養(yǎng)成習(xí)慣。我見過太多人因為不知道addpath和當(dāng)前文件夾的概念導(dǎo)致“Undefined function or variable”滿天飛。其實這個錯誤大多數(shù)時候不是代碼邏輯錯誤而是MATLAB找不到對應(yīng)的函數(shù)文件或數(shù)據(jù)文件。你只要養(yǎng)成把所有相關(guān)代碼放在同一個工程文件夾、通過“Set Path”把依賴的工具箱文件夾加進來的習(xí)慣這類問題就能減少一半以上。2. 常見報錯的類型拆解與根治方法2.1 語法與基礎(chǔ)錯誤從根源上減少低級BugMATLAB報錯里最煩人的是一些基礎(chǔ)錯誤比如括號不匹配、缺少end、變量名拼寫不一致。這類問題其實和MATLAB本身關(guān)系不大更多是寫代碼時的習(xí)慣問題。我在編寫一個數(shù)據(jù)擬合工具時經(jīng)常一段代碼里好幾個地方用了不同的變量名例如第一次寫的是data_input后面一改就變成inputDataMATLAB又不像強類型語言一樣在編譯期就攔住你結(jié)果運行到一半才發(fā)現(xiàn)變量未定義。這個階段我總結(jié)出的經(jīng)驗是寫代碼前先花一兩分鐘聲明變量清單或者直接用一種命名風(fēng)格貫穿始終camelCase也行下劃線也行但不要混用。關(guān)于語法錯誤還有一個非常有效的技巧就是善用編輯器自帶的語法檢查。新版MATLAB編輯器會在你敲代碼的同時就在旁邊標(biāo)黃警告未定義變量、標(biāo)紅提示語法錯誤不用運行就能發(fā)現(xiàn)大部分問題。這比運行時報錯之后再去一行行找高效太多。另外別忘了MATLAB里有個神奇的“Tab鍵自動補全”功能變量名和函數(shù)名可以自動補全這也能大大減少名字拼寫錯誤導(dǎo)致的報錯。我用這個功能幾年了中途幾乎很少因為變量名拼錯而抓狂。2.2 維度不匹配與索引越界吃透矩陣思維MATLAB對矩陣運算的支持是無比強大的但這也帶來一個特有的難點——矩陣維度不匹配的報錯幾乎每天都能見到。你本來想做一個矩陣點乘結(jié)果用了*MATLAB會認(rèn)為你在做矩陣乘法當(dāng)內(nèi)維不一致時就會直接報錯。這種錯誤的典型場景是做數(shù)組歸一化或統(tǒng)計時試圖把一個行向量和一個列向量相加或者把一個多維矩陣按元素操作時忘了.點運算符。我處理“Matrix dimensions must agree”這類問題的經(jīng)驗是先檢查運算符。想要逐元素相乘、相除、冪運算必須使用點運算符.*、./、.^這是新手最容易犯的錯。其次檢查兩個變量的shapeMATLAB用size()函數(shù)查看確保維度匹配。不要對著代碼猜直接在命令行窗口輸入size(變量名)一秒鐘就能看清問題。還有一種比較隱蔽的情況是有些函數(shù)默認(rèn)輸出行向量而你腦子里以為是列向量幾經(jīng)周轉(zhuǎn)后兩者一拼接就出了維度錯誤。從這個角度看建議大家在寫麻煩的矩陣操作前先畫個草圖把矩陣的維度變化流程寫出來這樣能減少至少一半的維度相關(guān)報錯。至于索引越界這里有一個我自己的慘痛教訓(xùn)。當(dāng)時我在處理一個時間序列數(shù)據(jù)循環(huán)里動態(tài)增長數(shù)組循環(huán)條件里用的還是固定的loop上限導(dǎo)致有一輪循環(huán)訪問了超出當(dāng)前數(shù)組長度的索引MATLAB直接報錯“Index exceeds the number of array elements”。這個問題的根治辦法很簡單確保循環(huán)邊界和數(shù)組長度動態(tài)匹配不要寫死上限。寫循環(huán)之前先想好這個數(shù)組的長度是否會變化如果要變化是不是可以用動態(tài)添加或者一次性預(yù)分配好空間。預(yù)分配不僅解決越界問題還能大幅度提升性能這個我們后面會專門展開。2.3 內(nèi)存與性能陷阱別被隱形瓶頸拖垮與矩陣維度錯誤相比內(nèi)存和性能相關(guān)的報錯更隱蔽。比較典型的是“Out of memory”報錯特別是在處理高清圖像、大規(guī)模仿真或深度學(xué)習(xí)數(shù)據(jù)時。我遇到過一個人臉識別Demo載入幾千張圖片逐張?zhí)幚硪驗闆]預(yù)分配存儲空間MATLAB每循環(huán)一次數(shù)組自動膨脹一次內(nèi)存碎片不斷增加跑到一半直接內(nèi)存溢出。最后我改用預(yù)分配一個大矩陣運行時間從幾分鐘驟降到十幾秒內(nèi)存占用也穩(wěn)定了。另一個我常踩的坑是在處理數(shù)據(jù)時無意中創(chuàng)建了cell數(shù)組的深層嵌套每次訪問都要經(jīng)過好幾層花括號和索引浪費大量時間。MATLAB里cell數(shù)組是“萬能容器”但訪問效率確實比普通數(shù)值數(shù)組低不少。能用數(shù)值矩陣/多維數(shù)組解決問題的盡量不要用cell數(shù)組包一層又一層。如果你確實需要存儲不同類型的數(shù)據(jù)用table或者struct也許更合適而且可讀性更高報錯也更不容易出現(xiàn)。還有一個古老但依然常見的性能問題——不預(yù)分配就用循環(huán)往數(shù)組里塞東西。MATLAB的變量是在運行時動態(tài)構(gòu)造的循環(huán)里每寫入一個新元素如果數(shù)組沒有提前分配它就要重新分配一次內(nèi)存再做一次拷貝。這種做法在循環(huán)次數(shù)少時無所謂一旦上萬次、上十萬次速度差距就極其明顯。我寫過一個蒙特卡洛模擬最初版本跑一次要十分鐘把所有數(shù)組預(yù)分配之后時間縮短到二十秒差距直接是數(shù)量級的。優(yōu)化代碼的第一步永遠(yuǎn)是先看有沒有未預(yù)分配的數(shù)組。3. 調(diào)試技巧與排錯思路3.1 斷點調(diào)試與條件斷點不再靠猜對于很多初學(xué)者來說MATLAB調(diào)試就靠disp()滿天飛這確實是一種辦法但效率太低。我在評估一個推薦系統(tǒng)算法的MATLAB實現(xiàn)時發(fā)現(xiàn)循環(huán)里某個值偶爾會出現(xiàn)NaN如果是靠打印我得盯屏幕盯到眼花才能抓出是哪一次迭代出了問題。而用編輯器里的斷點功能只需要在可能出錯的行前面點擊設(shè)置紅點運行到那里就會暫停然后能逐行執(zhí)行、查看當(dāng)前所有變量的值一眼就能看出異常出現(xiàn)在哪里。更進一步MATLAB還支持條件斷點。右鍵點擊斷點可以設(shè)置條件比如當(dāng)iter 1000或者result Inf時才觸發(fā)暫停。這個功能太實用了尤其適合找那些“偶爾出現(xiàn)一次”的問題。我用過這樣一個場景某個數(shù)組第5000次迭代時才出現(xiàn)NaN不設(shè)條件斷點的話我要嘛一步步按5000次要嘛在循環(huán)內(nèi)用if isnan判斷一下然后故意觸發(fā)一個錯誤來暫停。有了條件斷點直接設(shè)置isnan(result)MATLAB在滿足條件的那一剎那自動暫停省了我無數(shù)時間。3.2 dbstop與結(jié)構(gòu)化排錯除了GUI斷點命令行也有幾個非常方便的調(diào)試命令。dbstop if error這個命令能在報錯出現(xiàn)時自動進入調(diào)試模式直接停在出錯的那一行你可以馬上查看當(dāng)時所有變量的狀態(tài)。這是我最推薦的入門實用命令比從編輯器里啟動調(diào)試更直接。使用的方法是程序運行前先在命令行輸入dbstop if error然后運行腳本一旦報錯MATLAB會直接停在出錯行不會讓你的程序崩潰退出省去了“報錯—找行—猜原因”的拉鋸戰(zhàn)。dbstep、dbcont這些命令配合使用可以實現(xiàn)逐行跟蹤但我個人更推薦直接用編輯器里的“繼續(xù)”、“單步”、“步入”按鈕可視化更強。但我仍然建議所有重度用戶記住dbstop if error和dbstop if naninf。后者是暫停在產(chǎn)生NaN或Inf的那一行專門用來抓數(shù)值異常類問題。我一度為一個矩陣分解算法為什么出現(xiàn)NaN煩惱了好久用了dbstop if naninf后第一輪就跑到了問題現(xiàn)場發(fā)現(xiàn)是一個分母在某種參數(shù)下計算接近零導(dǎo)致的溢出。3.3 變量檢查與日志輸出這里再提供一個我自己常用的技巧把中間結(jié)果輸出到MATLAB的“工作區(qū)”或者用disp、fprintf輸出關(guān)鍵變量的尺寸、范圍和統(tǒng)計量。比如在核心函數(shù)入口處先打印一下輸入數(shù)據(jù)的size、min、max、mean這能幫你快速判斷數(shù)據(jù)是否符合預(yù)期。不要只在最后打印最終結(jié)果那樣你只能看到“錯了”卻不知道錯在哪一步。我的一些圖像處理項目里甚至?xí)衙恳徊降奶幚斫Y(jié)果都存成圖片或變量文件方便后續(xù)復(fù)盤。此外我強烈建議在調(diào)試階段用%#ok*NOPRT或類似注釋來抑制某些警告但不要大面積屏蔽所有警告。警告信息有時候比錯誤信息更有用它能提示你“雖然沒崩潰但你的代碼有隱患”。比如“變量在某次循環(huán)中改變大小”這個警告就是MATLAB在提醒你預(yù)分配性能問題。如果你屏蔽了所有警告就喪失了一個免費的性能優(yōu)化提示器。4. 性能分析與優(yōu)化實操4.1 預(yù)分配與向量化性價比最高的加速手段在MATLAB性能調(diào)優(yōu)的經(jīng)驗里我首推預(yù)分配和向量化這兩件事做得好代碼提速幾乎是立竿見影的。先說說預(yù)分配。最典型的寫法是在循環(huán)前用zeros或nan和cell創(chuàng)建好最終大小的數(shù)組然后在循環(huán)里給元素賦值。比如你要存儲10000次模擬的結(jié)果一開始就定義results zeros(1, 10000);循環(huán)里results(i) ...。這看起來是很簡單的一件事但對運行速度的影響極大。前面我提到過一個蒙特卡洛模擬的案例沒預(yù)分配之前跑一次要十分鐘預(yù)分配之后變成二十秒原因就在于避免了反復(fù)的內(nèi)存重分配和元素拷貝。向量化則是把很多循環(huán)操作盡量改成矩陣運算。比如要對一個數(shù)組每個元素做平方再加一不要寫for i 1:length(x); y(i) x(i)^2 1; end直接寫y x.^2 1;就好了。MATLAB的底層是高度優(yōu)化的矩陣運算庫直接對整個數(shù)組運算能調(diào)用大量并行加速遠(yuǎn)比一行一行循環(huán)快。我在一個圖像批量處理Demo里用向量化重寫了像素級的歸一化操作原本要遍歷每張圖的每個像素耗時巨長改成對整個矩陣做歸一化處理之后時間縮短到原來的十幾分之一。當(dāng)然向量化也要適度。有些算法本質(zhì)上是迭代的、依賴上一個時刻狀態(tài)的強行矩陣化只會寫出難以看懂的代碼。遇到這種情況我的原則是如果循環(huán)次數(shù)少直接寫循環(huán)就行如果循環(huán)次數(shù)多且可以被矩陣改寫那就優(yōu)先向量化如果循環(huán)次數(shù)多且不得不保留迭代結(jié)構(gòu)那就考慮預(yù)分配加高效代碼或者直接考慮并行化。4.2 基準(zhǔn)測試與Profile工具讓數(shù)據(jù)說話做優(yōu)化之前我建議一定要先做性能分析不要靠感覺猜哪段代碼是瓶頸。我見過有人辛辛苦苦優(yōu)化了一個函數(shù)的計算邏輯結(jié)果profile一跑發(fā)現(xiàn)那部分代碼占總耗時還不到5%真正耗時的在文件讀取或數(shù)據(jù)復(fù)制上等于做了無用功。在MATLAB中性能分析的神器就是profile on; your_script; profile viewer;。運行完之后會生成一個詳細(xì)報告列出每個函數(shù)被調(diào)用的次數(shù)、自耗時、總耗時。如果某個函數(shù)或某一行耗時特別長它還會標(biāo)注出來。我第一次用這個工具時才發(fā)現(xiàn)自己寫的一個“很復(fù)雜”的核心算法耗時其實只占20%而數(shù)據(jù)從一個大矩陣?yán)镏鹆腥〕鰜砥唇拥牟糠址炊亲詈臅r的。知道這一點之后我針對性地優(yōu)化了數(shù)據(jù)訪問方式總體性能直接提升了一倍。對于更精細(xì)的基準(zhǔn)測試可以用timeit函數(shù)來測量某個函數(shù)的單次運行時間這個函數(shù)會自動多次運行求中位數(shù)結(jié)果比tic/toc穩(wěn)定得多。tic/toc適合快速測試但要警惕在同一腳本里多個計時段互相干擾。我的習(xí)慣是大框架用tic/toc看個大概函數(shù)級別或算法版本對比用timeit做精確評估。4.3 并行與工具箱權(quán)衡優(yōu)化不是只有向量化當(dāng)你的代碼經(jīng)過預(yù)分配、向量化優(yōu)化之后如果性能還不夠就可以考慮并行計算。MATLAB的parfor可以把循環(huán)并行化執(zhí)行。但要注意parfor不是萬能的它要求每次循環(huán)迭代必須相互獨立不能依賴其他迭代的結(jié)果。我在一個參數(shù)掃描項目中試過parfor把最耗時的參數(shù)組合計算分散到多核上八核系統(tǒng)下確實快了不少但前提是每次迭代的輸入數(shù)據(jù)和輸出結(jié)果都是獨立的變量。并行計算還有一點要注意不是所有環(huán)境都支持Parallel Computing Toolbox。所以我在交付項目時通常會在代碼里寫一個判斷如果用戶沒有并行工具箱就自動回退到普通for循環(huán)保證在只有基礎(chǔ)MATLAB的環(huán)境里也能正常運行。這個兼容性思路對于做跨平臺或跨機構(gòu)分享的代碼尤為重要。除了并行工具箱權(quán)衡也很重要。MATLAB自帶的各種工具箱比如統(tǒng)計、優(yōu)化、信號處理功能強大但第一次加載會有開銷并且有些工具箱函數(shù)的實現(xiàn)可能不是最快的。我在實現(xiàn)一個定制濾波器時發(fā)現(xiàn)直接調(diào)用信號處理工具箱的函數(shù)比我自己寫的循環(huán)慢很多原因是工具箱函數(shù)做了大量邊界校驗和通用處理但當(dāng)數(shù)據(jù)量巨大時工具箱函數(shù)的底層優(yōu)化又能反超回來。所以不能盲目“必須自己寫”或者“必須用工具箱”關(guān)鍵就是通過profile和timeit來實測不同實現(xiàn)版本在典型數(shù)據(jù)規(guī)模下的表現(xiàn)用數(shù)據(jù)說話。4.4 優(yōu)化前后的對比案例我舉一個具體的優(yōu)化案例便于大家看明白一整套流程怎么落地。之前模擬一個簡單彈簧阻尼系統(tǒng)需要對系數(shù)的不同取值做2000次仿真每次仿真內(nèi)部有5000個時間步迭代。起初我直接寫了雙層循環(huán)運行一遍要超過十分鐘。我做的第一步是預(yù)分配所有結(jié)果數(shù)組確保循環(huán)中不會動態(tài)增長任何數(shù)組。運行時間降到四分鐘。 第二步我把最內(nèi)層能用向量化計算的物理公式改寫為矩陣表達(dá)比如把加速度計算改成對整段位移向量一次性求解。運行時間降到一分鐘。 第三步我用profile分析發(fā)現(xiàn)很大一部分時間在重復(fù)計算一些與參數(shù)無關(guān)的固定系數(shù)于是把這些固定系數(shù)提到循環(huán)外部。運行時間降到四十秒。 第四步我檢查了parfor的可行性由于每次仿真完全獨立直接把外層循環(huán)變成了parfor運行時間降到十二秒。從十分鐘到十二秒優(yōu)化的總收益接近50倍。這里沒有花哨的魔法每一步都是標(biāo)準(zhǔn)流程——先分析再預(yù)分配再向量化再并行。這個案例你可以直接套用到自己的項目里。優(yōu)化不是一次性的動作而是一輪一輪的迭代每次迭代都要以profiling數(shù)據(jù)為準(zhǔn)。5. 一個標(biāo)準(zhǔn)化的查錯與優(yōu)化流程SOP5.1 問題復(fù)現(xiàn)與最小化這一節(jié)匯總一下我在項目里反復(fù)使用的一套標(biāo)準(zhǔn)化流程。遇到報錯或者性能問題時不要慌首先要做的是確保問題可以穩(wěn)定復(fù)現(xiàn)。如果是偶發(fā)性的報錯比如有時報錯時又不報錯那多半是有內(nèi)存或數(shù)據(jù)狀態(tài)問題這個時候一定要先找到觸發(fā)條件。我做一項數(shù)據(jù)分析項目時遇到一個偶發(fā)的維度錯誤排查了很久發(fā)現(xiàn)是因為讀取的某一批文件里有一個空數(shù)據(jù)文件導(dǎo)致讀取出來的數(shù)組是空的維度結(jié)果和正常情況不一致觸發(fā)了后續(xù)運算的報錯。找到這個觸發(fā)條件后修復(fù)就變得簡單——加入文件校驗和空數(shù)據(jù)判斷即可。一旦問題穩(wěn)定復(fù)現(xiàn)下一步就是“最小化問題”。把出錯的代碼盡量精簡能用幾行代碼復(fù)現(xiàn)就不要保留巨大工程。這個思路不僅方便自己調(diào)試如果在技術(shù)社區(qū)求助別人也更愿意幫你。我在幫助某同行排查一個矩陣分解問題的時候他把整個項目發(fā)給我?guī)浊写a根本沒法看。我讓他只保留分解函數(shù)和一小段構(gòu)造的測試數(shù)據(jù)把其他部分全部注釋掉。結(jié)果幾行代碼就復(fù)現(xiàn)了問題大家都輕松。5.2 從報錯到修復(fù)的速查表這里我按實際經(jīng)歷整理一個簡易速查表覆蓋最常見的幾大類報錯方便大家對照排查。報錯特征常見根因排查建議Undefined function or variable路徑未添加、函數(shù)名拼錯、變量名未定義用which 函數(shù)名檢查路徑用exist檢查變量Matrix dimensions must agree運算符用了矩陣乘法而不是點乘或維度本身不匹配用size查看兩個參與運算變量的維度Index exceeds the number of array elements數(shù)組長度小于訪問索引在報錯處檢查數(shù)組長度和索引值重點找動態(tài)變化未同步的數(shù)組Out of memory沒有預(yù)分配數(shù)組或單次申請內(nèi)存過大用profile看內(nèi)存峰值檢查是否有巨型臨時數(shù)組產(chǎn)生Subscript indices must either be real positive integers索引值含小數(shù)、負(fù)數(shù)或NaN檢查索引來源確認(rèn)是否有除零或非法計算產(chǎn)生了非整數(shù)Error using fft / Invalid data type數(shù)據(jù)類型不支持該函數(shù)檢查輸入是否為double、single或符合要求的類型Unbalanced or unexpected parenthesis括號不匹配用編輯器高亮匹配功能查找未閉合括號Input argument must be a scalar參數(shù)要求是標(biāo)量卻傳了向量檢查函數(shù)調(diào)用處是否多傳了一個參數(shù)或傳錯了變量排查這些常見報錯時我的通用技巧是先用dbstop if error跑一次直接跳到出錯現(xiàn)場看變量比對照報錯信息猜原因效率高得多。如果你卡了很久也解決不了可以試試看能不能把問題“最小化”到一個小腳本然后去MATLAB官方文檔查找對應(yīng)函數(shù)的詳細(xì)限制大多數(shù)報錯都能在“文檔說明”里找到對應(yīng)的限制條件。5.3 從根因出發(fā)防止問題復(fù)發(fā)修復(fù)完成不意味著結(jié)束我還會多問一句“這個報錯為什么會發(fā)生”。舉個例子如果你遇到“Index exceeds the number of array elements”最簡單的修復(fù)是把for i 1:1000改成for i 1:length(data)但如果你不思考為什么之前會寫死上限下次換一批數(shù)據(jù)還會犯同樣的錯。更根本的修復(fù)是在代碼里顯式地獲取數(shù)組長度并確保循環(huán)邊界始終跟隨數(shù)據(jù)的實際變化。對于性能優(yōu)化也是一樣不要只看到“某一行代碼慢”要理解為什么慢。比如上一節(jié)說的蒙特卡洛模擬核心不是“把循環(huán)改成parfor”這一個動作而是理解整個數(shù)據(jù)流的形態(tài)發(fā)現(xiàn)動態(tài)增長數(shù)組反復(fù)分配內(nèi)存才是瓶頸。再強調(diào)一次優(yōu)化的第一原則是“讓數(shù)據(jù)形狀穩(wěn)定”也就是說每個數(shù)組在創(chuàng)建時就要想清楚它的最終大小、維度變化規(guī)律這樣內(nèi)存分配、索引訪問和并行拆分都會順理成章。結(jié)合我自己幾年用MATLAB做項目的體會我想最后再分享一個建議經(jīng)?;仡^審視自己寫過的代碼特別是那些跑得慢、出錯多的腳本。花一點時間把其中頻繁出現(xiàn)的錯誤和性能問題記錄下來形成一個屬于你自己的“避坑清單”。我自己的清單里現(xiàn)在有二十多條比如“所有循環(huán)前必須先預(yù)分配”、“矩陣點運算必須檢查是否用了點號”、“每次調(diào)試前先打開dbstop if error”、“改動核心函數(shù)前先備份舊版本”、“提交代碼前用profile看一遍”……看起來都是很小的事但每一條都來自真實踩坑的經(jīng)歷。你會慢慢發(fā)現(xiàn)真正讓你從頻繁報錯走向順滑開發(fā)的關(guān)鍵不是你背下了多少語法規(guī)則而是你形成了一套自己的查錯和優(yōu)化方法知道該往哪里看知道用什么工具幫忙知道哪些坑以前踩過、以后不要再踩。希望這篇MATLAB編程全攻略里的經(jīng)驗和思路也能幫你少走一些彎路把更多時間花在算法和思想上而不是和報錯信息死磕。