从一次重复造轮子中发现的一个性能优化思路。

clark523 2013-10-25 04:25:54
起因是这样的,自己出于各种原因,一方面是兴趣,一方面是目前市面上没有找到比较合适的开源项目。
所以自己重复造轮子写了一个IOC容器。
一开始很简单就是使用Dictionary来进行缓存,使用Reflect进行创建。
在优化过程中,我发现c#中可以用这么一个有趣的写法来进行提升效率。
一开始我的缓存的写法是这样的。

Dictionary<Type, Dictionary<string, RegistObjectContext>()> dic;
if(!dic.ContainsKey(key)){
dic.Add(key, new Dictionary<string, RegistObjectContext>());
}
cDic = dic[key];


而优化后是这样的

try
{
cDic = dic[pType];
}
catch (KeyNotFoundException)
{
dic.Add(pType, new Dictionary<string, RegistObjectContext>());
cDic = dic[pType];
}


在进行100w次运算,优化前和优化后的写法,大概有100-200ms的区别。
为什么会这样呢?
就是因为有些时候我们进行验证所消耗的性能反而高于抛出特定异常所消耗的性能。
这样,我们在有些时候就可以利用这些特定异常来跳过判定过程,以达到提速的目的。
希望能对大家有所帮助
...全文
644 31 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
31 条回复
切换为时间正序
请发表友善的回复…
发表回复
clark523 2013-10-28
  • 打赏
  • 举报
回复
引用 28 楼 liuxiaoyi666 的回复:
引用 29 楼 sj178220709 的回复:
@两位来看一下我这个测试能说明问题不。
clark523 2013-10-28
  • 打赏
  • 举报
回复
1楼的测试方案本身也不是为了测试这个的效率而写的,主要是测试其他场景而做。
下面的截图是我根据命中率来重新写的一个测试。

都是执行的1000w次。
在0%命中率的情况下。
tryCatch 要比Normal快3%左右
在50%命中率的情况下。
tryCatch 要比Normal快10%左右
在近似100%命中率的情况下。
tryCatch 要比Normal快35%左右。
当然我这个由于样本比较少,只执行了一次。
有兴趣的同学可以自己跑一下测试。
由此至少我们可以悲观的得到,在0%命中率的情况下,tryCatch不会比Normal效率低。
而效率成长与命中率是成正比的。

测试代码如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TryCatchTest
{
class Program
{


private static string[] strUnit = { "bytes", "kb", "mb", "gb" };

private static string[] strDateUnit = { "ms", "s", "m", "h" };

private string readCommand()
{
Console.Write("command:");
var command = Console.ReadKey();
Console.WriteLine();
return command.KeyChar.ToString();
}

static void Main(string[] args)
{
Program program = new Program();
Console.WriteLine("Please input howmany times to run");
int count = int.Parse(Console.ReadLine());
Console.WriteLine("Please enter command...");
Console.WriteLine("1. run all new normal");
Console.WriteLine("2. run all new tryCatch");
Console.WriteLine("3. run half new normal");
Console.WriteLine("4. run half new tryCatch");
Console.WriteLine("0. exit");

var command = program.readCommand();
Dictionary<string, string> dic;
try
{
while (command != "0")
{
switch (command)
{
case "1":
dic = new Dictionary<string, string>();
program.run(count, p =>
{
string key = p.ToString();
if (!dic.ContainsKey(key))
{
dic.Add(key, key);
}
else
{
dic[key] = key;
}
}, "all new normal");
break;
case "2":
dic = new Dictionary<string, string>();
program.run(count, p =>
{
string key = p.ToString();
try
{
dic[key] = key;
}
catch (KeyNotFoundException)
{
dic.Add(key, key);
}
}, "all new tryCatch");
break;
case "3":
dic = new Dictionary<string, string>();
program.run(count, p =>
{
string key = (p / 2).ToString();
if (!dic.ContainsKey(key))
{
dic.Add(key, key);
}
else
{
dic[key] = key;
}
}, "half new normal");
break;
case "4":
dic = new Dictionary<string, string>();
program.run(count, p =>
{
string key = (p / 2).ToString();
try
{
dic[key] = key;
}
catch (KeyNotFoundException)
{
dic.Add(key, key);
}
}, "half new tryCatch");
break;
case "5":
dic = new Dictionary<string, string>();
program.run(count, p =>
{
string key = "1";
if (!dic.ContainsKey(key))
{
dic.Add(key, key);
}
else
{
dic[key] = key;
}
}, "half new normal");
break;
case "6":
dic = new Dictionary<string, string>();
program.run(count, p =>
{
string key = "1";
try
{
dic[key] = key;
}
catch (KeyNotFoundException)
{
dic.Add(key, key);
}
}, "1 new tryCatch");
break;
default:
dic = new Dictionary<string, string>();
break;
}
Console.WriteLine("dic length {0}", dic.Count.ToString());
command = program.readCommand();
}
}
catch (OutOfMemoryException ex)
{
Console.WriteLine("create too many instance, please minus the instace count, press enter to quit");
Console.ReadLine();
}
}

private void run(int count, Action<int> func, string methodName)
{
var sDate = DateTime.Now;
var originUse = GC.GetTotalMemory(false);
for (var i = 0; i < count; i++)
{
func(i);
}
var currentUse = GC.GetTotalMemory(false);
var eDate = DateTime.Now;
double use = currentUse - originUse;
var dateDiff = eDate - sDate;

int index = 0;

var unit = strUnit[index];

var dateUnit = strDateUnit[index];

while (Math.Abs(use) > 1024)
{
use = use / 1024;
index++;
unit = strUnit[index];
}

index = 0;

double ms = dateDiff.TotalMilliseconds;

while (ms > 1000)
{
ms = ms / 1000;
index++;
dateUnit = strDateUnit[index];
}

Console.WriteLine("use {1} run{0} times use time {2}{3}", count, methodName, ms, dateUnit);
}
}
}



  • 打赏
  • 举报
