加快C#雙循環(huán)的速度效率的方法
一、前言
最近使用 C# 改寫以前編的一個程序,檢索數(shù)據(jù)賦值時,使用 FOR 循環(huán)結(jié)構(gòu),當數(shù)據(jù)量在9萬條時,計算量很大,導(dǎo)致很耗時,何況計算完成同時加載到 chart 和 dataGridView 圖表控件中(沒有使用第三方控件),于是百度了很多循環(huán)手段。
二、多種循環(huán)的速度效率
2.1、常用循環(huán)速度效率對比
循環(huán)結(jié)構(gòu)體 | 速度和效率 |
---|---|
for(int i=0;i<n;i++){} | 對于已知次數(shù)的迭代,使用 for 循環(huán)通常是最快的。 |
foreach(string S in Data){} | 適用于遍歷集合,如數(shù)組或列表,但通常比 for 循環(huán)慢,因為它需要更多的間接訪問。 |
while do-while | 適用于不確定次數(shù)的迭代,但可能在性能上不如 for 循環(huán)。 |
Parallel.For Parallel.ForEach Parallel.Invoke | 并行計算類,三個方法都會阻塞線程直到所有工作完成為止。僅當適用時可采用,否則出現(xiàn)意外錯誤。 Parallel.ForEach 比 Parallel.For 效率更高。 Parallel.Invoke 適合用于執(zhí)行大量且無返回值的場景; Parallel.For 使用是無序的,適合帶索引的大量循環(huán)操作; Parallel.ForEach 適合數(shù)組、集合、枚舉大數(shù)據(jù)集的循環(huán)執(zhí)行,執(zhí)行結(jié)果是無序的。 |
2.2、如何提高循環(huán)的速度效率
2.2.1、選擇合適的循環(huán)類型
循環(huán)的效率通常取決于多個因素,包括循環(huán)的類型、循環(huán)體內(nèi)的操作、以及循環(huán)的次數(shù)。選擇合適的循環(huán)類型很重要。
2.2.2、減少循環(huán)體內(nèi)的操作
循環(huán)體內(nèi)的操作越少,循環(huán)的效率越高。例如,避免在每次迭代中進行復(fù)雜的計算或方法調(diào)用。
2.2.3、使用局部變量而非頻繁訪問成員
如果循環(huán)體內(nèi)需要多次訪問對象的成員,最好將該成員的值存儲在局部變量中,這樣可以減少多次訪問成員的開銷。
2.2.4、避免在循環(huán)體內(nèi)進行不必要的對象創(chuàng)建或銷毀
每次迭代創(chuàng)建新對象會增加GC(垃圾回收)的壓力,降低性能。如果可能,考慮對象池技術(shù)或復(fù)用對象。
2.2.5、僅當適用時可采用并行處理
對于可以并行處理的計算任務(wù),使用 Parallel.For 或 PLINQ(Parallel LINQ)可以顯著提高性能,特別是當處理大量數(shù)據(jù)時。然而,并行處理需要謹慎使用,因為它會增加線程管理的開銷。
三、如何提高雙循環(huán)計算速度效率的編程實例
遵循上述循環(huán)原則,對于有序的數(shù)據(jù)集,通過多次測試各種循環(huán),采用的 for 雙循環(huán)。
Hypack RAW 數(shù)據(jù)中,實時采集的定位 POS 數(shù)據(jù)和 EC1 數(shù)據(jù)系列,時間和位置大部分不同步,但處于同一個航線軌跡上,通過各采集點的時間秒、坐標、水深,計算航行距離、航行速度,以計算插補 EC1 采集數(shù)據(jù)所在定位坐標。
POS 數(shù)據(jù)有時間、定位東和北坐標數(shù)組,POSTime[]、POSEast[]、POSNorth[] 數(shù)據(jù)長度一致。
EC1 數(shù)據(jù)有時間、水深數(shù)組,EC1Time[]、EC1WaterDepth[] 數(shù)據(jù)長度一致。
POS 和 EC1 數(shù)據(jù)個數(shù)不對等。
還要根據(jù) FIX 和 EC1 數(shù)據(jù),插補 FIX 采集數(shù)據(jù)點的水深,這里就不描述。
Hypack RAW 數(shù)據(jù)節(jié)選如下:
POS 0 37410.599 626291.650 3216635.552 QUA 0 37410.599 7 3.000 1.000 8.000 2.000 0.000 0.000 0.000 RAW 0 37410.599 4 290361.17840 1121780.82570 9.91600 22330.60000 MSG 0 37410.538 $GPGGA,022330.60,2903.611784,N,11217.808257,E,2,08,1.0,9.916,M,0.0,M,8.0,0643*71 MSG 0 37410.581 $GPVTG,22.3,T,,M,0.08,N,0.15,K,P*23 MSG 0 37410.600 $GPZDA,022330.60,27,03,2024,00,00*62 EC1 1 37410.736 1.040 POS 0 37410.799 626291.660 3216635.561 QUA 0 37410.799 7 3.000 1.000 8.000 2.000 0.000 0.000 0.000 RAW 0 37410.799 4 290361.17890 1121780.82630 9.90800 22330.80000 MSG 0 37410.735 $GPGGA,022330.80,2903.611789,N,11217.808263,E,2,08,1.0,9.908,M,0.0,M,8.0,0643*7A MSG 0 37410.778 $GPVTG,42.9,T,,M,0.15,N,0.27,K,P*22 MSG 0 37410.797 $GPZDA,022330.80,27,03,2024,00,00*6C EC1 1 37410.939 1.040 POS 0 37411.000 626291.673 3216635.574 QUA 0 37411.000 7 3.000 1.000 8.000 2.000 0.000 0.000 0.000 RAW 0 37411.000 4 290361.17960 1121780.82710 9.89800 22331.00000 MSG 0 37410.938 $GPGGA,022331.00,2903.611796,N,11217.808271,E,2,08,1.0,9.898,M,0.0,M,8.0,0643*76 MSG 0 37410.981 $GPVTG,46.0,T,,M,0.17,N,0.31,K,P*2A MSG 0 37411.001 $GPZDA,022331.00,27,03,2024,00,00*65 FIX 99 37411.109 48 626291.673 3216635.574 EC1 1 37411.138 1.030 POS 0 37411.200 626291.687 3216635.584 QUA 0 37411.200 7 3.000 1.000 8.000 2.000 0.000 0.000 0.000 RAW 0 37411.200 4 290361.18010 1121780.82800 9.90800 22331.20000 MSG 0 37411.137 $GPGGA,022331.20,2903.611801,N,11217.808280,E,2,08,1.0,9.908,M,0.0,M,4.0,0643*7F MSG 0 37411.180 $GPVTG,54.9,T,,M,0.21,N,0.39,K,P*2D MSG 0 37411.199 $GPZDA,022331.20,27,03,2024,00,00*67 EC1 1 37411.337 1.020 POS 0 37411.400 626291.708 3216635.595 QUA 0 37411.400 7 3.000 1.000 8.000 2.000 0.000 0.000 0.000 RAW 0 37411.400 4 290361.18070 1121780.82930 9.90400 22331.40000 MSG 0 37411.336 $GPGGA,022331.40,2903.611807,N,11217.808293,E,2,08,1.0,9.904,M,0.0,M,4.0,0643*71 MSG 0 37411.379 $GPVTG,57.9,T,,M,0.23,N,0.42,K,P*20 MSG 0 37411.398 $GPZDA,022331.40,27,03,2024,00,00*61 EC1 1 37411.538 1.030 POS 0 37411.599 626291.727 3216635.606 QUA 0 37411.599 7 3.000 1.000 8.000 2.000 0.000 0.000 0.000 RAW 0 37411.599 4 290361.18130 1121780.83050 9.90100 22331.60000 MSG 0 37411.537 $GPGGA,022331.60,2903.611813,N,11217.808305,E,2,08,1.0,9.901,M,0.0,M,4.0,0643*7D MSG 0 37411.580 $GPVTG,61.7,T,,M,0.22,N,0.41,K,P*29 MSG 0 37411.600 $GPZDA,022331.60,27,03,2024,00,00*63 EC1 1 37411.737 1.020 POS 0 37411.799 626291.750 3216635.618 QUA 0 37411.799 7 3.000 1.000 8.000 2.000 0.000 0.000 0.000 RAW 0 37411.799 4 290361.18190 1121780.83190 9.89600 22331.80000 MSG 0 37411.736 $GPGGA,022331.80,2903.611819,N,11217.808319,E,2,08,1.0,9.896,M,0.0,M,4.0,0643*7B MSG 0 37411.779 $GPVTG,64.7,T,,M,0.25,N,0.45,K,P*2F MSG 0 37411.798 $GPZDA,022331.80,27,03,2024,00,00*6D EC1 1 37411.937 1.030 POS 0 37412.000 626291.774 3216635.627 QUA 0 37412.000 7 3.000 1.000 8.000 2.000 0.000 0.000 0.000 RAW 0 37412.000 4 290361.18240 1121780.83340 9.89900 22332.00000 MSG 0 37411.937 $GPGGA,022332.00,2903.611824,N,11217.808334,E,2,08,1.0,9.899,M,0.0,M,4.0,0643*7E MSG 0 37411.980 $GPVTG,68.6,T,,M,0.25,N,0.47,K,P*20 MSG 0 37411.999 $GPZDA,022332.00,27,03,2024,00,00*66 EC1 1 37412.138 1.020 POS 0 37412.200 626291.790 3216635.646
3.1、中規(guī)中矩的雙循環(huán)
以下代碼實例中,是 Hypack RAW 數(shù)據(jù)處理中的雙循環(huán),插補 EC1 采集數(shù)據(jù)所在定位坐標。
數(shù)據(jù)的時間秒從小到大有序排列。
for (int j = 0; j < POSLeng; j++) { if (j == 0 && EC1Time[i] < POSTime[j])//EC1時間小于第一個POS時間 { EC1East[i] = POSEast[j] - (POSEast[j + 1] - POSEast[j]) / (POSTime[j + 1] - POSTime[j]) * (POSTime[j] - EC1Time[i]);//水深點東坐標 EC1North[i] = POSNorth[j] - (POSNorth[j + 1] - POSNorth[j]) / (POSTime[j + 1] - POSTime[j]) * (POSTime[j] - EC1Time[i]);//水深點北坐標 break; } if (j == POSLeng - 1 && EC1Time[i] > POSTime[POSLeng - 1])//EC1時間大于最后一個POS時間 { EC1East[i] = POSEast[j] + (POSEast[j] - POSEast[j - 1]) / (POSTime[j] - POSTime[j - 1]) * (EC1Time[i] - POSTime[j]);//水深點東坐標 EC1North[i] = POSNorth[j] + (POSNorth[j] - POSNorth[j - 1]) / (POSTime[j] - POSTime[j - 1]) * (EC1Time[i] - POSTime[j]);//水深點北坐標 break; } if (EC1Time[i] == POSTime[j]) { EC1East[i] = POSEast[j]; EC1North[i] = POSNorth[j]; break; } if ((EC1Time[i] > POSTime[j]) && (EC1Time[i] < POSTime[j + 1])) { EC1East[i] = POSEast[j] + (POSEast[j + 1] - POSEast[j]) / (POSTime[j + 1] - POSTime[j]) * (EC1Time[i] - POSTime[j]);//水深點東坐標 EC1North[i] = POSNorth[j] + (POSNorth[j + 1] - POSNorth[j]) / (POSTime[j + 1] - POSTime[j]) * (EC1Time[i] - POSTime[j]);//水深點北坐標 break; } } SurveyEC1[i, 0] = EC1Time[i]; SurveyEC1[i, 1] = EC1East[i]; SurveyEC1[i, 2] = EC1North[i]; SurveyEC1[i, 3] = EC1WaterDepth[i];
以上代碼循環(huán)效率不高,測試中,當 RAW 文件 3119 KB,EC1 數(shù)據(jù)量達到 97981 個,POS數(shù)據(jù)量達到 6142 個,F(xiàn)IX數(shù)據(jù)量達到 503個。插補 EC1 采集點的坐標和 從 EC1 采集點檢索 FIX 采集的水深,很耗時,達到 2 千毫秒左右,插補數(shù)據(jù)后,還要加載到 chart 和 dataGridView 圖表控件中,加載圖標達到 7 百毫秒左右,導(dǎo)致顯示很慢。
3.2、采用改進二分法的雙循環(huán)
為了提高運算速度和效率,采用改進二分法的雙循環(huán),根據(jù)循環(huán)原則,優(yōu)化代碼結(jié)構(gòu),使用類封裝坐標計算循環(huán)為 InterpCoord 函數(shù),二分法循環(huán)調(diào)用它。插補 EC1 采集點的坐標和 FIX 采集的水深,整體循環(huán)減小到 764 毫秒左右。
//根據(jù) POS 定位數(shù)據(jù)和時間數(shù)據(jù),以航速計算插補 EC1 水深的定位坐標 //改進二分法,加快循環(huán) int EC1Leng = ListEc1Time.Length;//EC1數(shù)據(jù)個數(shù) int MedianNum = 0; if ((EC1Leng & 1) == 0) { MedianNum = EC1Leng / 2;//偶數(shù),數(shù)據(jù)半數(shù) } else { MedianNum = (int)(EC1Leng / 2) + 1; //奇數(shù),數(shù)據(jù)半數(shù)+1 } for (int i = 0; i <= MedianNum; i++)//for (int i = 0; i < EC1Leng; i++) { //從時間秒最小索引 i = 0 開始順序計算到中數(shù) InterpCoord(EC1Time[i], ref EC1East[i], ref EC1North[i], POSTime, POSEast, POSNorth); SurveyEC1[i, 0] = EC1Time[i]; SurveyEC1[i, 1] = EC1East[i]; SurveyEC1[i, 2] = EC1North[i]; SurveyEC1[i, 3] = EC1WaterDepth[i]; //從時間秒最大索引 i = EC1Leng - 1 開始逆序計算到中數(shù) int ReverseOrderNum = EC1Leng - i - 1; InterpCoord(EC1Time[ReverseOrderNum], ref EC1East[ReverseOrderNum], ref EC1North[ReverseOrderNum], POSTime, POSEast, POSNorth); //組合為二維 EC1 數(shù)據(jù) SurveyEC1[ReverseOrderNum, 0] = EC1Time[ReverseOrderNum]; SurveyEC1[ReverseOrderNum, 1] = EC1East[ReverseOrderNum]; SurveyEC1[ReverseOrderNum, 2] = EC1North[ReverseOrderNum]; SurveyEC1[ReverseOrderNum, 3] = EC1WaterDepth[ReverseOrderNum]; }
使用類封裝坐標計算循環(huán)為 InterpCoord 函數(shù),以便構(gòu)造引用:
public class Hypack { /// <summary>計算 EC1 時間點的坐標</summary> /// <param name="InterpTime">EC1時間點</param> /// <param name="InterpEast">EC1東坐標</param> /// <param name="InterpNorth">EC1西坐標</param> /// <param name="LineTime">POS時間點</param> /// <param name="LineEast">POS東坐標</param> /// <param name="LineNorth">POS西坐標</param> public static void InterpCoord(double InterpTime, ref double InterpEast, ref double InterpNorth, double[] LineTime, double[] LineEast, double[] LineNorth) { int LineLeng = LineTime.Length; if (InterpTime < LineTime[0])//EC1時間小于第一個 POS 線上時間,計算EC1時間對應(yīng)坐標 { InterpEast = LineEast[0] - (LineEast[1] - LineEast[0]) / (LineTime[1] - LineTime[0]) * (LineTime[0] - InterpTime);//水深點東坐標 InterpNorth = LineNorth[0] - (LineNorth[1] - LineNorth[0]) / (LineTime[1] - LineTime[0]) * (LineTime[0] - InterpTime);//水深點北坐標 } else if (InterpTime > LineTime[LineLeng - 1])//EC1時間大于最后一個 POS 線上時間,計算EC1時間對應(yīng)坐標 { InterpEast = LineEast[LineLeng - 1] + (LineEast[LineLeng - 1] - LineEast[LineLeng - 2]) / (LineTime[LineLeng - 1] - LineTime[LineLeng - 2]) * (InterpTime - LineTime[LineLeng - 1]);//水深點東坐標 InterpNorth = LineNorth[LineLeng - 1] + (LineNorth[LineLeng - 1] - LineNorth[LineLeng - 2]) / (LineTime[LineLeng - 1] - LineTime[LineLeng - 2]) * (InterpTime - LineTime[LineLeng - 1]);//水深點北坐標 } else { for (int j = 0; j < LineLeng; j++) { if (InterpTime == LineTime[j])//EC1時間等于 POS 線上時間,坐標一致 { InterpEast = LineEast[j]; InterpNorth = LineNorth[j]; break; } if ((InterpTime > LineTime[j]) && (InterpTime < LineTime[j + 1]))//EC1時間在 POS 線上 2 個時間之間,計算EC1時間對應(yīng)坐標 { InterpEast = LineEast[j] + (LineEast[j + 1] - LineEast[j]) / (LineTime[j + 1] - LineTime[j]) * (InterpTime - LineTime[j]);//水深點東坐標 InterpNorth = LineNorth[j] + (LineNorth[j + 1] - LineNorth[j]) / (LineTime[j + 1] - LineTime[j]) * (InterpTime - LineTime[j]);//水深點北坐標 break; } } } // 嘗試如下代碼更耗時,相當于增加了循環(huán),盡管代碼簡潔 找到大于目標的第一個元素的索引 //int indexGreaterThan = Array.FindIndex(LineTime, n => n > InterpTime); 找到等于于target的第一個元素的索引 //int indexEqualThan = Array.FindIndex(LineTime, n => n == InterpTime); 找到小于target的第一個元素的索引 //int indexLessThan = Array.FindLastIndex(LineTime, n => n < InterpTime); //if (indexEqualThan >= 0) //{ // InterpEast = LineEast[indexEqualThan]; // InterpNorth = LineNorth[indexEqualThan]; //} //if (indexLessThan >= 0 && indexGreaterThan >= 0) //{ // InterpEast = LineEast[indexLessThan] + (LineEast[indexGreaterThan] - LineEast[indexLessThan]) / (LineTime[indexGreaterThan] - LineTime[indexLessThan]) * (InterpTime - LineTime[indexLessThan]);//水深點東坐標 // InterpNorth = LineNorth[indexLessThan] + (LineNorth[indexGreaterThan] - LineNorth[indexLessThan]) / (LineTime[indexGreaterThan] - LineTime[indexLessThan]) * (InterpTime - LineTime[indexLessThan]);//水深點北坐標 //} } }
3.3 、采用并行循環(huán)
為了提高到更快的運算速度和效率,采用并行循環(huán),優(yōu)化代碼結(jié)構(gòu),調(diào)用類封裝的坐標計算循環(huán)為 InterpCoord 函數(shù)。插補 EC1 采集點的坐標和 FIX 采集的水深,整體循環(huán)減小到 377 毫秒左右。
Parallel.For(0, EC1Leng, j => { InterpCoord(EC1Time[j], ref EC1East[j], ref EC1North[j], POSTime, POSEast, POSNorth); SurveyEC1[j, 0] = EC1Time[j]; SurveyEC1[j, 1] = EC1East[j]; SurveyEC1[j, 2] = EC1North[j]; SurveyEC1[j, 3] = EC1WaterDepth[j]; });
四、雙循環(huán)更好的辦法
多線程處理不當容易內(nèi)存溢出,線程間的錯誤處理和同步問題。需要嚴謹?shù)谋苊舛鄠€線程同時訪問同一資源,否則導(dǎo)致沖突或錯誤。存在內(nèi)存銷毀消耗,處理不當反而浪費時間,增加開銷。其它方法還在探索中…
以上就是加快C#雙循環(huán)的速度效率的方法的詳細內(nèi)容,更多關(guān)于C#雙循環(huán)速度的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#判斷一個類是否實現(xiàn)了某個接口3種實現(xiàn)方法
這篇文章主要介紹了C#判斷一個類是否實現(xiàn)了某個接口3種實現(xiàn)方法,本文直接給出實現(xiàn)代碼,需要的朋友可以參考下2015-06-06C#使用偽隨機數(shù)實現(xiàn)加密用戶密碼的方法
這篇文章主要介紹了C#使用偽隨機數(shù)實現(xiàn)加密用戶密碼的方法,對于開發(fā)C#會員系統(tǒng)或者程序安全問題都有一定的參考借鑒價值,需要的朋友可以參考下2014-07-07C# MeasureString測量字符串函數(shù)的使用方法
這篇文章主要介紹了C# MeasureString測量字符串函數(shù)的使用方法,需要的朋友可以參考下2014-10-10淺談Async和Await如何簡化異步編程(幾個實例讓你徹底明白)
本篇文章主要介紹了淺談Async和Await如何簡化異步編程,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-12-12讀寫XML文件的內(nèi)容并將其顯示在ListView控件上的方法
下面小編就為大家?guī)硪黄x寫XML文件的內(nèi)容并將其顯示在ListView控件上的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-02-02C#使用Jquery zTree實現(xiàn)樹狀結(jié)構(gòu)顯示 異步數(shù)據(jù)加載
這篇文章主要為大家詳細介紹了C#使用Jquery zTree實現(xiàn)樹狀結(jié)構(gòu)顯示和異步數(shù)據(jù)加載,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-12-12