簡體   English   中英

自定義 switch-case 作為 foreach 的替代方案

[英]Custom switch-case as an alternative to foreach

對於游戲,我目前正在研究粒子系統。 粒子是只有位置數據的對象。 這些粒子有一種方法可以通過使用它們嵌入的矢量場來更新它們的位置。 粒子位於陣列內

為了在一個物理步驟中更新所有粒子,我目前使用 foreach:

foreach (particle p in ParList) p.update;

更新方法只是對原始位置進行兩次位移和兩次添加。 現在我想知道我的系統可以處理多少粒子,以及我是否可以優化它以提高這個數量。

查看 foreach 的工作原理,我發現它只是一個基本的 for 循環,進行了比較並添加了索引號。

我想做 for 循環所做的事情,而不檢查索引號是否 >= 0 並且不將索引號減一。

這兩個操作通常並不多,但在我的情況下,它們大約需要操作次數的 1/3。 所以我想知道我是否可以這樣做:

switch (Parlist.Length)
{
    case 1024:
        ParList[1023].update;
        goto case 1023;
    case 1023:
        ParList[1022].update;
        goto case 1022;
    //and so on until
    case 1:
        ParList[0].update;
        break;
}

雖然它看起來很糟糕,而且我知道這不是應該做的,但第一次測試看起來我實際上可以在這里提高性能。 我想把它放到一個類中,並以更通用的方式訪問它,就像 foreach 語法被翻譯成一個 for 循環。 我希望它以這樣的方式結束:

eachcase (particle p in ParList)
{
    //instructions using p
}

翻譯成這樣:

switch (Parlist.Length)
{
    case 1024:
        //reference to instructions using Parlist[1023]
        goto case 1023;
    //and so on until
    case 1:
        //reference to instructions using Parlist[0]
        break;
}

我如何構建這樣的自定義結構? 這在 C# 中可能嗎?

如果我能夠做到這一點,我還想實現一個像這樣的自定義中斷條件:

eachcase (particle p in ParList, p.x == 0 && p.z == 0)
{
    //instructions using p
}

翻譯成這樣:

switch (Parlist.Length)
{
    case 1024:
        if (/*break condition*/) break;
        //reference to instructions using Parlist[1023]
        goto case 1023;
    //and so on until
    case 1:
        if (/*break condition*/) break;
        //reference to instructions using Parlist[0]
        break;
}

我閱讀了 Lambda 表達式,它可以在這里幫助我,但我不確定如何將它連接到通常的對象數組。

通過研究foreach工作原理,我發現它只是一個基本的for循環,它進行了比較並添加了索引號。

這就是它對數組的工作方式,因為這是導航數組的最有效方法 ,但通常不是這樣。 編譯器對其進行了優化 ,以將索引用於數組。 其他集合類型將具有GetEnumerator()方法,該方法將返回某些對象,該對象具有在集合中導航的方法。 它是如何的是完全由枚舉的實現定義。 對於List ,枚舉只是增加索引並使用索引器(非常類似於數組,但枚舉器對象的開銷很小)。

我看不出您的“ switch”語句有何改進。 您要做的就是將循環硬編碼為N個語句。 我很難相信邊界檢查是您程序時間的重要部分。 您有大量重復的代碼,限制是有限的(如果基礎集合是無限的,則foreach循環可能會永遠循環),並且難以理解(IMHO)的意圖

現在我想知道我的系統可以處理多少個粒子

這很簡單。 將其設置為基礎集合類型的最大大小(如果“ Particle為引用類型,則為Particle億個元素,如果為值類型,則可以減少)。 如果您的系統可以處理該問題,那么您就很好了。 如果不是,請減小尺寸,直到找到極限為止。

如果我可以優化它來增加這個數字。

在優化之前,您必須弄清楚效率低下的地方。 找到占用程序最多時間的操作,然后首先進行操作。

我不會認為foreach存在效率問題。 如果您測量程序的性能並確定foreach本身(循環,而不是每次迭代所執行的操作), 開始尋找替代方案。

如果您的進程受CPU限制(這意味着它將100%固定在一個邏輯處理器上,那么您可能會從並行化中受益。您可以嘗試使用PLinqTPL之類的東西,看看並行化的好處是否超過了它創建的開銷。

它認為你需要這樣的東西

for (int i = Parlist.Length - 1; i >= 0; i--) 
{
    ParList[i].update;
}

要么

int i = Parlist.Length - 1;
while (i) {
    ParList[i--].update;
}

您為什么不嘗試遞歸,示例代碼如下所示

function UpdateParList(int length){
   ParList[length - 1].Update;
   // add break condition here if required
   UpdateParList(length-1);
}

您嘗試做的是手動循環展開或構建Duff設備的變體。

借助現代編譯器和CPU架構,兩者可能都不會給您帶來很大的加速,並且可能會使您在不同CPU上的代碼運行速度變慢。

但是,它們肯定會使您的代碼更難閱讀。

如果經過仔細衡量 ,您確實需要更多性能,請嘗試以下操作(按每工作量的平均收益排序):

  1. 使用[MethodImpl(MethodImplOptions.AggressiveInlining)]標記您的.update函數/ getter

    • 這指示編譯器內聯函數
    • 這可能有幫助的原因是它可以幫助編譯器為您做#4
  2. 確保您的Particle類是一個值類型(結構),並將它們存儲在數組或ArrayList中

    • 這將有助於CPU進行預取 從RAM中獲取數據片段(“高速緩存未命中”)將需要大約300倍的浮點操作時間。 確保數據順序正確,將允許CPU在需要之前預取該數據,並可能顯着提高速度。
  3. SIMD(有點高級)

    • 它使用SIMD指令,該指令一次對2-8個值執行運算,從而提供幾乎等效的(2x-8x)加速。

     class Particles { // No Idea what your particles actually do, but it should be implementable like this Vector<float> positions; Vector<float> motion; Particles(unsigned n) { positions = new Vector(n * 3); motion = new Vector(n*3); } void update() { positions += motion; // Really, really, really efficient. } }; 
  4. 多線程

  5. (在GPU上運行您的計算?)

嘗試Select() ,它比循環https://www.reddit.com/r/csharp/comments/4xb0d9/why_is_linqs_select_better_than_foreach/更有效

ParList = ParList.Select(s => s.update).ToArray();

暫無
暫無

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

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