C#程序优化一例

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

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

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

 public virtual void Trim(IList 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 _list;
        private string dst;
        private int times;
        /// 
        /// 
        /// 
	/// The times to be run;
        /// The length of the list to be tested
        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();
            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 l = new List();
                l.AddRange(_list);
                Trim(l, dst);
            }
            DateTime endTime = DateTime.Now;
            TimeSpan cost = endTime - startTime;
            Console.WriteLine("Time cost=" + cost);
            return cost;
        }


        /// 
        /// Remove all the strings that equals to s in the list
        /// 
        /// 
        /// 
        public virtual void Trim(IList 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 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 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)
        {
        }

        /// 
        /// 将--放到了后面,因为上一个版本在while()中自减需要后减操作,那样会保存一个临时变量,然后再把while的条件
        /// 判断执行后再将新值给count。把--操作改成先减,这样汇编语言中有直接指令INC支持,会使操作加快
        /// 
        /// 
        /// 
        public override void Trim(IList 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%
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值