for-loop / switch-statement的性能优化
Posted
技术标签:
【中文标题】for-loop / switch-statement的性能优化【英文标题】:Performance optimization of for-loop / switch-statement 【发布时间】:2012-06-27 11:46:06 【问题描述】:请帮我确定以下哪些是更优化的代码?
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 case 条件的开销。在第二种情况下,开销不存在。我觉得第二种情况好多了。如果有人有任何其他解决方法,请帮助我提出建议。
【问题讨论】:
试试这个:***.com/questions/445067/if-vs-switch-speed 你为什么不measure呢? @TimSchmelter 我的项目在 Silverlight 中,这里没有秒表。 针对什么进行了优化?速度、可读性等等。 就个人而言,我更喜欢第一个示例,因为它更易于阅读。优化 switch 语句对我来说似乎是微优化。 【参考方案1】:switch
在低的连续值上非常快 - 这种类型的跳转具有高度优化的处理。坦率地说,在绝大多数情况下,您所问的没有任何区别 - doWork2(i);
中的 任何东西 都会淹没这一点;哎呀,虚拟呼叫本身可能会淹没它。
如果它真的,真的,真的很重要(而且我很难在这里想到一个真实的场景),那么:测量它。在任何明显的情况下,测量它的唯一方法将是使用您的实际、准确代码 - 您无法概括 pico 优化。
所以:
-
没关系
测量
没关系
【讨论】:
你只指switch
(IL) 跳转表吗?如果没有,请考虑 way
评估属性的成本很高 - 除了跳转表之外,还有没有机会进行编译器优化?【参考方案2】:
你可以这样做:
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);
(写在这里,代码可能无法完全编译,只是给你一个想法......)
【讨论】:
【参考方案3】:实际上,尽管这里有一些 cmets,但它可能会更快一些。
让我们实际测试一下:
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.
现在将循环放入开关变得更慢了!这种违反直觉的结果是这类事情的典型表现,并说明了为什么在尝试优化代码时应该始终执行时序测试。 (并且像这样优化代码通常是你甚至不应该做的事情,除非你有充分的理由怀疑存在瓶颈。你最好花时间清理你的代码。;))
我做了一些其他测试,对于稍微简单的 doWork() 方法,test2() 方法更快。这在很大程度上取决于 JIT 编译器可以对优化做什么。
注意:我认为我的第二个测试代码的速度差异的原因是因为 JIT 编译器可以在内联对 doWork() 的调用时优化掉 'ref' 调用,当它们不在循环中时测试1();而对于 test2() 它不能(出于某种原因)。
【讨论】:
【参考方案4】:我会问自己问题以进行优化
-
首先,计数有多大?是 1,2,10, 10000000000 吗?
运行代码的机器有多强大?
我应该少写代码吗?
我写完这段代码有人会看吗?如果是这样如何
他是专业人士吗?
我缺少什么?时间?速度 ?还有什么?
way
是什么?我从哪里得到它?概率是多少
way
是 1 还是 2 还是 3?
很明显,第一个代码 sn-p 将用于 switch 部分,直到i
达到计数但计数有多大?如果它不是一个非常大的数字,那不会有关系吗?如果它太大并且运行时间很慢,那么它就没有用了。但是,正如我所说,如果您想要可读性并且可以保证计数很小,为什么不使用第一个呢?它比第二个更易读,代码更少,这是我喜欢的。
第二个 sn-p,看起来很丑,但如果 count 是一个巨大的数字,它应该是首选。
【讨论】:
【参考方案5】:你应该衡量它是否值得优化(我很确定it's not)。就可读性和简洁性而言,我个人更喜欢第一个(更少的代码,更不容易出错,更多的“dry”)。
这是另一种更简洁的方法:
for(int i = 0; i < count; i++)
doAllWays(way, i); // let the method decide what to do next
所有“方式”似乎都是相关的,否则它们不会出现在同一个switch
中。因此,首先将它们捆绑在一种方法中是有意义的,即switch
。
【讨论】:
【参考方案6】:第二种方法效率更高;无论如何,您都必须完成完整的 for 循环。但是在第一种方法中,您会不必要地重复 case 语句 count 次。
【讨论】:
我也觉得,只是如果在第二个代码中可以进行更多优化,那就更好了:) @vaibhav 您的意思是性能 优化,还是减少代码中的冗余(即DRY 原则)?至少根据您提供的信息,我看不出它如何更有效。 减少第二个代码的冗余,以获得更好的可读性,我想知道冗余代码是否会影响性能 @vaibhav per MG's answer,可读性应该是这里唯一真正关心的问题......但我认为,就你所呈现的代码而言,它的可读性很好。如果有 20 种不同的 doWork 方法,或者如果 doWork 方法是多余的,那就另当别论了。【参考方案7】:假设您在这里遇到性能问题(因为在大多数情况下 switch 真的非常快):
如果你对你的 switch 语句感到困扰,我建议在这里应用重构。
开关可以很容易地被策略模式替换(因为开关值 在for循环中没有改变,根本不需要切换)。
真正的优化目标是那些 for 循环,但没有上下文 很难说对此能做些什么。
这里有更多关于重构开关的信息(例如到策略模式) CodeProject Article on refactoring switch
【讨论】:
以上是关于for-loop / switch-statement的性能优化的主要内容,如果未能解决你的问题,请参考以下文章
如何在.innerHTML(for-loop)中迭代字符串的每个字符?