回复
引用 26 楼 clark523 的回复:
[quote=引用 25 楼 sj178220709 的回复:] cDic = dic[key]; 这个地方,楼主改一下,再测试看看。 try会额外消耗性能,这是肯定的, 但是你第一种多查询了一次。 至于第二种方法,还要看cache命中率,如果很高,第二种还有点优势,但如果命中率比较低,则异常引发次数太多,肯定不行。 不过归根结底来说,TryGetValue还是最好的,代码更简洁优美,这种内存操作,性能从来不是问题,优化的意义不大。
唉。。。还是没有办法脱离传统思想。 TryGetValue和我的第一种效率是完全一样的。 [/quote] 提个观点,大家探讨下是好的,但是楼主貌似有点小得瑟了啊。 传统之所以是传统,就是因为它的普适性,它确实是人们在生产中总结出来的精华。 你的优化方案,使用try catch这种重型武器,就为了干这点小事,有些过了。 代码量更大,思路更绕是否值得? 而且,好几个人包括我都质疑了你这样做是否能优化性能,恕我直言,你在一楼的测试方案,简直就是粗鄙不堪的。 命中率在这个场景中,是非常重要的一个因素,提出来了,你也不能正面应对。
  • 打赏
  • 举报
回复
cDic = dic[key]; 这个地方,楼主改一下,再测试看看。 try会额外消耗性能,这是肯定的, 但是你第一种多查询了一次。 至于第二种方法,还要看cache命中率,如果很高,第二种还有点优势,但如果命中率比较低,则异常引发次数太多,肯定不行。 不过归根结底来说,TryGetValue还是最好的,代码更简洁优美,这种内存操作,性能从来不是问题,优化的意义不大。
  • 打赏
  • 举报
回复
可以考虑一下你的样本 比如说 你的dic中的数据是123 你要返还的数据的样本是2141 你的两种不同的代码的执行是 第一种 带containskey的执行 判断是否有key为2 赋值key为2 判断是否有key为1 赋值key为1 判断是否有key为4 初始化key为4 赋值key为4 判断是否有key为1 赋值key为1 总共9步 第二种 try的 赋值key为2 赋值key为1 赋值key为4 key为4出错 初始化key为4 赋值key为4 赋值key为1 总共7步 以此为例,你的样本对性能的影响很大,所以暂时看来 论据不足以支持论点 不足以得出try catch比containskey的性能要优化。现在可以得出的结论是,在预知样本命中率显著 高于非命中的时候try catch比containskey方案要优化,并不能绝对保证性能的优化 有待于楼主给出抽样 样本 来实际反驳 -------------------------------------------------------------------- 另外从执行角度而言,try等于是在method(也可能是其他的如property)层面又加了一层statement, 也就是多了一层副本,如果出错可以保证回滚的机制,本身会有一定的性能开销 ,如果try中间的语句较多,相信也会很大程度上影响测试结果
clark523 2013-10-27
  • 打赏
  • 举报
回复
引用 25 楼 sj178220709 的回复:
cDic = dic[key]; 这个地方,楼主改一下,再测试看看。 try会额外消耗性能,这是肯定的, 但是你第一种多查询了一次。 至于第二种方法,还要看cache命中率,如果很高,第二种还有点优势,但如果命中率比较低,则异常引发次数太多,肯定不行。 不过归根结底来说,TryGetValue还是最好的,代码更简洁优美,这种内存操作,性能从来不是问题,优化的意义不大。
这里为什么我说第二种是优化后的代码呢。 因为try catch的性能损耗是低于 Check的性能损耗的。 而无论命中率如何,都会进行一次Check. 这就是优化点了。 另外。 并不是说这个优化的意义有多大。 只是我觉得这个是一个非常新的优化思路。也许在我这个场景只是很小的优化。 但是当你觉得无法再进行优化时,这个思路可能会开阔你的视野。 这也是我为什么要分享在论坛里面的初衷。 或许我这个解决方案比较片面。 但是到目前为止还没有看见任何一个同学在事实上来推翻我这个优化是错误的证据。 只看见很多都是觉得不行,觉得不对。觉得没有意义。 这让我对于本论坛很失望。
clark523 2013-10-27
  • 打赏
  • 举报
