C#性能优化的分析,其实问题很简单,但是分析的比较透彻

昨天去一个外企去面试,面试官是公司的一个副总,技术出身,所以聊了我的一些经历之后问了一些C++方面的问题,不过还是static, 线程和进程等问题,回答得还可以,然后就说出一道编程题让我在小白板上做一下。

题目是使用C#编程,实现一个函数,该函数将一个字符串List(任何一种List)中与给定字符串相同的字符串全部删去。

题目倒不是很复杂,一会儿就写出来了,如下所示:

 public virtual void Trim(IList<string> list, string s)
{
if (list == null || s == null)
{
throw new ArgumentNullException("list/s");
}

for (int i = 0; i < list.Count; )
{
if (list[i].Equals(s))
{
list.RemoveAt(i);
}
else i++;
}
}

开始问我i++为什么不放进for的括号内。当然不能了,因为删去当前的字符串项之后,i是不是再增1的,因为删除之后后面的项会向前提,实际上此时的i已经是下一项了。

然后,又问如果想改进该函数的性能,可能改的地方有哪些,应该如何修改。

为了测试该函数的性能,及改进后函数的性能,编写了一个类封装该函数,然后使用Template Method模式,将Trim定义为虚函数,Run()函数作为测试函数,在 Run()函数中调用Trim()函数,在继承的类中重写Trim()为改进后的函数,而Run()不需要更改,便于测试,将测试类封装为如下类 TrimStringList:

class TrimStringList
{
private List<string> _list;
private string dst;
private int times;
/// <summary>
///
/// </summary>
/// <param name="runCount">The times to be run;</param>
/// <param name="length">The length of the list to be tested</param>
public TrimStringList(int length, int runCount)
{
if (length == 0 || runCount == 0)
throw new ArgumentException("the argument length and runCount can't equals to 0");
_list = new List<string>();
Random ran = new Random();
dst = ran.Next(length).ToString();
times = runCount;

for (int i = 0; i < length; i++)
{
_list.Add(ran.Next(length).ToString());
}
}

public TimeSpan Run()
{
int i = times;

Console.WriteLine("/nRunning " + this.GetType().Name);
DateTime startTime = DateTime.Now;
while (i-- > 0)
{
List<string> l = new List<string>();
l.AddRange(_list);
Trim(l, dst);
}
DateTime endTime = DateTime.Now;
TimeSpan cost = endTime - startTime;
Console.WriteLine("Time cost=" + cost);
return cost;
}


/// <summary>
/// Remove all the strings that equals to s in the list
/// </summary>
/// <param name="list"></param>
/// <param name="s"></param>
public virtual void Trim(IList<string> list, string s)
{
if (list == null || s == null)
{
throw new ArgumentNullException("list/s");
}

for (int i = 0; i < list.Count; )
{
if (list[i].Equals(s))
{
list.RemoveAt(i);
}
else i++;
}
}
}

既然有循环,要提高性能肯定是减少每次循环的运算量。这个函数明显消耗时间的一个地方就是list.Count,因为这是一个属性,比使用变量或者 字段都是要消耗资源的,因为属性的读取实际上跟调用函数的开销是一样的,所以可以把这里的Count属性换到for外面,使用一个临时变量保存,但是这个 变量是变的可不是读一次就完了。再分析,什么情况下Count会变,那就是list中的项被删除时,即list.RemoveAt()被执行的时候,只要 这时候将临时变量减1就行了,修改后的代码如下所示。

class TrimStringListNoCount : TrimStringList
{
public TrimStringListNoCount(int len, int time)
: base(len, time)
{
}


public override void Trim(IList<string> list, string s)
{
if (list == null || s == null)
{
throw new ArgumentNullException("list/s");
}

int count = list.Count;

for (int i = 0; i < count; )
{
if (list[i].Equals(s))
{
list.RemoveAt(i);
count--;
}
else i++;
}

}
}

面试官对我的这个方案给予了肯定,说方案很不错,但是还有地方可以改,说到用指针,他就笑,说没必要把关公请来吧,那样改动太大了,就是用现在的 for循环,可能当时也有点儿紧张吧,真找不到可以改进的地方了。回来以后又想了想,其实i++改成++i可能会快点儿,但整个循环使用两个变量好像不是 太必要,于是改用了while循环实现如下:

    class TrimStringListWhile : TrimStringList
{
public TrimStringListWhile(int len, int count)
: base(len, count)
{
}

public override void Trim(IList<string> list, string s)
{
if (list == null || s == null)
{
throw new ArgumentNullException("list/s");
}

int count = list.Count;

while (count-- > 0)
{
if (list[count].Equals(s))
{
list.RemoveAt(count);
}
}

}
}

