{ Snake @ NET } 废寝忘食的程序工作者.

18三/100

浅测Try&Catch的性能到底有多差(3)

上2节咱们使用30000行杨辉三角来测试try&catch的性能,结果差强人意.原因是什么我不是很确定,但是有一点我觉得有点问题,而且结果很有可能就差在这上面:我们无法确定try块被执行了多少次,catch块被执行了多少次.当然算是能算,就是太麻烦.而这次我把算法设定的很简单.代码如下:

using System;
using System.Diagnostics;

public class MyClass
{
	public static void Main()
	{
		string[] strs=
		{
			"I'm",
			"Snake"
		};
		int rounds=1000000;
		Stopwatch sw=new Stopwatch();
		for(int r=0;r<5;r++)
		{
			sw.Reset();
			sw.Start();
			for(int i=0;i<rounds;i++)
			{
				string s=strs[i%2];

				if(s.Length>5)
					s.Substring(3,1);
				else
					s.Substring(0,1);
				/*
				try
				{
					s.Substring(3,1);
				}
				catch
				{
					s.Substring(0,1);
				}
				*/
			}
			sw.Stop();
			Console.WriteLine(sw.ElapsedMilliseconds);
		}
		Console.ReadLine();
}
}

这个算法优点是不占内存,另外使用了引用类型的简单操作.,并且我们可以知道算法中各个块被执行了多少次.
这次我毫不留情地把基数(即:rounds)设定得很大,达到一百万.那么我们的if&else还有块在这个算法中执行了多少次呢?

循环次数:1,000,000
if块: 1,000,000次
else块 500,000次
try块 1,000,000次
catch块 500,000次

好的,让我们来看看运行结果吧!

If&Else
1. 40ms
2. 39ms
3. 37ms
4. 37ms
5. 40ms
6. 38ms
7. 37ms
8. 37ms
9. 37ms
10. 39ms
平均值:38.1 ms

Try&Catch
1. 39947ms
2. 39744ms
3. 39054ms
4. 39971ms
5. 40006ms
然后我实在等不下去了…于是中止了10次循环.
好吧,性能差了整整1000倍.
这次再改改代码,事先说一下,这次改代码是出于个人兴趣,比较一下引用类型和值类型的性能,跟本主题无关.
更改代码如下:

int[] ints={0,1};
int rounds=1000000;
for(int i=0;i<rounds;i++)
{
	int n=ints[i%2];

	if(n>0)
		n = n/2;
	else
		n = n+1/2;
}

运行结果是清一色的1ms.感叹一下值类型的操作果然比引用类型快,那么题外话就赶快结束吧.

回到我们之前操作string的代码中,我们这次的代码修改如下:

public class MyClass
{
	public static void Main()
	{
		string str="Hi";
		int rounds=1000000;
		Stopwatch sw=new Stopwatch();
		for(int r=0;r<10;r++)
		{
			sw.Reset();
			sw.Start();
			for(int i=0;i<rounds;i++)
			{

				if(str.Length>5)
					str.Substring(3,1);
				else
					str.Substring(0,1);
				/*
				try
				{
					s.Substring(3,1);
				}
				catch
				{
					s.Substring(0,1);
				}
				*/
			}
			sw.Stop();
			Console.WriteLine(sw.ElapsedMilliseconds);
		}
		Console.ReadLine();
	}
}

这次的执行次数如下:
循环次数:1,000,000
if块: 1,000,000次
else块 1,000,000次
try块 1,000,000次
catch块 1,000,000次

运行结果如下:

if&else运行10次结果如下:
1. 37 ms
2. 36 ms
3. 37 ms
4. 36 ms
5. 36 ms
6. 36 ms
7. 36 ms
8. 40 ms
9. 36 ms
10. 36 ms
见鬼,比之前的还快= =

那么轮到测试try&catch块了:
1. 85498ms
2. 84335ms
我不想再测试了.这次性能更可怕了.原来try块跟catch块都有性能消耗.那finally块呢?我们来验证一下吧!
加上finally以后,耗时还是85ms左右.那就不让catch块执行看看执行时间有什么变化呢.
结果竟然和if&else一样的37ms!

本节总结:

