簡體   English   中英

如何優化此算法

[英]How to optimize this algorithm

我需要幫助才能使這段代碼更快:

UnitBase* Formation::operator[](ushort offset)
{
 UnitBase* unit = 0;
 if (offset < itsNumFightingUnits)
 {
  ushort j = 0;
  for (ushort i = 0; i < itsNumUnits; ++i)
  {
   if (unitSetup[i] == UNIT_ON_FRONT)
   {
    if (j == offset)
     unit = unitFormation[i];
    ++j;
   }
  }
 }
 else
  throw NotFound();
 return unit;
}

所以,為了給出一些背景知識,我有這個類Formation ,它包含一個指向UnitBase對象的指針數組,稱為UnitFormation UnitBase*數組具有相同大小的數字數組,用於指示每個對應的UnitBase對象的狀態,稱為UnitSetup

我重載了[]運算符,只返回指向那些具有特定狀態的UnitBase對象的指針,所以如果我要求它的itsFormation[5] ,該函數不一定返回UnitFormation[5] ,而是UnitFormation的第5個元素其狀態為UNIT_ON_FRONT

我嘗試過使用上面的代碼,但根據我的分析器,這花費了太多時間。 這是有道理的,因為算法必須在返回請求的指針之前計算所有元素。

我是否需要完全重新思考整個問題,還是可以以某種方式更快地進行?

提前致謝。

一個快速優化將是在您找到它時立即返回單元,而不是繼續迭代所有其余單元,例如

if (j == offset)
 unit = unitFormation[i];

if (j == offset)
 return unitFormation[i];

當然,這只有在您正在尋找的單元朝向unitFormation序列的前面時才會有所幫助,但這樣做很簡單,並且有時會有所幫助。

對於每種狀態,更加有效但更有效的方法是使其更快,以構建和維護具有該狀態的單元的鏈接列表。 您可以與主單元數組並行執行此操作,鏈接列表的內容將指向主單元數組,因此您不會復制單元數據。 然后,要在狀態中找到給定的偏移量,您可以只遍歷鏈接列表的offset節點,而不是遍歷每個單元。

使它成為一個雙向鏈表並保持一個尾指針可以讓你找到具有高偏移的元素,就像低偏移一樣快(從結束開始然后向后)。

但是,如果有很多具有相同狀態的單位並且您正在尋找偏移量接近中間的單位,那么這仍然會很慢。

那么重新設計你的代碼以保持一個“前面的單位”表無論那意味着什么,聽起來很有趣:-)。 如果真的要查詢那部分並且經常不進行修改,那么你將節省一些時間。 您無需檢查整個或部分完整的單元列表,而是立即獲得結果。

PS: int應該為你的CPU使用最自然的類型,因此使用ushorts 並不一定能使你的程序更快

除了一些人提出的其他建議之外,您可能希望查看是否對這個函數的任何調用都是不必要的,並消除了這些調用點。 例如,如果您發現在結果無法改變的情況下重復調用此選項。 最快的代碼是永不運行的代碼。

是否可以按狀態UNIT_ON_FRONT對數據進行排序(或插入排序)? 這將使功能變得微不足道。

一個單位的狀態多久會改變一次? 也許您應該保留一個具有正確狀態的單元列表,並且只在狀態更改時更新該列表。

如果需要最小化狀態更改的成本,您可以保留一個數組,其中顯示前256個單元中有多少具有特定狀態,接下來256個單元中有多少等等。一個可以掃描數組的速度是256倍一個人可以掃描單位,直到一個在第N個“好”單位的256個插槽內。 更改單元的狀態只需要遞增或遞減一個陣列槽。

在給定各種使用模式的情況下,可以使用其他方法來平衡改變單元狀態的成本與查找單元的成本。

其中一個問題可能是太頻繁地調用此函數。 假設UNIT_ON_FRONT的比例是常數,則復雜度是線性的。 但是,如果從循環中調用運算符,那么復雜性將上升到O(N ^ 2)。

相反,如果你返回類似boost::filter_iterator東西,你可以提高那些需要迭代UNIT_ON_FRONT的算法的效率。

我已經完全重新設計了解決方案,使用了兩個向量,一個用於前面的單元,一個用於其他單元,並且更改了所有算法,以便具有已更改狀態的單元立即從一個向量移動到另一個向量。 因此我消除了[]運算符中的計數,這是主要的瓶頸。

在使用分析器之前,我的計算時間大約為5500到7000毫秒。 在看了這里的答案之后,1)我將循環變量從ushort更改為int或uint,這將持續時間縮短了~10%,2)我在輔助算法中進行了另一次修改,將持續時間再減少了30%左右,3)我實現了如上所述的兩個向量。 這有助於將計算時間從~3300 ms減少到~700 ms,另外40%!

總之,這減少了85-90%! 感謝SO和探查器。

接下來,我將實現一個中介模式,並且只在需要時調用更新函數,可能會滲出幾個ms。 :)

與舊代碼段對應的新代碼(功能現在完全不同):

UnitBase* Formation::operator[](ushort offset)
{
    if (offset < numFightingUnits)
        return unitFormation[offset]->getUnit();
    else
        return NULL;
}

更短,更重要。 當然,還有許多其他重大修改,最重要的是unitFormation現在是std::vector<UnitFormationElement*>而不是簡單的UnitBase** UnitFormationElement*包含UnitBase*以及之前在Formation類中UnitBase*一些其他重要數據。

這不應該產生很大的影響,但你可以檢查程序集,看看是否每次循環迭代都加載了itsNumFightingUnitsitsNumUnits ,或者它們是否被放入寄存器。 如果每次都加載它們,請嘗試在函數開頭添加臨時值。

對於最后一點果汁,如果定期拋出異常,可能需要切換到返回錯誤代碼。 這是更丑陋的代碼,但缺乏堆棧跳躍可能是一個很大的幫助。 關閉異常和RTTI在游戲開發中很常見。

你是超越自己(每個人都有時會這樣做)。 你做了一個簡單的問題O(N ^ 2)。 想想在你超載運營商之前你必須做些什么。

添加以回應評論:

嘗試退回到更簡單的語言,如C或C ++的C子集。 忘記抽象,收集課程,以及所有那些hao-haw。 看看你的程序需要做什么,並以這種方式考慮你的算法。 然后,如果你可以通過使用容器類和重載來簡化它,而不需要再做任何工作,那么就去做吧。 大多數性能問題都是由於通過嘗試使用所有奇特的想法來解決簡單的問題並使它們變得復雜。

例如,您正在使用[]運算符,通常將其視為O(1),並將其設為O(N)。 然后我假設你在一些O(N)循環中使用它,所以得到O(N ^ 2)。 你真正想要做的是遍歷滿足特定條件的數組元素。 你可以這樣做。 如果它們非常少,而且您的頻率非常高,那么您可能需要構建一個單獨的列表。 但要保持數據結構簡單簡單簡單 最好“浪費”循環,只有在你真正需要時才進行優化。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM