[英]Performance optimization of for-loop / switch-statement
請幫我確定以下哪些是更優化的代碼?
for(int i=0;i<count;i++)
{
switch(way)
{
case 1:
doWork1(i);
break;
case 2:
doWork2(i);
break;
case 3:
doWork3(i);
break;
}
}
要么
switch(way)
{
case 1:
for(int i=0;i<count;i++)
{
doWork1(i);
}
break;
case 2:
for(int i=0;i<count;i++)
{
doWork2(i);
}
break;
case 3:
for(int i=0;i<count;i++)
{
doWork3(i);
}
break;
}
在第一種情況下, 總是在每次迭代中始終檢查開關情況條件的開銷。 在第二種情況下,開銷不存在。 我覺得第二種情況要好得多。 如果有人有任何其他解決方法,請幫我建議。
在低連續值上switch
是非常快的 - 這種類型的跳轉具有高度優化的處理。 坦率地說,你所要求的在絕大多數情況下都沒有任何區別 - doWork2(i);
任何東西 doWork2(i);
會淹沒這個; 哎呀,虛擬電話本身可能會淹沒它。
如果真的,真的,真的很重要(我很難想到這里真實的情況),那么:衡量它。 在任何明顯的情況下, 只有測量它的方法才是 實際的,精確的代碼 - 你不能概括微微優化。
所以:
你可以這樣做:
Func(void, int> doWork;
switch(way)
{
case 1:
doWork = doWork1;
break;
case 2:
doWork = doWork2;
break;
case 3:
doWork = doWork3;
break;
}
for (int i=0;i<count;i++)
{
doWork(i);
}
(寫在這里,代碼可能不完全編譯,只是為了給你這個想法......)
我會問自己優化的問題
way
? 我從哪里得到它? way
為1或2或3的概率是多少? 很明顯,第一個代碼片段將用於切換部分,直到i
達到計數但計數有多大? 如果它不是一個非常大的數字那么無關緊要? 如果它太大而你的運行時間非常慢那么它就沒用了。 但是,正如我所說,如果你想要可讀性並且可以保證計數很小,為什么不使用第一個呢? 它比第二個更容易讀取,而且我喜歡的代碼更少。
第二個片段,看起來很流暢但如果count是一個巨大的數字應該是首選。
實際上,盡管有一些評論,它可能會更快一些。
我們實際測試它:
using System;
using System.Diagnostics;
namespace Demo
{
class Program
{
static void Main(string[] args)
{
int count = 1000000000;
Stopwatch sw = Stopwatch.StartNew();
for (int way = 1; way <= 3; ++way)
test1(count, way);
var elapsed1 = sw.Elapsed;
Console.WriteLine("test1() took " + elapsed1);
sw.Restart();
for (int way = 1; way <= 3; ++way)
test2(count, way);
var elapsed2 = sw.Elapsed;
Console.WriteLine("test2() took " + elapsed2);
Console.WriteLine("test2() was {0:f1} times as fast.", + ((double)elapsed1.Ticks)/elapsed2.Ticks);
}
static void test1(int count, int way)
{
for (int i = 0; i < count; ++i)
{
switch (way)
{
case 1: doWork1(); break;
case 2: doWork2(); break;
case 3: doWork3(); break;
}
}
}
static void test2(int count, int way)
{
switch (way)
{
case 1:
for (int i = 0; i < count; ++i)
doWork1();
break;
case 2:
for (int i = 0; i < count; ++i)
doWork2();
break;
case 3:
for (int i = 0; i < count; ++i)
doWork3();
break;
}
}
static void doWork1()
{
}
static void doWork2()
{
}
static void doWork3()
{
}
}
}
現在這是非常不現實的,因為doWork()方法不做任何事情。 但是,它會給我們一個基線時間。
我在Windows 7 x64系統上構建RELEASE的結果是:
test1() took 00:00:03.8041522
test2() took 00:00:01.7916698
test2() was 2.1 times as fast.
因此,將循環移動到switch語句中會使其快速超過兩次。
現在讓我們通過在doWork()中添加一些代碼來使它變得更加真實:
using System;
using System.Diagnostics;
namespace Demo
{
class Program
{
static void Main(string[] args)
{
int count = 1000000000;
Stopwatch sw = Stopwatch.StartNew();
for (int way = 1; way <= 3; ++way)
test1(count, way);
var elapsed1 = sw.Elapsed;
Console.WriteLine("test1() took " + elapsed1);
sw.Restart();
for (int way = 1; way <= 3; ++way)
test2(count, way);
var elapsed2 = sw.Elapsed;
Console.WriteLine("test2() took " + elapsed2);
Console.WriteLine("test2() was {0:f1} times as fast.", + ((double)elapsed1.Ticks)/elapsed2.Ticks);
}
static int test1(int count, int way)
{
int total1 = 0, total2 = 0, total3 = 0;
for (int i = 0; i < count; ++i)
{
switch (way)
{
case 1: doWork1(i, ref total1); break;
case 2: doWork2(i, ref total2); break;
case 3: doWork3(i, ref total3); break;
}
}
return total1 + total2 + total3;
}
static int test2(int count, int way)
{
int total1 = 0, total2 = 0, total3 = 0;
switch (way)
{
case 1:
for (int i = 0; i < count; ++i)
doWork1(i, ref total1);
break;
case 2:
for (int i = 0; i < count; ++i)
doWork2(i, ref total2);
break;
case 3:
for (int i = 0; i < count; ++i)
doWork3(i, ref total3);
break;
}
return total1 + total2 + total3;
}
static void doWork1(int n, ref int total)
{
total += n;
}
static void doWork2(int n, ref int total)
{
total += n;
}
static void doWork3(int n, ref int total)
{
total += n;
}
}
}
現在我得到了這些結果:
test1() took 00:00:03.9153776
test2() took 00:00:05.3220507
test2() was 0.7 times as fast.
現在將循環放入開關是SLOWER! 這種違反直覺的結果是典型的這類事情,並說明了在嘗試優化代碼時應始終執行時序測試的原因。 (並且優化這樣的代碼通常是你甚至不應該做的事情,除非你有充分的理由懷疑存在瓶頸。你最好花時間清理你的代碼。))
我做了一些其他測試,對於稍微簡單的doWork()方法,test2()方法更快。 它實際上很大程度上取決於JIT編譯器可以對優化做些什么。
注意:我認為我的第二個測試代碼的速度差異的原因是因為JIT編譯器在內聯調用doWork()時可以優化掉'ref'調用,當它們不在循環中時,就像在test1()中一樣; 而對於test2(),它不能(由於某種原因)。
第二種方法更有效; 無論如何,你必須完成完整的for循環。 但是在第一種方法中,你不必要地重復case語句計數次數。
假設你在這里遇到性能問題(因為在大多數情況下,交換機真的非常快):
如果您對switch語句感到困擾,我建議您在此處應用重構。
可以很容易地用策略模式替換開關(因為在for循環中沒有改變切換值,所以根本不需要切換)。
真正的優化目標是那些循環,但沒有上下文,很難說明可以做些什么。
這里有一些關於重構開關的更多信息(例如,關於策略模式) 關於重構開關的CodeProject文章
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.