1 緣起
實(shí)驗(yàn)室接了一個(gè)新的項(xiàng)目辜荠,需要在一塊3d地形上基于速度繪上顏色瘤缩,而且要?jiǎng)討B(tài)改變紊服。由于地形比較大伺糠,mesh網(wǎng)格的頂點(diǎn)一共有一千三百多萬(wàn)個(gè)蒙谓,即使在邏輯上優(yōu)化之后也達(dá)到了一百多萬(wàn)個(gè)。使用傳統(tǒng)的for循環(huán)為每個(gè)頂點(diǎn)賦顏色值训桶,效率太低累驮,最終的fps只能達(dá)到8左右,完全達(dá)不到項(xiàng)目要求渊迁。在老師的指導(dǎo)下慰照,開(kāi)始考慮多線(xiàn)程的方法提高效率。
2 Parallel.For
在查閱了大量網(wǎng)絡(luò)資源之后琉朽,確定了最簡(jiǎn)單易行的方法是C#本身提供的Parallel并行計(jì)算方法毒租。
關(guān)于Parallel.For的語(yǔ)法就不細(xì)說(shuō)了,直接通過(guò)下面的代碼見(jiàn)識(shí)一下并行的威力吧箱叁。
using System.Diagnostics;
using System.Threading.Tasks;
using UnityEngine;
public class testParallel : Mono Behaviour
{
Stopwatch stopWatch = new Stopwatch();
void Start()
{
stopWatch.Start();
for (int i = 0; i < 10000; i++)
{
for (int j = 0; j < 60000; j++)
{
int sum = 0;
sum += i;
}
}
stopWatch.Stop();
print("NormalFor run " + stopWatch.ElapsedMilliseconds + " ms.");
stopWatch.Reset();
stopWatch.Start();
Parallel.For(0, 10000, item =>
{
for (int j = 0; j < 60000; j++)
{
int sum = 0;
sum += item;
}
});
stopWatch.Stop();
print("ParallelFor run " + stopWatch.ElapsedMilliseconds + " ms.");
}
}
在unity中運(yùn)行該代碼墅垮,其結(jié)果如下圖所示
可以發(fā)現(xiàn),Parallel.For相較于傳統(tǒng)的for循環(huán)運(yùn)行時(shí)間加快了1s耕漱。而在本次項(xiàng)目中算色,在使用了Parallel.For的情況下,fps直接達(dá)到了23螟够,這是一個(gè)相當(dāng)大的提升了灾梦。
3 深入思考
在體驗(yàn)到Parallel.For的巨大優(yōu)勢(shì)之后峡钓,我不禁思考:
- 是不是所有的Parallel.For都比傳統(tǒng)的for循環(huán)都快呢?
- 今后我是不是可以把所有的for循環(huán)都替換為Parallel.For呢?
帶著這樣的問(wèn)題,我繼續(xù)搜索若河,也發(fā)現(xiàn)了使用Parallel.For的一些注意事項(xiàng)能岩。
3.1 Parallel.For的快是有前提的
眾所周知,在實(shí)現(xiàn)多線(xiàn)程時(shí)萧福,為了防止多個(gè)線(xiàn)程同時(shí)處理同一個(gè)變量而導(dǎo)致變量處于"薛定諤狀態(tài)"拉鹃,引入了"鎖"的概念,即在每一時(shí)刻只有獲得"鎖"的線(xiàn)程才能操作目標(biāo)變量鲫忍。那么如果在Parallel.For中也需要操作一個(gè)全局變量膏燕,就意味著即使這是并行計(jì)算,大家也需要排隊(duì)操作全局變量悟民,此時(shí)Parallel.For可能遠(yuǎn)遠(yuǎn)不如傳統(tǒng)的for循環(huán)來(lái)的快坝辫。
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading.Tasks;
using UnityEngine;
public class testParallel : MonoBehaviour
{
Stopwatch stopWatch = new Stopwatch();
void Start()
{
var obj = new Object();
long num = 0;
ConcurrentBag<long> bag = new ConcurrentBag<long>();
stopWatch.Start();
for (int i = 0; i < 10000; i++)
{
for (int j = 0; j < 60000; j++)
{
num++;
}
}
stopWatch.Stop();
print("NormalFor run " + stopWatch.ElapsedMilliseconds + " ms.");
stopWatch.Reset();
stopWatch.Start();
Parallel.For(0, 10000, item =>
{
for (int j = 0; j < 60000; j++)
{
lock (obj)
{
num++;
}
}
});
stopWatch.Stop();
print("ParallelFor run " + stopWatch.ElapsedMilliseconds + " ms.");
}
}
在unity中運(yùn)行該代碼,其結(jié)果如下圖所示
可以發(fā)現(xiàn)射亏,Parallel.For的運(yùn)行時(shí)間竟然是傳統(tǒng)for循環(huán)運(yùn)行時(shí)間的100倍阀溶!因此可以得出結(jié)論:
如果在循環(huán)中需要競(jìng)爭(zhēng)資源,用到線(xiàn)程鎖的話(huà)鸦泳,Parallel.For未必優(yōu)于傳統(tǒng)for循環(huán)
3.2 Parallel.For的循環(huán)是無(wú)序的
由于Parallel.For中的各個(gè)循環(huán)是同時(shí)進(jìn)行的银锻,所以每次循環(huán)體執(zhí)行的時(shí)間會(huì)有些許差異,這就導(dǎo)致了循環(huán)的運(yùn)行是無(wú)序的做鹰。
using System.Threading.Tasks;
using UnityEngine;
public class testParallel : MonoBehaviour
{
void Start()
{
Parallel.For(0, 5, item =>
{
print(item);
});
}
}
在unity中運(yùn)行該代碼击纬,其結(jié)果如下圖所示
因此可以得出結(jié)論:
如果循環(huán)的執(zhí)行順序需要嚴(yán)格控制的話(huà),則不能使用Parallel.For
4 總結(jié)
在計(jì)算機(jī)多核處理器普及的前提下钾麸,合理的使用多線(xiàn)程或者并行能夠顯著提高軟件的運(yùn)行效率更振。當(dāng)然,這一切都是有前提的饭尝。濫用多線(xiàn)程往往會(huì)適得其反肯腕,不僅得不到理想的輸出結(jié)果,甚至?xí)蓴_其他程序的正常運(yùn)行钥平。在明確了自己的需求的前提下实撒,選擇合適的方法才是最重要的。