根据上面的分析,特别是文章末尾的分析非常明显的表明了如果在try块中执行无误的代码的性能损失几乎为0,但是一旦try块中的代码出现错误,那么CLR(应该是这玩意)就会生成一个异常,并且进入catch块中.catch块中搞了什么鬼我不知道,也暂时没能力知道,但是最终倒是让我明白了原来罪魁祸首是出了错的try块中的代码导致运行catch才出现的性能损耗.
最后我要说个前两节都忘记说的事:我是用Snippet Compiler编写并执行所有的代码的,而据说Snippet Compiler是以debug的形式执行的代码.所以所有之前的代码性能可能会被debug模式影响到.但具体影响的大不大就请高手告知我一声,而完成了这篇代码之后我还有其他正式得干呢,呵呵.

17三/100

浅测Try&Catch的性能到底有多差(2)

昨晚因为快要熄灯断电的缘故,发表上一篇文章有些仓促.在昨晚熄灯之后我考虑到算法可以简化,于是今早放学之后就马上回来改了改算法.代码如下:

public class MyClass
{
	public static void Main()
	{
		int maxLine=30000;
		int[][] ns=new int[maxLine][];
		Stopwatch sw=new Stopwatch();
		sw.Reset();
		sw.Start();
		for(int i=0;i<maxLine;i++)
		{
			ns[i]=new int[i+1];
			ns[i][0]=1;
			for(int j=1;j<=i;j++)
			{

				if(j==i)
				{
					ns[i][j]=1;
					continue;
				}
				else
				{
					ns[i][j]=ns[i-1][j-1]+ns[i-1][j];
				}
			/*
				try
				{
					ns[i][j]=ns[i-1][j-1]+ns[i-1][j];
				}
				catch(Exception ex)
				{
					ns[i][j]=1;
				}
			}
			*/
		}
		sw.Stop();
		Console.WriteLine(sw.Elapsed.ToString());
		Console.ReadLine();
	}

}

最后执行10000行的时间缩短了0.1~0.15秒.但这显然不是我现在要阐述的论题.
在上一篇文章我并未提出我的平台和操作系统,这显然是对测试的一种不尊重,所以赶紧放上我的测试环境详情:
操作系统:windows 7 Ultimate
主板芯片组: A780G(超级缩水版)
CPU:AMD X2 2700MHZ(5000+黑盒)
内存:4G(金士顿 ddr2 800 2G*2)
还好我没有高估我的内存.刚才想提高测试的函数,于是在10000后面加了个零,差点没把我电脑给搞歇菜了.于是只再加了20000行.那么现在在改进了算法和行数之后他们的效率又会是如何呢?
ps:这算法明显给if和else减少压力从原来的四段变为两段,而try&catch负担依旧很重.另外我就不截图了,截图太麻烦,而且我之准备让程序在运行期间只执行2次,并不是原先的5次,可能是程序内存占用的原因吧,每次运行时间都相继减少,而且内存也吃不消,30000行1趟占用2G左右的内存.

下面是if&else方式执行30000行的记录:

1.1    30000lines     2.956s

1.2    30000lines    3.751s

2.1    30000lines    2.826s

2.2   30000lines    3.619s

3.1    30000lines    3.068s

3.2   30000lines    3.306s

现在是try&catch方式执行30000行的记录:

1.1    30000lines    7.611s

1.2    30000lines    7.813s

2.1    30000lines    6.804s

2.2    30000lines    7.064s

3.1    30000lines    6.945s

3.2    30000lines    7.036s

这次虽然算法偏向了if&else,但是最终的结果并没有跟try&catch拉开更大的距离,反而缩小了一倍.

在执行30000行测试的时候发现内存占用特别厉害,将近100%,可能是在这方面遇上瓶颈,于是我缩减10000行代码再试试看.

1.1    20000lines     1.430s

1.2    20000lines    1.755s

2.1    20000lines    1.404s

2.2   20000lines    1.851s

3.1    20000lines    1.410s

3.2   20000lines    1.764s

现在是try&catch方式执行30000行的记录:

1.1    20000lines    3.536s

1.2    20000lines    4.092s

2.1    20000lines   3.613s

2.2    20000lines   4.103s

3.1    20000lines    3.558s

3.2    20000lines    4.080s

我郁闷了.不过也没关系,事实已经证明了try&catch是性能低下的东西.

本节总结:

本节使用杨辉三角的优化(较前一节)算法,并且提升了运算量,但结果if&else的优势反而变小了,先不管原因是什么,以后您还会在代码中大量的"踹"一下再"咔吃"一下吗?事实上很多异常都可以提前判断,特别是"引用类型是否为空"和指定格式"是否为预期类型"的判断基本上不需要使用try&catch块,特别在大量的循环中.

try&catch的确给我们带来方便,但它的性能也的确难以恭维,另外也降低了我们原有的编写代码的谨慎程度.所以在我个人编写的代码中一般是不会出现try&catch的,除非必要.

好了,本节也正式完成,但是因为杨辉三角的复杂度导致程序运算中try&catch发生的概率难以确定(确定是肯定可以,但是我实在懒得去验证,很久没有拿笔在草纸上计算数学问题了).那么下一节我将以一个简单算法来验证if&else和try&catch之间的性能,我们至少要确定一次循环内至少触发了多少次try和catch,或执行了多少次判断才能更精确地确定这两者的性能.那么如果您还有兴趣看下去的话,咱们就在下一篇"不见不散" : )