回复
引用 25 楼 sj178220709 的回复:
cDic = dic[key]; 这个地方,楼主改一下,再测试看看。 try会额外消耗性能,这是肯定的, 但是你第一种多查询了一次。 至于第二种方法,还要看cache命中率,如果很高,第二种还有点优势,但如果命中率比较低,则异常引发次数太多,肯定不行。 不过归根结底来说,TryGetValue还是最好的,代码更简洁优美,这种内存操作,性能从来不是问题,优化的意义不大。
唉。。。还是没有办法脱离传统思想。 TryGetValue和我的第一种效率是完全一样的。
clark523 2013-10-26
  • 打赏
  • 举报
回复
引用 21 楼 caozhy 的回复:
我发现吃自助餐前先拉泡屎再去吃,我就可以多赚一点,这么创新的成果,有没有必要向大家汇报下。
你这水平是怎么当上的版主啊?
clark523 2013-10-26
  • 打赏
  • 举报
回复
引用 18 楼 sp1234 的回复:
如果是100W次加起来发现有100~200ms差别,那么这基本上就可以认定为没有什么差别。
请问你知道100w执行所需要的时间么? 是优化前是1.4秒,优化后是1.2秒也就是说是10%-20%的提速。能叫没什么差别??? 另。请看我7楼的回复。里面已经解释清楚了。为什么不用TryGetValue.
缪军 2013-10-25
  • 打赏
  • 举报
回复
引用 21 楼 caozhy 的回复:
我发现吃自助餐前先拉泡屎再去吃,我就可以多赚一点,这么创新的成果,有没有必要向大家汇报下。
这么龌龊的回复你也说得出口, 你还记得当初怎么牛逼烘烘的毛遂自荐来当版主的么? 你骂人骂习惯了,累教不改
threenewbee 2013-10-25
  • 打赏
  • 举报
回复
我发现吃自助餐前先拉泡屎再去吃,我就可以多赚一点,这么创新的成果,有没有必要向大家汇报下。
  • 打赏
  • 举报
回复
哦,看错了。sorry。 我同意#17楼的意见。基本上我们没有先查询然后再查询,都是使用 TryGetValue 一次查询的。所以看到你的代码实在是太陌生了,所以一下子没有看懂。
  • 打赏
  • 举报
回复
你的代码有一点费解: Dictionary<Type, Dictionary<string, RegistObjectContext>()> dic; if(!dic.ContainsKey(key)){ dic.Add(key, new Dictionary<string, RegistObjectContext>()); } cDic = dic[key]; 为什么插入之后,再查找一遍?直接写 Dictionary<Type, Dictionary<string, RegistObjectContext>()> dic; cDic = new Dictionary<string, RegistObjectContext>() if(!dic.ContainsKey(key)) dic.Add(key, cDic); 岂不是更好嘛!
  • 打赏
  • 举报
回复
如果是100W次加起来发现有100~200ms差别,那么这基本上就可以认定为没有什么差别。
showjim 2013-10-25
  • 打赏
  • 举报
回复
最好是用TryGetValue
cheng2005 2013-10-25
  • 打赏
  • 举报
回复
对不起,我踩到您的尾巴了。
clark523 2013-10-25
  • 打赏
  • 举报
回复
算了。懒得搭理你了。你就不是来讨论问题的。而是不知道在哪里炫耀什么莫名的优越感的。。
clark523 2013-10-25
  • 打赏
  • 举报
回复
引用 12 楼 wddw1986 的回复:
[quote=引用 11 楼 clark523 的回复:] 请拿事实说话,即使是你说的情况,也不一定是第一种效率高。 请注意,我每次都少了一次FindEntry的执行。 你通过什么判断的FindEntry的执行消耗是低于Try Catch???
麻烦你把我的回帖 [size=48px]好好[/size] 看一遍,我哪句话说过第一种效率高?[/quote]
引用 9 楼 wddw1986 的回复:
测试样本的构成是会对结果造成影响的。最简单的例子,如果你的值大部分已经在缓存里了,那么必然是第二种效率高,如果你的缓存是空的,每次取得都是新值,那么必然是前一种高。 你分析过测试样本吗?有吗?
这句话不是你说的?
clark523 2013-10-25
  • 打赏
  • 举报
回复
引用 10 楼 wddw1986 的回复:
另外 cDic = dic[pType]; 这个也是有问题的。 新插入的没必要再去取一遍,为了省一行代码结果多一次查找。
你说得没错。 这一次查找是有点没有意义,如果是新插入的话。 但是貌似这个是不是我这篇帖子的重点吧? 我的帖子是想通过这个例子来说明我们在写代码的时候可以用一些更巧妙的思路来优化性能。
cheng2005 2013-10-25
  • 打赏
  • 举报
回复
引用 11 楼 clark523 的回复:
请拿事实说话,即使是你说的情况,也不一定是第一种效率高。 请注意,我每次都少了一次FindEntry的执行。 你通过什么判断的FindEntry的执行消耗是低于Try Catch???
麻烦你把我的回帖 [size=48px]好好[/size] 看一遍,我哪句话说过第一种效率高?
加载更多回复(11)

111,089

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • AIGC Browser
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