[英]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%固定在一個邏輯處理器上,那么您可能會從並行化中受益。您可以嘗試使用PLinq或TPL之類的東西,看看並行化的好處是否超過了它創建的開銷。
它認為你需要這樣的東西
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);
}
借助現代編譯器和CPU架構,兩者可能都不會給您帶來很大的加速,並且可能會使您在不同CPU上的代碼運行速度變慢。
但是,它們肯定會使您的代碼更難閱讀。
如果經過仔細衡量 ,您確實需要更多性能,請嘗試以下操作(按每工作量的平均收益排序):
使用[MethodImpl(MethodImplOptions.AggressiveInlining)]標記您的.update函數/ getter
確保您的Particle類是一個值類型(結構),並將它們存儲在數組或ArrayList中
SIMD(有點高級)
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. } };
多線程
嘗試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.