下面一段代码,有两个方法,实现的功能是一样的,就是在 arraylist 表里 分别取出一个 string,为了让差别明显些,都循环999999次 private void H()
{
string str = "The quick brown fox jumps over a lazy dog";
ArrayList arrLst = new ArrayList();
for (int i = 0; i != 9; ++i)
{
arrLst.Add(str);
}
DateTime timeStart = DateTime.Now;
int arrLstCnt = arrLst.Count;
for (int i = 0; i != 999999; ++i)
{
for (int j = 0; j != arrLstCnt; ++j)
{
object o = arrLst[j];
}
}
DateTime timeEnd = DateTime.Now;
TimeSpan ts = timeEnd - timeStart;
this.Title = ts.TotalMilliseconds.ToString();
} private void I()
{
string str = "The quick brown fox jumps over a lazy dog";
ArrayList arrLst = new ArrayList();
for (int i = 0; i != 9; ++i)
{
arrLst.Add(str);
}
DateTime timeStart = DateTime.Now;
for (int i = 0; i != 999999; ++i)
{
foreach (object obj in arrLst)
{
object o = obj;
}
}
DateTime timeEnd = DateTime.Now;
TimeSpan ts = timeEnd - timeStart;
this.Title = ts.TotalMilliseconds.ToString();
}1.我测试表明,用for循环要比foreach遍历要快3倍,我只知道结果是这样,但对于造成这样的结果的准确原因还不知道,请高人解答,谢谢!
2.对于循环条件i != 999999,用 !=(不等号)而不是普通大家用的 <(小于号),这个问题我解决不清楚,因为C++ Primer推荐用的是!=,所以我一直用!=,我记得那书说过原因,但我忘了.
{
string str = "The quick brown fox jumps over a lazy dog";
ArrayList arrLst = new ArrayList();
for (int i = 0; i != 9; ++i)
{
arrLst.Add(str);
}
DateTime timeStart = DateTime.Now;
int arrLstCnt = arrLst.Count;
for (int i = 0; i != 999999; ++i)
{
for (int j = 0; j != arrLstCnt; ++j)
{
object o = arrLst[j];
}
}
DateTime timeEnd = DateTime.Now;
TimeSpan ts = timeEnd - timeStart;
this.Title = ts.TotalMilliseconds.ToString();
} private void I()
{
string str = "The quick brown fox jumps over a lazy dog";
ArrayList arrLst = new ArrayList();
for (int i = 0; i != 9; ++i)
{
arrLst.Add(str);
}
DateTime timeStart = DateTime.Now;
for (int i = 0; i != 999999; ++i)
{
foreach (object obj in arrLst)
{
object o = obj;
}
}
DateTime timeEnd = DateTime.Now;
TimeSpan ts = timeEnd - timeStart;
this.Title = ts.TotalMilliseconds.ToString();
}1.我测试表明,用for循环要比foreach遍历要快3倍,我只知道结果是这样,但对于造成这样的结果的准确原因还不知道,请高人解答,谢谢!
2.对于循环条件i != 999999,用 !=(不等号)而不是普通大家用的 <(小于号),这个问题我解决不清楚,因为C++ Primer推荐用的是!=,所以我一直用!=,我记得那书说过原因,但我忘了.
就是砖家说的,那也是C++的砖家,不是C#的。就是C++我也用小于。如果出于某种特殊需要在循环体内改变i的值,有可能变成死循环。
---------------------
这个是事实,但C++ PRIMER推荐这么写,我就这么写了,而且用久了,习惯了,不愿意去改,嘿嘿.
测试了几次,每次结果相差多少?
运行时的环境是怎样的?如果能保证两个运行环境一致?
是一次运行的还是分别运行的?
如果是一次运行的,有没有两个顺序颠倒再试?
有没有改变循环的次数?
.NET版本?
操作体统?
硬件配置?
结果时间?
拿具体数据来说话
具体数字循环,我原来自己小测过一次,差不多的,foreach稍快
这种循环条件第一次见,太强大了!
----------------
VMM,你说的是用 != 而不用 < 是吗, 用 != 是C++ PRIMER推荐的写法,我也不知道好在哪:)
=====================================
假如...i循环到999998时突然变成了1000000...你猜会发生什么事?不要问我为什么会变也不要大叫不可能...in computer world...impossible is nothing...永远不要猜测计算机的行为...
我们总是说可以忽略,那就是说有差别,只是差别大小而已.
当然,在小范围内,那么一点点差别真的是不必要记较的,但在一个项目里,上千上万几十万行的代码,一点点的差别堆积起来就不再是"一点点"了.
=====================================
如果项目中需要考虑这种差别...请相信我...我会用C++...优化的重点是拾西瓜而不是去捡芝麻...不要成为穿着长袍而站着喝酒的唯一的人...
问题是你只测试了arraylist而已,
就弄"循环与遍历,哪个效率更高"这么大个题目
不同的东西的遍历当然是不一样的!
这叫,以偏概全、管中窥豹~
当然,凡事有两面性,有好也有坏,就象是牺牲效率节省内存和牺牲内存提高效率一样.
他们的理由可能是C++中!=效率高,但是C#可不是C++!.NET更注重的是代码安全性而不是运行效率!
--------------------------
难道为了安全而效率完全不要吗?!
foreach (object obj in arrLst) //在这里,每次都是创建一个对象 obj,耗时相对比较大//而在循环中
object o = arrLst[j];
//在循环中,看起来是在这个对象集合一个引用而已.
//所以在这样的情况下,循环for取值要比foreach要效率来得高.
//我说的都是表面看起来的东西,可能不准我说的都是表面看起来的东西,可能不准
-------------------------------
我想你是对的!
private static long H()
{
string str = "The quick brown fox jumps over a lazy dog";
List<string> arrLst = new List<string>();
for (int i = 0; i < 9; i++)
{
arrLst.Add(str);
}
DateTime timeStart = DateTime.Now;
int arrLstCnt = arrLst.Count;
for (int i = 0; i < 999999; i++)
{
for (int j = 0; j < arrLstCnt; j++)
{
string o = arrLst[j];
}
}
DateTime timeEnd = DateTime.Now;
TimeSpan ts = timeEnd - timeStart;
return ts.Ticks;
} private static long I()
{
string str = "The quick brown fox jumps over a lazy dog";
List<string> arrLst = new List<string>();
for (int i = 0; i < 9; i++)
{
arrLst.Add(str);
}
DateTime timeStart = DateTime.Now;
for (int i = 0; i < 999999; i++)
{
foreach (string s in arrLst)
{
string o = s;
}
}
DateTime timeEnd = DateTime.Now;
TimeSpan ts = timeEnd - timeStart;
return ts.Ticks;
}
----------------------
不好意思,我不太明白,你给了的代码与我的,差别只是在你用泛型的list,我用的是arraylist,这里面差别很大吗?
那么就拿list<string>与arraylist做个比较,不同在哪,好处在哪?
谢谢!
{
string str = "The quick brown fox jumps over a lazy dog";
ArrayList arrLst = new ArrayList();
for (int i = 0; i != 9; ++i) // 这里和下面的不等于就不说了
{
arrLst.Add(str);
}
DateTime timeStart = DateTime.Now;
int arrLstCnt = arrLst.Count;
for (int i = 0; i != 999999; ++i)
{
for (int j = 0; j != arrLstCnt; ++j)
{
object o = arrLst[j]; //注意这里,取出arrLst的值
}
}
DateTime timeEnd = DateTime.Now;
TimeSpan ts = timeEnd - timeStart;
this.Title = ts.TotalMilliseconds.ToString();
} private void I()
{
string str = "The quick brown fox jumps over a lazy dog";
ArrayList arrLst = new ArrayList();
for (int i = 0; i != 9; ++i)
{
arrLst.Add(str);
}
DateTime timeStart = DateTime.Now;
for (int i = 0; i != 999999; ++i)
{
foreach (object obj in arrLst) //这里已经去出了string
{
object o = obj; //这里又取一次
}
}
DateTime timeEnd = DateTime.Now;
TimeSpan ts = timeEnd - timeStart;
this.Title = ts.TotalMilliseconds.ToString();
}
一个取值两次,一个一次,你说谁快
{
object o = obj; //这里又取一次
}
----------------------foreach总是这么用的呀,取出来,在需要使用地主用上,而for却不必,刚才我在 25楼说了.
主要是感觉你的测试代码有问题。for 和 foreach 是用的区别恰在于此,
foreach 相当于
for(...)
获取数据
{
string str = "The quick brown fox jumps over a lazy dog";
ArrayList arrLst = new ArrayList();
for (int i = 0; i != 9; ++i)
{
arrLst.Add(str);
}
DateTime timeStart = DateTime.Now;
int arrLstCnt = arrLst.Count;
for (int i = 0; i != 999999; ++i)
{
for (int j = 0; j != arrLstCnt; ++j)
{
object o = arrLst[j];
}
}
DateTime timeEnd = DateTime.Now;
TimeSpan ts = timeEnd - timeStart;
this.Title = ts.TotalMilliseconds.ToString();
} private void I()
{
string str = "The quick brown fox jumps over a lazy dog";
ArrayList arrLst = new ArrayList();
for (int i = 0; i != 9; ++i)
{
arrLst.Add(str);
}
DateTime timeStart = DateTime.Now;
for (int i = 0; i != 999999; ++i)
{
foreach (object obj in arrLst)
{
}
}
DateTime timeEnd = DateTime.Now;
TimeSpan ts = timeEnd - timeStart;
this.Title = ts.TotalMilliseconds.ToString();
}
在汇编里小于跳转和不等于跳转分别是glt,gn,(没记错的话)这两个最底层命令的效率差别能有多少?
调用的是System.Collections.ArrayList::get_Item(int32)Foreach迭代调用了:
System.Collections.ArrayList::GetEnumerator()
System.Collections.IEnumerator::get_Current()
System.Collections.IEnumerator::MoveNext()
System.IDisposable::Dispose()
而且代码中包含一对try{}finally{}块另外:foreach块中的代码可能不会执行,因为编译器并不编译里面的代码,
原因如下:object o始终未被引用,编译器会将它忽视
这一点可以通过查看汇编代码来证明
2、List<T>的迭代器是值类型(foreach不会去装箱),而ArrayList的迭代器是引用类型,所以ArrayList的Foreach需要额外分配999999次对象,而List<T>不需要
3、如果换成Hashtable或Dictionary<TKey,TValue>,再比较一下看看
集合应该是foreach快.
那么取 0 索引的按寻址运算,姑且认为是指针在移
索引 0
从null移到0
取1索引
指针要移二次
null ->0 ->1
取 3索引
null->0 ->1 -> 2
那么我们用for,这僦是个阶加的运算
1+ 2 +3 +..+ n
如果用foreach
那么
移动次数就是n
还不如去学c++
妈的,至少看起来是那回事
原因是 foreach 需要额外的方法 MoveNext() 和属性调用Current、以及类型转换。
下列形式的 foreach 语句:
foreach (ElementType element in collection) statement
对应于两个可能的扩展中的一个:
1:如果 collection 表达式的类型实现了集合模式(如上面定义的那样),则 foreach 语句的扩展是:E enumerator = (collection).GetEnumerator();
try {
ElementType element;
while (enumerator.MoveNext()) {
element = (ElementType)enumerator.Current;
statement;
}
}
finally {
IDisposable disposable = enumerator as System.IDisposable;
if (disposable != null) disposable.Dispose();
}通常可以很容易对上述代码进行有效的优化。如果类型 E 实现了 System.IDisposable,则表达式 (enumerator as System.IDisposable) 将始终为非 null,因而,该实现可以安全地用一个简单转换替代可能更昂贵的类型测试。相反,如果类型 E 是 sealed 而且没有实现 System.IDisposable,则表达式 (enumerator as System.IDisposable) 的计算结果始终为 null。这种情况下,在上述代码中可以安全地删除整个 finally 子句。
2:否则,collection 表达式的类型就实现了 System.IEnumerable,这样,foreach 语句的扩展就成为:IEnumerator enumerator =
((System.Collections.IEnumerable)(collection)).GetEnumerator();
try {
ElementType element;
while (enumerator.MoveNext()) {
element = (ElementType)enumerator.Current;
statement;
}
}
finally {
IDisposable disposable = enumerator as System.IDisposable;
if (disposable != null) disposable.Dispose();
}在上面两个扩展的任意一个中,enumerator 变量是一个临时变量,它在嵌入 statement 中既是不可访问的,也是不可见的,element 变量在嵌入 statement 中是只读的。不管如何优化,GetEnumerator()、MoveNext()、Current 调用是无法缺少的,而对于 MoveNext()、Current 与 for 中的计数器,索引访问是一样的。下面以对 String 对象的 for 和 foreach 为例,都实现将字符串中的所有字符访问一次的功能:首先看 for 语句:int len = str.Length;
for (int j = 0; j != len; ++j)
{
char s = str[j];
}就是这么简洁,没有额外的操作。再看 foreach 语句:foreach (char c in str)
{
char s = c;
}从代码上来看,foreach 更简洁、优美,
但是,foreach 展开之后,类似于:IEnumerator etor = str.GetEnumerator();
while (etor.MoveNext())
{
char c = (char)etor.Current; // 这里还有装箱和拆箱的性能损耗
}展开为类似于 for 的形式:int length = str.Length;for (int i = 0; i < length; i++)
{
etor.MoveNext();
char c = (char)etor.Current; // char c = string[i];
}不难看出比上面的 for 多做了些什么,当然编译器不是这么展开的,但运行次数是差不多的。string 的 GetEnumerator 方法将创建一个 CharEnumerator 类型的对象;
CharEnumerator 的定义为:[Serializable, ComVisible(true)]
public sealed class CharEnumerator : ICloneable, IEnumerator<char>, IDisposable, IEnumerator
{
// Fields
private char currentElement;
private int index;
private string str; // Methods
internal CharEnumerator(string str)
{
this.str = str;
this.index = -1;
} public object Clone()
{
return base.MemberwiseClone();
} public bool MoveNext() // 看看 MoveNext, 代码还不少呢
{
if (this.index < (this.str.Length - 1))
{
this.index++;
this.currentElement = this.str[this.index];
return true;
}
this.index = this.str.Length;
return false;
} public void Reset()
{
this.currentElement = '\0';
this.index = -1;
} void IDisposable.Dispose()
{
} // Properties
public char Current
{
get
{
if (this.index == -1)
{
throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumNotStarted"));
}
if (this.index >= this.str.Length)
{
throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumEnded"));
}
return this.currentElement;
}
} object IEnumerator.Current // 这里要装箱,赋值(char c = (char)etor.Current)的时候还要拆箱
{
get
{
if (this.index == -1)
{
throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumNotStarted"));
}
if (this.index >= this.str.Length)
{
throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumEnded"));
}
return this.currentElement;
}
}
}这就是 foreach 一定慢的原因。
using System.Collections;
using System.Collections.Generic;
using System.Text;namespace TempTestA1
{
public class Class2
{ static long F()
{
string str = "The quick brown fox jumps over a lazy dog";
DateTime timeStart = DateTime.Now;
for (int i = 0; i != 999999; ++i)
{
for (int j = 0; j != str.Length; ++j)
{
char s = str[j];
}
}
DateTime timeEnd = DateTime.Now;
TimeSpan ts = timeEnd - timeStart;
return ts.Ticks;
} static long G()
{
string str = "The quick brown fox jumps over a lazy dog";
DateTime timeStart = DateTime.Now;
for (int i = 0; i != 999999; ++i)
{
foreach (char c in str)
{
char s = c;
}
}
DateTime timeEnd = DateTime.Now;
TimeSpan ts = timeEnd - timeStart;
return ts.Ticks;
} static void Main(string[] args)
{
int b = 0; b++; // 设置断点 int fcount = 0/* f 快的次数 */, gcount = 0/* g 快的次数 */, eq = 0/* 一样快的次数 */; for (int i = 0; i < 20; i++) // 第 1 段
{
long f = F(); long g = G(); if (f < g) fcount++; else if (f != g) gcount++; else eq++; } for (int i = 0; i < 20; i++) // 第 2 段
{
long g = G(); long f = F(); if (f < g) fcount++; else if (f != g) gcount++; else eq++; } b++; // 设置断点 }
}
}// 测试结果:
/*times 1, 使用第 1 段(注释第 2 段):
fcount 18 int
gcount 0 int
eq 2 inttimes 2, 使用第 1 段(注释第 2 段):
fcount 20 int
gcount 0 int
eq 0 inttimes 3, 使用第 1 段(注释第 2 段):
fcount 20 int
gcount 0 int
eq 0 inttimes 4 使用第 1 段(注释第 2 段):
fcount 20 int
gcount 0 int
eq 0 inttimes 5, 使用第 1 段(注释第 2 段):
fcount 19 int
gcount 0 int
eq 1 inttimes 6, 使用第 2 段(注释第 1 段):
fcount 20 int
gcount 0 int
eq 0 inttimes 7 使用第 2 段(注释第 1 段):
fcount 19 int
gcount 0 int
eq 1 inttimes 8, 使用第 2 段(注释第 1 段):
fcount 20 int
gcount 0 int
eq 0 inttimes 9, 使用第 2 段(注释第 1 段):
fcount 20 int
gcount 0 int
eq 0 inttimes 10, 使用第 2 段(注释第 1 段):
fcount 20 int
gcount 0 int
eq 0 inttimes 11, 第 1 和 2 段一起(都不注释):
fcount 40 int
gcount 0 int
eq 0 inttimes 12, 第 1 和 2 段一起(都不注释):
fcount 40 int
gcount 0 int
eq 0 inttimes 13, 第 1 和 2 段一起(都不注释):
fcount 40 int
gcount 0 int
eq 0 inttimes 14, 第 1 和 2 段一起(都不注释):
fcount 40 int
gcount 0 int
eq 0 inttimes 14, 第 1 和 2 段一起(都不注释):
fcount 40 int
gcount 0 int
eq 0 int*/
using System.Collections;
using System.Collections.Generic;
using System.Text;namespace TempTestA1
{
public class Class3
{ static long F()
{
string str = "The quick brown fox jumps over a lazy dog";
DateTime timeStart = DateTime.Now;
for (int i = 0; i != 999999; ++i)
{
for (int j = 0; j != str.Length; ++j)
{
char s = str[j];
}
}
DateTime timeEnd = DateTime.Now;
TimeSpan ts = timeEnd - timeStart;
return ts.Ticks;
} static long G()
{
string str = "The quick brown fox jumps over a lazy dog";
DateTime timeStart = DateTime.Now;
for (int i = 0; i != 999999; ++i)
{
foreach (char c in str)
{
char s = c;
}
}
DateTime timeEnd = DateTime.Now;
TimeSpan ts = timeEnd - timeStart;
return ts.Ticks;
} static void Main(string[] args)
{
int b = 0; b++; // 设置断点 long fcount = 0/* f 用时 */, gcount = 0/* g 用时 */; for (int i = 0; i < 20; i++) // 第 1 段
{
fcount += F(); gcount += G(); } for (int i = 0; i < 20; i++) // 第 2 段
{
gcount += G(); gcount += F(); } b++; // 设置断点 }
}
}// 测试结果
/*
times 1, 第 1 和 2 段一起(都不注释):
fcount 15156250 long
gcount 57812500 longtimes 2, 第 1 和 2 段一起(都不注释):
fcount 15625000 long
gcount 56406250 longtimes 3, 第 1 和 2 段一起(都不注释):
fcount 14843750 long
gcount 57812500 longtimes 4, 第 1 和 2 段一起(都不注释):
fcount 15468750 long
gcount 57031250 longtimes 5, 第 1 和 2 段一起(都不注释):
fcount 15468750 long
gcount 56562500 long*/
如下所示:
int a = 0, i = 0;
00415BA7 mov dword ptr [a],0
00415BAE mov dword ptr [i],0
if(a<1)i++;
00415BB5 cmp dword ptr [a],1 // 比较
00415BB9 jge main+64h (415BC4h) // 大于或等于则跳转
00415BBB mov eax,dword ptr [i]
00415BBE add eax,1
00415BC1 mov dword ptr [i],eax
if(a!=1)i++;
00415BC4 cmp dword ptr [a],1 // 比较
00415BC8 je main+73h (415BD3h) // 等于则跳转
00415BCA mov eax,dword ptr [i]
00415BCD add eax,1
00415BD0 mov dword ptr [i],eax i++;
00415BD3 mov eax,dword ptr [i]
00415BD6 add eax,1
00415BD9 mov dword ptr [i],eax
循环遍历、递归遍历、随机遍历(遍历无约定次序,然后每次把遍历过的排出去。呵呵。)(不过与主题思想没什么关系,纯属灌水)
,而集合的特定循环foreach封装了wihle或for等语句,所以其实是一回事,
但效率上由于多封装了一层所以会有一定成本,但这种成本是值得的,不然MS也
不会多事,搞个foreach出来.
复杂度是n的情况下,由加一个常数引起的边界成本可以忽略。
还记得复杂度理论里说的吗?O(n)+k=O(n)
当嵌套循环的时候
(O(n)+k)*(O(m)+l)边界成本会升高,但是理论上还是O(m*n)理论上证明,foreach增加的边界成本不会从根本上影响复杂度,但会略微增加运行时间。
如果测试结果差距3倍,也不能说明foreach过于效率低,
因为这个三倍是加法引起的,不是出现积数,对数,指数的情况。
所以会比for要慢