之前一直认为
for (int i = 0, h = arr.Count; i < h; i++)
和
for (int i = 0; i < arr.Count; i++)
两种写法,在C#里应该是差不多的,今天突然有人问,就写了个程序测试了一下,结果出乎我的意料
如果arr是List<T>,前者的效率比后者高大约一倍,如果arr是string[],两者效率基本差不多测试代码:
int tnum = 100000; // 添加或查找的次数
int outnum = 10; // 外层循环次数List<string> arr = new List<string>();
for(int i=0;i<tnum;i++)
arr.Add(i.ToString());
string[] arr2 = new string[tnum];for(int j=0;j<outnum;j++)
{
Stopwatch watch = new Stopwatch();
string msg; msg = "Number ";
watch.Reset();
watch.Start();
for (int i = 0, h = arr.Count; i < h; i++)
{
}
watch.Stop();
Console.WriteLine(msg + "耗时:" + watch.ElapsedTicks.ToString()); msg = ".Count ";
watch.Reset();
watch.Start();
for (int i = 0; i < arr.Count; i++)
{
}
watch.Stop();
Console.WriteLine(msg + "耗时:" + watch.ElapsedTicks.ToString()); msg = "Length ";
watch.Reset();
watch.Start();
for (int i = 0; i < arr2.Length; i++)
{
}
watch.Stop();
Console.WriteLine(msg + "耗时:" + watch.ElapsedTicks.ToString());
}
for (int i = 0, h = arr.Count; i < h; i++)
和
for (int i = 0; i < arr.Count; i++)
两种写法,在C#里应该是差不多的,今天突然有人问,就写了个程序测试了一下,结果出乎我的意料
如果arr是List<T>,前者的效率比后者高大约一倍,如果arr是string[],两者效率基本差不多测试代码:
int tnum = 100000; // 添加或查找的次数
int outnum = 10; // 外层循环次数List<string> arr = new List<string>();
for(int i=0;i<tnum;i++)
arr.Add(i.ToString());
string[] arr2 = new string[tnum];for(int j=0;j<outnum;j++)
{
Stopwatch watch = new Stopwatch();
string msg; msg = "Number ";
watch.Reset();
watch.Start();
for (int i = 0, h = arr.Count; i < h; i++)
{
}
watch.Stop();
Console.WriteLine(msg + "耗时:" + watch.ElapsedTicks.ToString()); msg = ".Count ";
watch.Reset();
watch.Start();
for (int i = 0; i < arr.Count; i++)
{
}
watch.Stop();
Console.WriteLine(msg + "耗时:" + watch.ElapsedTicks.ToString()); msg = "Length ";
watch.Reset();
watch.Start();
for (int i = 0; i < arr2.Length; i++)
{
}
watch.Stop();
Console.WriteLine(msg + "耗时:" + watch.ElapsedTicks.ToString());
}
当然,这个不是影响代码性能的重点,只是平时写代码时,顺手就写上效率较高的代码,会更好。类似的提升效率的地方,还有拼接字符串时,值类型一定要加上ToString()
比如用:string a = "aa" + 123.ToString();
而不是用string a = "aa" + 123
字符串比较或查找,请加上StringComparison.Ordinal参数如果要忽略大小写,请使用StringComparison.OrdinalIgnoreCase
比如要使用:str.IndexOf("abc", StringComparison.Ordinal)
而不是使用:str.IndexOf("abc"),这个等于str.IndexOf(value,StringComparison.CurrentCulture)
StringComparison.CurrentCulture:使用区域敏感排序规则和当前区域比较字符串
StringComparison.Ordinal:使用序号排序规则比较字符串
即使arr是string[]、也会差很多的
一般for中的第二个条件一般都是2个变量、
我很少用不是变量的数存进去
List的Count,代码是return this._size;估计时间是消耗在对象引用查找上
HashTable、Dictionary、SortedList、SortedDictionary等字典使用 a、使用字典的TryGetValue方法,如:
Dictionary<string, string> abc;
string a;
if(!abc.TryGetValue(key, out a)){
//key不存在
}else{
}
而不要用下面的代码,因为下面的代码重复查找了2次key:
if(!abc.ContainKeys(key))
{
//key不存在
}else{
a = abc[key];
}
b、删除字典的key时,直接使用Remove方法,不要事先判断,比如:
Dictionary<string, string> abc;
if(abc.Remove(key)){// 没必要先判断ContainKeys,重复查找,浪费性能
//key存在,且移除成功
}else{
//key不存在,或移除失败
}
c、插入元素时,直接使用this[key] = value,如:
abc[key] = value;// 注意需求,如果允许覆盖才可以用
而不需要:if(!abc.ContainsKey(key))abc.Add(key, value);
反编译代码,可以看到Add和this[]是调用同一个方法的
相当于
h = arr.Count;
for (int i = 0; i < h; i++)
for (int i = 0, h = arr.Count; i < h; i++)
IL_0054: ldc.i4.0
IL_0055: stloc.s V_8
IL_0057: ldloc.2
IL_0058: callvirt instance int32 class [mscorlib]System.Collections.Generic.List`1<string>::get_Count()
IL_005d: stloc.s h
IL_005f: br.s IL_0067
IL_0061: ldloc.s V_8
IL_0063: ldc.i4.1
IL_0064: add
IL_0065: stloc.s V_8
IL_0067: ldloc.s V_8
IL_0069: ldloc.s h
IL_006b: blt.s IL_0061
for (int i = 0; i < arr2.Length; i++)
IL_00aa: ldc.i4.0
IL_00ab: stloc.s V_10
IL_00ad: br.s IL_00b5
IL_00af: ldloc.s V_10
IL_00b1: ldc.i4.1
IL_00b2: add
IL_00b3: stloc.s V_10
IL_00b5: ldloc.s V_10
IL_00b7: ldloc.s arr2
IL_00b9: ldlen
IL_00ba: conv.i4
IL_00bb: blt.s IL_00af
for (int i = 0; i < arr.Count; i++)
IL_00fa: ldc.i4.0
IL_00fb: stloc.s V_11
IL_00fd: br.s IL_0105
IL_00ff: ldloc.s V_11
IL_0101: ldc.i4.1
IL_0102: add
IL_0103: stloc.s V_11
IL_0105: ldloc.s V_11
IL_0107: ldloc.2
IL_0108: callvirt instance int32 class [mscorlib]System.Collections.Generic.List`1<string>::get_Count()
IL_010d: blt.s IL_00ff
时间应该就是花在i < arr.Count里,每次循环都去调用get_Count()方法了,
难道是这一步要取对象引用,再获取对象的方法返回值导致的耗时吗,呵呵
如原来为了省事,复制就算了:
if (指定.IndexOf(转换.Substring(字符, 1)) >= 0) 等效 += 指定.Substring(指定.IndexOf(转换.Substring(字符, 1)), 1);
显然上面这样写是消耗的写法,是不尽合理的,应该优化如下:
int 位置 = 指定内容.IndexOf(转换内容.Substring(字符, 1));
if (位置 >= 0) 等效 += 指定.Substring(位置, 1);
只要不重复执行同样转换应该就可以了。
主要时间还是浪费在每次循环都需要调用get方法吧
最终还是被JIT优化了??
请教一下il代码怎么显示出来的,还有如何去阅读?
上次我想在.net里面找这个功能的,没有找到
static void Main(string[] args)
{
List<int> li = new List<int>(1000000);
for (int i = 0; i < li.Capacity; i++)
{
li.Add(i);
}
DateTime d1 = DateTime.Now;
//for (int i = 0; i < li.Count; i++)
//{
// li[i] = li[i] * 100;
//}
for (int i = 0, h = li.Count; i < h; i++)
{
li[i] = li[i] * 100;
}
DateTime d2 = DateTime.Now;
Console.WriteLine(d2-d1);
Console.Read();
}
因为count函数会被优化成直接访问内存。
而cpu有cache,最终这种访问内存的操作都会变成访问cache。
所以无论是哪一种,实际上是没有多少差别的。
我写的时候一般都是
int i,arr.count=N;
for(i=0;i<arr.count;i++)
今天学习了
至于泛型则不优化,前者肯定优于后者..
就是耗在i<arr.Count,每次要去取引用,而不是直接取值。
问题出在arr.Count。。
// 空操作
}根据测试结果,10 000 000 次循环如下:(时钟周期)Number 耗时:114359
.Count 耗时:166333
.Length 耗时:108633
Number 耗时:122153
.Count 耗时:164890
.Length 耗时:111406表面上看,array.Count最高(对比.Length)浪费约 53% 效率,似乎很高,但实际上呢?来看看花费的时间:(毫秒)
Number 耗时:41
.Count 耗时:48
.Length 耗时:32
Number 耗时:33
.Count 耗时:46
.Length 耗时:29也就是说即使是最慢的array.Count,平均单次花费时间最高也不超过 0.4us (约0.36us),如果按照循环体中操作耗时 10ms 计算(比较简单的循环体),array.Count浪费的时间相当于 0.004% 效率,就是说即便用了看起来最烂的array.Count方法,循环体仍最少保有原来的 99.996% 的效率。循环体内部耗时越长,array.Count浪费的效率越可以忽略不计所以在计算量小循环次数多的for中考虑这些就好了,也不必太在意,对于一般应用,哪种都一样。
两个都可以删减list,比如说前者:
for (int i = 0, h = arr.Count; i < h; i++)
如果你写成这样,就能删减list:
for (int h = arr.Count, int i = h-1, ; i >= 0; i--)
表头
不同的循环写法
1.
2.
3.另外,大家也看得出, 广告鼓吹的即时优化,还是比较xx的。 release的空循环优化,这么基本的编译器优化,都做不到。 (上面的是 x86 release的代码。 x64的release代码,也类似)
for (int i = 0, h = arr.Count; i < h; i++)
for (int i = 0; i < arr.Count; i++)
两者的 release代码,没区别, 都是 清空计数器edx(i=0)
把array 大小 move到eax。 都是 cmp edx, eax 比较两者,然后 jl (jump less)
第三种写法,也没什么差别。
总得说,三种写法都没差别。这些东西,在c/c++的人心中,都是很清楚,没有争议的。
如果, 判断条件的 arr.count 在循环体内,是有变化的。有变动的,那么, 必须在for的第二个条件,判断。
如果循环的count,循环体内没变动,那么,都是 直接成为一个常量,在 eax 里面。 (不管你用何种写法,独立变量,或是 循环判断句内)
我也蛋疼一次
楼上有几位从IL和汇编分析,我没他们那么蛋疼
我只从字段和属性分析
属性在IL层面,分变成函数
调用函数会不会比调用字段更快呢?
不得而知
试试千万次的蛋疼循环吧using System;
using System.Collections.Generic;
using System.Diagnostics;namespace ConsoleApplication1
{
public class Program
{
static void Main()
{
int tnum = 10000000; // 添加或查找的次数
int outnum = 10; // 外层循环次数
int iTmp = 0; List<TestClass> arr = new List<TestClass>(tnum);
for (int i = 0; i < tnum; ++i)
{
arr.Add(new TestClass());
} for (int j = 0; j < outnum; j++)
{
Stopwatch watch = new Stopwatch();
string msg; msg = "属性赋值 ";
watch.Reset();
watch.Start();
for (int i = 0; i < tnum; ++i)
{
arr[i].Property = i;
}
watch.Stop();
Console.WriteLine(msg + "耗时:" + watch.ElapsedTicks.ToString()); msg = "字段赋值 ";
watch.Reset();
watch.Start();
for (int i = 0; i < tnum; ++i)
{
arr[i].Field = i;
}
watch.Stop();
Console.WriteLine(msg + "耗时:" + watch.ElapsedTicks.ToString()); msg = "属性取值 ";
watch.Reset();
watch.Start();
for (int i = 0; i < tnum; ++i)
{
iTmp = arr[i].Property;
}
watch.Stop();
Console.WriteLine(msg + "耗时:" + watch.ElapsedTicks.ToString()); msg = "字段取值 ";
watch.Reset();
watch.Start();
for (int i = 0; i < tnum; ++i)
{
iTmp = arr[i].Field;
}
watch.Stop();
Console.WriteLine(msg + "耗时:" + watch.ElapsedTicks.ToString() + "\r\n");
} Console.ReadKey();
}
} public class TestClass
{
public Int32 Field;
public Int32 Property
{
get { return Field; }
set { Field = value; }
}
}
}
你忽略了一点,就是编程的目的,你说微软降低了编程人员所需的知识,我觉得应该换一句话,那就是微软让编程人员把时间花在了真正需要的地方--软件要实现的目标,我只是要写个酒店管理系统或者为测试人员编写测试工具,或者方便我自己日常生活的一些小工具,我根本没必要去了解这些东西在底层是如何实现的,你觉得用PS的必须要清除PS中每个功能是如何实现的吗?根本不需要,他们只需要了解这些功能的用途,能够熟练的把他们组合在一起做出漂亮的图就可以了
我们首要注意的是发布给客户的最终执行的机器代码是经过csc和JIT两次优化的,因此,要确定我们的代码的确有更高的实际运行效率,不仅要选择release版本,而且程序不能由调试器启动(调试模式),而是要直接运行
我个人也对运行效率有一种偏执的追求,但很多认为高效率的写法,最终多半发现其实是白费那个劲了
两个相关的链接:
http://technet.microsoft.com/zh-cn/query/ms241594
http://www.cnblogs.com/neoragex2002/archive/2006/12/25/sampleofjitoptimization.html
===========================================
List<T>应该是用链表来实现的吧,计算元素总数要从头到尾遍历一次,而string[]是数组,能直接得出元素个数。
这些都是数据结构中最基本的东西啊。
因为Javascript没有Release版本,而在Javascript里,是每次都要重新计算length,性能低下
你应该先看反编译后的代码,再来回答的
也许你们忘记了,现在是web的时代,很多人都不只是写服务端代码,也要写很多的客户端脚本,
如果你没有养成一个良好的代码习惯,认为
for (int i = 0; i < arr.Count; i++)
这个写法没有问题,那么你的js代码效率可想而知当然你可以说,我在服务端用这种写法,在js用另外一种,可能你记忆非常好,能做到,但我相信
一半以上像我一样的码农是做不到的我经常在js代码里写:
int a=123;
然后运行时出错,查错查半天,才发现是用var a=123;所以,我又重申一下我的建议:养成良好的代码习惯,不是可能出问题的代码习惯