16三/100

浅测Try&Catch的性能到底有多差(1)

今天中午本来是java课老师叫我们用它来实现杨辉三角.当初已经临近下课,由于用不惯Eclipse,一直卡在一个下标越界的错误上,直到下课也没把程序正常运行出来.晚上回宿舍就特别不爽,于是用C#将之重写了一遍,首次测试运行,结果成功.心理稍稍有点欣喜.

由于未来的就业压力,我脑子突然一热,再想未来面试途中如果面试官刚好叫我输出杨辉三角我应该怎么写它比较快,切不容易错呢?在我的办法呈现之前,先把我的算法代码贴上来(由于本人数学的那根筋比较细,算法不一定是最佳算法)

public class MyClass
{
	public static void Main()
	{
		int maxLine=8;
		int[][] ns=new int[maxLine][];
		for(int i=0;i<maxLine;i++)
		{
			ns[i]=new int[i+1];
			for(int j=0;j<i+1;j++)
			{
				if(i-1<0)
				{
					ns[i][j]=1;
					continue;
				}
				else if(j - 1 < 0)
				{
					ns[i][j]=1;
					continue;
				}
				else if(ns[i-1].Length<j+1)
				{
					ns[i][j]=1;
					continue;
				}
				else
				{
					ns[i][j]=ns[i-1][j-1]+ns[i-1][j];
				}
			}
		}

		for(int i=0;i<MaxLine;i++)
		{
			for(int j=0;j<i+1;j++)
			{
			    int t=ns[i][j];
				if(t<10)
					Console.Write(" ");
				Console.Write(t.ToString()+" ");
			}
			Console.WriteLine("");
		}
		Console.ReadLine();
	}
}

于是结果如下:

是不是相当标准 : )
好了,由于前面的代码有一长串if和else语句,个人认为这个代码丑了点,于是决定直接不判断,直接try和catch一下.于是上面的代码发生的变化:

for(int i=0;i<maxLine;i++)
{
        try
        {
            ns[i][j]=ns[i-1][j-1]+ns[i-1][j];
        }
        catch
	{
	    ns[i][j]=1;
	}
}

现在代码清爽了点.但是由于以前听说过try&catch的性能相当的差,于是谨慎得测试一下性能会比较好,而且到头来还能多长点见识.于是改写代码如下(这时就不输出结果了,只是纯粹后台操作):

public class MyClass
{
	public static void Main()
	{
		int maxLine=10000;
		int[][] ns=new int[maxLine][];
		Stopwatch sw=new Stopwatch();
		for(int t=0;t<5;t++)
		{
			sw.Reset();
			sw.Start();
			for(int i=0;i<maxLine;i++)
			{
				ns[i]=new int[i+1];
				for(int j=0;j<i+1;j++)
				{
					if(i-1<0)
					{
						ns[i][j]=1;
						continue;
					}
					else if(j - 1 < 0)
					{
						ns[i][j]=1;
						continue;
					}
					else if(ns[i-1].Length<j+1)
					{
						ns[i][j]=1;
						continue;
					}
					else
					{
						ns[i][j]=ns[i-1][j-1]+ns[i-1][j];
					}
//					try
//					{
//						ns[i][j]=ns[i-1][j-1]+ns[i-1][j];
//					}
//					catch(Exception ex)
//					{
//						ns[i][j]=1;
//					}
				}
			}
			sw.Stop();
			Console.WriteLine(sw.Elapsed.ToString());
		}
		Console.ReadLine();
	}
}

我先是测试了一下多次if和else的10000行操作:
结果我已经截图下来了:总共10次.


10000万行的总共用时大概在0.5秒左右.而这次我要运行try&catch块的代码:


这个结果果然验证了try&catch性能差的命题了!相差3~4倍的时间!
所以如果您下次要在超级循环中用到try&catch的时候,该谨慎点了.