但仔细看,count--实际上其表达式的值为自增前的count,这样不免在程序执行过程中会使用一个中间变量保存这个值,然后执行完比较操作再将该表达式存回count,实际上这一过程是没有必要的,于是做了如下改进

    class TrimStringListWhilePlus : TrimStringListWhile
{
public TrimStringListWhilePlus(int len, int count)
: base(len, count)
{
}

/// <summary>
/// 将--放到了后面,因为上一个版本在while()中自减需要后减操作,那样会保存一个临时变量,然后再把while的条件
/// 判断执行后再将新值给count。把--操作改成先减,这样汇编语言中有直接指令INC支持,会使操作加快
/// </summary>
/// <param name="list"></param>
/// <param name="s"></param>
public override void Trim(IList<string> list, string s)
{
if (list == null || s == null)
{
throw new ArgumentNullException("list/s");
}
int count = list.Count;

while (count > 0)
{
if (list[--count].Equals(s))
{
list.RemoveAt(count);
}
}
}
}

这里对list的遍历不能使用foreach,因为这里会修改list,修改后会抛出异常。

下面编程测试几个实现的性能,测试程序如下:

    class Program
{
static void Main(string[] args)
{
int len = 800;
int count = 1000;
int cycle = 1000;
TimeSpan t1 = new TimeSpan() ,
t2 = new TimeSpan() ,
t3 = new TimeSpan() ,
t4 = new TimeSpan() ;


do
{
TrimStringList test1 = new TrimStringList(len, count);
t1 += test1.Run();

TrimStringListNoCount test2 = new TrimStringListNoCount(len, count);
t2 += test2.Run();


TrimStringListWhile test3 = new TrimStringListWhile(len, count);
t3 += test3.Run();

TrimStringListWhilePlus test4 = new TrimStringListWhilePlus(len, count);
t4 += test4.Run();

} while (--cycle > 0);

Console.WriteLine("/n =============== Statistics ====================/n");
Console.WriteLine("improvement:{0:P2}, {1:P2}, {2:P2}", Radio(t1, t2) , Radio(t1, t3) , Radio(t1, t4));

Console.ReadKey();
}

static double Radio(TimeSpan t1, TimeSpan t2)
{
return (t1.TotalMilliseconds - t2.TotalMilliseconds) / t1.TotalMilliseconds;
}

}

因为单次运行的话各个函数使用的时间不尽相同,于是运行1000遍求平均值,下面是运行结果,可能两次运行结果不完全相同,但相差不大。

......
Running TrimStringList
Time cost=00:00:00.0312500

Running TrimStringListNoCount
Time cost=00:00:00.0312500

Running TrimStringListWhile
Time cost=00:00:00.0312500

Running TrimStringListWhilePlus
Time cost=00:00:00.0312500

=============== Statistics ==============

improvement:11.90%, 14.03%, 14.29%

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C#中,可以使用性能分析工具来帮助我们找出代码中的性能瓶颈,并进行优化。下面是一些常用的性能分析工具和使用方法: 1. Visual Studio性能分析器:Visual Studio自带了一个强大的性能分析器,可以帮助我们分析和优化C#代码的性能。使用方法如下: - 打开要进行性能分析的项目。 - 在“调试”菜单中选择“性能分析器”。 - 选择要进行性能分析的启动项(例如,选择“CPU性能分析”)。 - 点击“开始分析”按钮,运行代码并进行性能分析。 - 分析结果将显示在性能分析器窗口中,可以查看函数调用图、CPU使用情况、内存使用情况等信息,从而找出性能瓶颈。 2. JetBrains dotTrace:dotTrace是一款功能强大的性能分析工具,可以帮助我们找出C#代码中的性能问题。使用方法如下: - 安装并打开dotTrace。 - 选择要进行性能分析的应用程序或进程。 - 点击“开始分析”按钮,运行代码并进行性能分析。 - 分析结果将显示在dotTrace窗口中,可以查看函数调用图、CPU使用情况、内存使用情况等信息,从而找出性能瓶颈。 3. PerfView:PerfView是一个免费的性能分析工具,由微软提供。使用方法如下: - 下载并打开PerfView。 - 选择要进行性能分析的应用程序或进程。 - 点击“Collect”按钮,运行代码并进行性能分析。 - 分析结果将显示在PerfView窗口中,可以查看函数调用图、CPU使用情况、内存使用情况等信息,从而找出性能瓶颈。 使用性能分析工具进行优化时,可以关注以下几个方面: - CPU使用情况:查看代码中哪些函数占用了大量的CPU时间,是否存在性能瓶颈。 - 内存使用情况:查看代码中是否存在内存泄漏或者频繁的垃圾回收。 - 函数调用图:查看函数之间的调用关系,找出函数调用次数较多或者耗时较长的地方。 通过性能分析工具的帮助,我们可以更加直观地了解代码的性能问题,并进行有针对性的优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值