469,270 Members | 1,118 Online
Bytes | Developer Community
New Post

Home Posts Topics Members FAQ

Post your question to a community of 469,270 developers. It's quick & easy.

foreach or List.ForEach

Which is the better way to go and why?

//trivial example
List<string> strings = GetStrings();
foreach (string s in strings)
{
// some operation;
}

strings.ForEach(
delegate(string s)
{
// some operation
}
);
Jan 19 '06 #1
27 79383
Tripper,

I wouldn't say that there is a better way, really, since they pretty
much do the same thing.

The call to ForEach might be a little less performant, because you are
making a call through a delegate (which ultimately is slower than the code
in the braces).

However, I wouldn't say that this is a deal-killer, since LINQ is going
to be heavily based on anonymous delegates doing this kind of thing.

Personally, for something as simple as a foreach, I would just use
foreach, since it is that simple.

Hope this helps.
--
- Nicholas Paldino [.NET/C# MVP]
- mv*@spam.guard.caspershouse.com

"Tripper" <Tr*****@discussions.microsoft.com> wrote in message
news:8E**********************************@microsof t.com...
Which is the better way to go and why?

//trivial example
List<string> strings = GetStrings();
foreach (string s in strings)
{
// some operation;
}

strings.ForEach(
delegate(string s)
{
// some operation
}
);

Jan 19 '06 #2
Thanks Nicholas! I wonder then, when is a good time to use List.ForEach over
foreach? Maybe like this?

strings.ForEach(ProcessString);

vs.

foreach (s in strings)
{
ProcessString(s);
}

Thanks!

Tripp

"Nicholas Paldino [.NET/C# MVP]" wrote:
Tripper,

I wouldn't say that there is a better way, really, since they pretty
much do the same thing.

The call to ForEach might be a little less performant, because you are
making a call through a delegate (which ultimately is slower than the code
in the braces).

However, I wouldn't say that this is a deal-killer, since LINQ is going
to be heavily based on anonymous delegates doing this kind of thing.

Personally, for something as simple as a foreach, I would just use
foreach, since it is that simple.

Hope this helps.
--
- Nicholas Paldino [.NET/C# MVP]
- mv*@spam.guard.caspershouse.com

"Tripper" <Tr*****@discussions.microsoft.com> wrote in message
news:8E**********************************@microsof t.com...
Which is the better way to go and why?

//trivial example
List<string> strings = GetStrings();
foreach (string s in strings)
{
// some operation;
}

strings.ForEach(
delegate(string s)
{
// some operation
}
);


Jan 19 '06 #3
Tripp,

I would say that is a good time, if you want to reduce your code
profile. Then again, the effects are so minimal, I would say it is
inconsequential.
--
- Nicholas Paldino [.NET/C# MVP]
- mv*@spam.guard.caspershouse.com

"Tripper" <Tr*****@discussions.microsoft.com> wrote in message
news:05**********************************@microsof t.com...
Thanks Nicholas! I wonder then, when is a good time to use List.ForEach
over
foreach? Maybe like this?

strings.ForEach(ProcessString);

vs.

foreach (s in strings)
{
ProcessString(s);
}

Thanks!

Tripp

"Nicholas Paldino [.NET/C# MVP]" wrote:
Tripper,

I wouldn't say that there is a better way, really, since they pretty
much do the same thing.

The call to ForEach might be a little less performant, because you
are
making a call through a delegate (which ultimately is slower than the
code
in the braces).

However, I wouldn't say that this is a deal-killer, since LINQ is
going
to be heavily based on anonymous delegates doing this kind of thing.

Personally, for something as simple as a foreach, I would just use
foreach, since it is that simple.

Hope this helps.
--
- Nicholas Paldino [.NET/C# MVP]
- mv*@spam.guard.caspershouse.com

"Tripper" <Tr*****@discussions.microsoft.com> wrote in message
news:8E**********************************@microsof t.com...
> Which is the better way to go and why?
>
> //trivial example
> List<string> strings = GetStrings();
> foreach (string s in strings)
> {
> // some operation;
> }
>
> strings.ForEach(
> delegate(string s)
> {
> // some operation
> }
> );


Jan 19 '06 #4
Hi,

"Tripper" <Tr*****@discussions.microsoft.com> wrote in message
news:05**********************************@microsof t.com...
Thanks Nicholas! I wonder then, when is a good time to use List.ForEach
over
foreach? Maybe like this?

strings.ForEach(ProcessString);

vs.

foreach (s in strings)
{
ProcessString(s);
}

Thanks!


In my opinion it should perform almost similar, I bet that the
implementation of ForEach is VERY similar to the second code snip, so you
would only be saving in a little typing

Frankly I prefer the second, let's say it's more evident for anybody coming
from any language.

--
Ignacio Machin,
ignacio.machin AT dot.state.fl.us
Florida Department Of Transportation
Jan 19 '06 #5
Tripper wrote:
Which is the better way to go and why?

//trivial example
List<string> strings = GetStrings();
foreach (string s in strings)
{
// some operation;
}
Reflector shows that foreach on a List<string> is not optimized: It
does the full GetEnumerator/while MoveNext.
strings.ForEach(
delegate(string s)
{
// some operation
}


List<T>.ForEach, otoh, does a simple for loop, internal to the List<T>
class.

So, given the example you show, strings.ForEach might actually perform
a bit better: More overhead to call the delegate than to execute an
inline foreach body, but less loop control overhead.

If you were comparing

foreach (string s in strings)
SomeMethod(S);

to

strings.ForEach(SomeMethod);

I'd expect strings.ForEach to come out even farther ahead, although
any differences should be really modest.

Fwiw, I benchmarked "foreach (string s in strings) SomeMethod(S)" vs
"strings.ForEach(SomeMethod);" with a 7 string list.

10x Inline: 43.58 microseconds.
10x ForEach: 18.72 microseconds.

--
<http://www.midnightbeach.com>
Jan 19 '06 #6
Tripper <Tr*****@discussions.microsoft.com> wrote:
Which is the better way to go and why?

//trivial example
List<string> strings = GetStrings();
foreach (string s in strings)
{
// some operation;
}

strings.ForEach(
delegate(string s)
{
// some operation
}
);


I was surprised by the results I found when benchmarking, so thought
people might be interested:

using System;
using System.Diagnostics;
using System.Collections.Generic;

class Test
{
const int Iterations = 10000;
const int Size = 100000;

static void Main()
{
Stopwatch stopwatch = new Stopwatch();
List<string> list = new List<string>();
for (int i=0; i < Size; i++)
{
list.Add("x");
}

{
int sum = 0;

stopwatch.Reset();
stopwatch.Start();
for (int i=0; i < Iterations; i++)
{
foreach (string s in list)
{
sum += s.Length;
}
}
TimeSpan t1 = stopwatch.Elapsed;

Console.WriteLine ("First version: {0} (correct? {1})",
t1, sum==Iterations*Size);
}

{
int sum = 0;
stopwatch.Reset();
stopwatch.Start();
for (int i=0; i < Iterations; i++)
{
list.ForEach (delegate(string s) { sum += s.Length; });
}
TimeSpan t2 = stopwatch.Elapsed;

Console.WriteLine ("Second version: {0} (correct? {1})",
t2, sum==Iterations*Size);
}

{
int sum = 0;

Action<string> action = delegate(string s)
{
sum += s.Length;
};

stopwatch.Reset();
stopwatch.Start();
for (int i=0; i < Iterations; i++)
{
list.ForEach (action);
}
TimeSpan t3 = stopwatch.Elapsed;

Console.WriteLine ("Third version: {0} (correct? {1})",
t3, sum==Iterations*Size);
}
}
}

Results:

First version: 00:00:11.5096608 (correct? True)
Second version: 00:00:05.5193006 (correct? True)
Third version: 00:00:05.5257766 (correct? True)

Note that the "sum" variable is different in each case. I don't know
what the performance impact of having a single variable would be, but I
believe there *would* be a difference (primarily or possibly solely to
the first version) due to it being captured by the other delegates.

I'd expected foreach to claim a significant victory...

--
Jon Skeet - <sk***@pobox.com>
http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet
If replying to the group, please do not mail me too
Jan 19 '06 #7
Jon,

will you blog on this so I can link it from mine?

--
Patrik L÷wendahl [C# MVP]
http://www.lowendahl.net

Tripper <Tr*****@discussions.microsoft.com> wrote:
Which is the better way to go and why?

//trivial example
List<string> strings = GetStrings();
foreach (string s in strings)
{
// some operation;
}
strings.ForEach(
delegate(string s)
{
// some operation
}
);

I was surprised by the results I found when benchmarking, so thought
people might be interested:

using System;
using System.Diagnostics;
using System.Collections.Generic;
class Test
{
const int Iterations = 10000;
const int Size = 100000;
static void Main()
{
Stopwatch stopwatch = new Stopwatch();
List<string> list = new List<string>();
for (int i=0; i < Size; i++)
{
list.Add("x");
}
{
int sum = 0;
stopwatch.Reset();
stopwatch.Start();
for (int i=0; i < Iterations; i++)
{
foreach (string s in list)
{
sum += s.Length;
}
}
TimeSpan t1 = stopwatch.Elapsed;
Console.WriteLine ("First version: {0} (correct? {1})",
t1, sum==Iterations*Size);
}
{
int sum = 0;
stopwatch.Reset();
stopwatch.Start();
for (int i=0; i < Iterations; i++)
{
list.ForEach (delegate(string s) { sum += s.Length;
});
}
TimeSpan t2 = stopwatch.Elapsed;
Console.WriteLine ("Second version: {0} (correct? {1})",
t2, sum==Iterations*Size);
}
{
int sum = 0;
Action<string> action = delegate(string s)
{
sum += s.Length;
};
stopwatch.Reset();
stopwatch.Start();
for (int i=0; i < Iterations; i++)
{
list.ForEach (action);
}
TimeSpan t3 = stopwatch.Elapsed;
Console.WriteLine ("Third version: {0} (correct? {1})",
t3, sum==Iterations*Size);
}
}
}
Results:

First version: 00:00:11.5096608 (correct? True)
Second version: 00:00:05.5193006 (correct? True)
Third version: 00:00:05.5257766 (correct? True)
Note that the "sum" variable is different in each case. I don't know
what the performance impact of having a single variable would be, but
I believe there *would* be a difference (primarily or possibly solely
to the first version) due to it being captured by the other delegates.

I'd expected foreach to claim a significant victory...

Jan 19 '06 #8
Patrik L÷wendahl [C# MVP] <pa**************@home.se> wrote:
will you blog on this so I can link it from mine?


Yeah, okay - you talked me into it :) It might not be tonight though.
As an added bonus I'll have the "captured variable" version and another
one where the delegate is a method call.

Tonight's job is continuing with my C# 2.0 pages which I hope to finish
by the end of January. It's not that there'll be reams of stuff on
there, but I don't get a lot of free time these days...

--
Jon Skeet - <sk***@pobox.com>
http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet
If replying to the group, please do not mail me too
Jan 19 '06 #9

"Jon Skeet [C# MVP]" <sk***@pobox.com> wrote in message
news:MP************************@msnews.microsoft.c om...
| Tripper <Tr*****@discussions.microsoft.com> wrote:
| > Which is the better way to go and why?
| >
| > //trivial example
| > List<string> strings = GetStrings();
| > foreach (string s in strings)
| > {
| > // some operation;
| > }
| >
| > strings.ForEach(
| > delegate(string s)
| > {
| > // some operation
| > }
| > );
|
| I was surprised by the results I found when benchmarking, so thought
| people might be interested:
|
| using System;
| using System.Diagnostics;
| using System.Collections.Generic;
|
| class Test
| {
| const int Iterations = 10000;
| const int Size = 100000;
|
| static void Main()
| {
| Stopwatch stopwatch = new Stopwatch();
| List<string> list = new List<string>();
| for (int i=0; i < Size; i++)
| {
| list.Add("x");
| }
|
| {
| int sum = 0;
|
| stopwatch.Reset();
| stopwatch.Start();
| for (int i=0; i < Iterations; i++)
| {
| foreach (string s in list)
| {
| sum += s.Length;
| }
| }
| TimeSpan t1 = stopwatch.Elapsed;
|
| Console.WriteLine ("First version: {0} (correct? {1})",
| t1, sum==Iterations*Size);
| }
|
| {
| int sum = 0;
| stopwatch.Reset();
| stopwatch.Start();
| for (int i=0; i < Iterations; i++)
| {
| list.ForEach (delegate(string s) { sum += s.Length; });
| }
| TimeSpan t2 = stopwatch.Elapsed;
|
| Console.WriteLine ("Second version: {0} (correct? {1})",
| t2, sum==Iterations*Size);
| }
|
| {
| int sum = 0;
|
| Action<string> action = delegate(string s)
| {
| sum += s.Length;
| };
|
| stopwatch.Reset();
| stopwatch.Start();
| for (int i=0; i < Iterations; i++)
| {
| list.ForEach (action);
| }
| TimeSpan t3 = stopwatch.Elapsed;
|
| Console.WriteLine ("Third version: {0} (correct? {1})",
| t3, sum==Iterations*Size);
| }
| }
| }
|
| Results:
|
| First version: 00:00:11.5096608 (correct? True)
| Second version: 00:00:05.5193006 (correct? True)
| Third version: 00:00:05.5257766 (correct? True)
|
| Note that the "sum" variable is different in each case. I don't know
| what the performance impact of having a single variable would be, but I
| believe there *would* be a difference (primarily or possibly solely to
| the first version) due to it being captured by the other delegates.
|
| I'd expected foreach to claim a significant victory...
|
|

Just add two lines, and watch the result...
...
stopwatch.Start();
// Put the list into an array for performance
string[] sa = new string[list.Count]; <----
list.CopyTo(sa, 0); <----
for (int i=0; i != Iterations; i++)
{
foreach (string s in sa)
{
sum += s.Length;
}
}
...

Also, try to run the first two tests only, and watch the results....
Willy.
Jan 20 '06 #10
Willy Denoyette [MVP] wrote:
Just add two lines, and watch the result...
..
stopwatch.Start();
// Put the list into an array for performance
string[] sa = new string[list.Count]; <----
list.CopyTo(sa, 0); <----
Yes, but I think that counts as cheating :)
Also, try to run the first two tests only, and watch the results....


I don't see any difference there. What difference are you seeing?

Jon

Jan 20 '06 #11
Patrik wrote:
will you blog on this so I can link it from mine?


http://msmvps.com/blogs/jon.skeet/ar...reachperf.aspx

Jon

Jan 20 '06 #12

"Jon Skeet [C# MVP]" <sk***@pobox.com> wrote in message
news:11*********************@g43g2000cwa.googlegro ups.com...
| Willy Denoyette [MVP] wrote:
|
| > Just add two lines, and watch the result...
| > ..
| > stopwatch.Start();
| > // Put the list into an array for performance
| > string[] sa = new string[list.Count]; <----
| > list.CopyTo(sa, 0); <----
|
| Yes, but I think that counts as cheating :)
|

Yes, a little bit ;-), but isn't that (don't keep allocating enumerators)
what ForEach is doing also?

| > Also, try to run the first two tests only, and watch the results....
|
| I don't see any difference there. What difference are you seeing?

First version: 00:00:10.5909059 (correct? True)
Second version: 00:00:04.4966891 (correct? True)
Third version: 00:00:04.4944295 (correct? True)

First version: 00:00:11.4495034 (correct? True)
Second version: 00:00:08.9772915 (correct? True)

Willy.
Jan 20 '06 #13

"Tripper" <Tr*****@discussions.microsoft.com> wrote in message
news:8E**********************************@microsof t.com...
Which is the better way to go and why?

//trivial example
List<string> strings = GetStrings();
foreach (string s in strings)
{
// some operation;
}

strings.ForEach(
delegate(string s)
{
// some operation
}
);


foreach is the most readable which is way more important than any trivial
speed difference.

If you're into speed you should try:

int len - strings.Length;
for(int i = 0; i < len;++i)
sim += strings[i].Length;

I have read stuff saying that this should be fastest because it is
realtively easy to JIT compile it into entirely in-line code.


Jan 20 '06 #14
For a string (ar any other simple) array, yes you may be right...

However, if this was a linked-list implementation, either of the foreach /
ForEach methods is likely to outperform it due to not having to start at the
beginning each time (assuming it doesn't internally keep a cursor for
reference via the indexers).

Another issue with both the enumerator syntax and the List<T>.ForEach syntax
is that they force the caller down a particular route; this may be fine if
the varialbe is private to method, but if the variable is a *parameter* it
seems (to me at least) to use IEnumerable<T> to a: satisfy the "least
required" paradigm, and b: give the caller the most flexibility (i.e. they
can give me a list, an array, a collection, a linked-list, or whatever). And
yes, I could write an overload with the improved performance, but then I
increase my maintenance overhead.

If desired usage dictates, I might just about stretch to providing a "params
T[]" overload (which in turn calls the IEnumerable<T> version, maintaining a
single functional code-base), but I don't really want to write (and
maintain) 3 versions of the code (one for T[] using indexers, one for
List<T> using ForEach and one for IEnumerable<T> using foreach).

For me, then, foreach is still my *default* way to go (although I will make
exceptions under consideration); although not always the *absolute* best, it
is suitably performant (i.e. in the same league) without imposing any
restrictions, plus it avoids careless errors due to incorrect use (i.e.
using an indexer with a linked list).

Marc

Jan 20 '06 #15
And yes - I know you weren't advicating usage on a linked list;

My point is that there is a category of developer who could read this ad
think "ah, indexer usage is best" and then use it constantly. Regardless of
whether they actually have an array or just something with indexer access.
And it isn't as simple as that.

I wasn't disagreeing in principle, though.

Marc
Jan 20 '06 #16
Willy Denoyette [MVP] wrote:
| Yes, but I think that counts as cheating :)

Yes, a little bit ;-), but isn't that (don't keep allocating enumerators)
what ForEach is doing also?
Well, there's a big difference between "copy this potentially huge
array" and "allocate a single enumerator".
| > Also, try to run the first two tests only, and watch the results....
|
| I don't see any difference there. What difference are you seeing?

First version: 00:00:10.5909059 (correct? True)
Second version: 00:00:04.4966891 (correct? True)
Third version: 00:00:04.4944295 (correct? True)

First version: 00:00:11.4495034 (correct? True)
Second version: 00:00:08.9772915 (correct? True)


Odd. How are you compiling and running? Do you get the same difference
if you use the new benchmark as linked to from my blog entry?
(http://msmvps.com/blogs/jon.skeet/ar...reachperf.aspx)

Jon

Jan 20 '06 #17

"Jon Skeet [C# MVP]" <sk***@pobox.com> wrote in message
news:11*********************@g44g2000cwa.googlegro ups.com...
| Willy Denoyette [MVP] wrote:
| > | Yes, but I think that counts as cheating :)
| >
| > Yes, a little bit ;-), but isn't that (don't keep allocating
enumerators)
| > what ForEach is doing also?
|
| Well, there's a big difference between "copy this potentially huge
| array" and "allocate a single enumerator".
|

Not sure what you mean by that, ForEach doesn't allocate enumerators, while
foreach on the list allocates an enumerator and calls Dispose once per
iteration. 'foreach' on an array (as I did) doesn't allocate an enumerator,
it simply uses a pointer calls get_Length to get the length of the string
and advances the pointer.

| > | > Also, try to run the first two tests only, and watch the results....
| > |
| > | I don't see any difference there. What difference are you seeing?
| >
| > First version: 00:00:10.5909059 (correct? True)
| > Second version: 00:00:04.4966891 (correct? True)
| > Third version: 00:00:04.4944295 (correct? True)
| >
| > First version: 00:00:11.4495034 (correct? True)
| > Second version: 00:00:08.9772915 (correct? True)
|
| Odd. How are you compiling and running? Do you get the same difference
| if you use the new benchmark as linked to from my blog entry?
| (http://msmvps.com/blogs/jon.skeet/ar...reachperf.aspx)
|
| Jon
|

Compiling and running from the command line with /o and /o-. Using XP SP2
and 2.0.50727.42.

There is definitely something wrong with this (First version and Second
version), the problem doesn't occur when Third follows First. More trying to
profile the "second" version alone crashes the profilers (VS2005 as well as
special profiler using the HW CPU and memory controller perfcounters). I'm
actually looking into this to see what's happening.

The new version doesn't have any of these issues, that is, they show
consistent timing and no profiler crashes !?
Here are my results running on the same box as above results.

Compiled from the cmd line with /o
Test parameters: Size=100000; Iterations=10000
Test 00:00:11.0218923: LanguageForEach
Test 00:00:04.5410905: NewDelegateEachTime
Test 00:00:04.5138248: CachedDelegate
Test 00:00:13.6877670: LanguageForEachWithCopy1
Test 00:00:01.6782582: LanguageForEachWithCopy2

Same with /o-
Test parameters: Size=100000; Iterations=10000
Test 00:00:11.6655393: LanguageForEach
Test 00:00:04.5820940: NewDelegateEachTime
Test 00:00:04.5459386: CachedDelegate
Test 00:00:14.4161721: LanguageForEachWithCopy1
Test 00:00:02.2684162: LanguageForEachWithCopy2

First two tests only.
Test parameters: Size=100000; Iterations=10000
Test 00:00:11.0486619: LanguageForEach
Test 00:00:04.5389746: NewDelegateEachTime

Notice the huge difference for the last test when o is turned off! The fun
of micro-benchmarks.
Willy.
Jan 20 '06 #18
Willy Denoyette [MVP] wrote:
| Well, there's a big difference between "copy this potentially huge
| array" and "allocate a single enumerator".

Not sure what you mean by that, ForEach doesn't allocate enumerators, while
foreach on the list allocates an enumerator and calls Dispose once per
iteration.
Sorry, yes, I misread which kind of ForEach you meant. The difference
between copying it to an array and calling ForEach is that ForEach
doesn't need the extra allocation. In other words, although using array
copy / foreach is faster than using ForEach (if you copy it once and
then iterate multiple times, at least), it's clearly more expensive in
terms of memory.

<snip>
| Odd. How are you compiling and running? Do you get the same difference
| if you use the new benchmark as linked to from my blog entry?
| (http://msmvps.com/blogs/jon.skeet/ar...reachperf.aspx)

Compiling and running from the command line with /o and /o-. Using XP SP2
and 2.0.50727.42.
Interesting. I've been running with .NET 1.1. I'll try it later with
..NET 2.0 and see if that makes a difference.
There is definitely something wrong with this (First version and Second
version), the problem doesn't occur when Third follows First. More trying to
profile the "second" version alone crashes the profilers (VS2005 as well as
special profiler using the HW CPU and memory controller perfcounters). I'm
actually looking into this to see what's happening.
Goodo :)
The new version doesn't have any of these issues, that is, they show
consistent timing and no profiler crashes !?
Well, there's significantly less codependence in the new version, so
I'm not entirely surprised. It's a more realistic test in various ways.

<snip>
Notice the huge difference for the last test when o is turned off! The fun
of micro-benchmarks.


Absolutely.

Jon

Jan 20 '06 #19

"Jon Skeet [C# MVP]" <sk***@pobox.com> wrote in message
news:11**********************@z14g2000cwz.googlegr oups.com...
| Willy Denoyette [MVP] wrote:
| > | Well, there's a big difference between "copy this potentially huge
| > | array" and "allocate a single enumerator".
| >
| > Not sure what you mean by that, ForEach doesn't allocate enumerators,
while
| > foreach on the list allocates an enumerator and calls Dispose once per
| > iteration.
|
| Sorry, yes, I misread which kind of ForEach you meant. The difference
| between copying it to an array and calling ForEach is that ForEach
| doesn't need the extra allocation. In other words, although using array
| copy / foreach is faster than using ForEach (if you copy it once and
| then iterate multiple times, at least), it's clearly more expensive in
| terms of memory.
|

True, there is the memory cost. isn't making "informed and sound design
tradeoffs" the beauty of software design?

| <snip>
|
| > | Odd. How are you compiling and running? Do you get the same difference
| > | if you use the new benchmark as linked to from my blog entry?
| > |
(http://msmvps.com/blogs/jon.skeet/ar...reachperf.aspx)
| >
| > Compiling and running from the command line with /o and /o-. Using XP
SP2
| > and 2.0.50727.42.
|
| Interesting. I've been running with .NET 1.1. I'll try it later with
| .NET 2.0 and see if that makes a difference.
|

Hmmmm... generics and anon delegates on v1.1???
| > There is definitely something wrong with this (First version and Second
| > version), the problem doesn't occur when Third follows First. More
trying to
| > profile the "second" version alone crashes the profilers (VS2005 as well
as
| > special profiler using the HW CPU and memory controller perfcounters).
I'm
| > actually looking into this to see what's happening.
|
| Goodo :)
|
| > The new version doesn't have any of these issues, that is, they show
| > consistent timing and no profiler crashes !?
|
| Well, there's significantly less codependence in the new version, so
| I'm not entirely surprised. It's a more realistic test in various ways.
|

Yep, but there is more than the performance drop. The same test runs
correctly on Intel P4 and Mobile HW, but shows this pattern on 32/64 bit AMD
and Intel.

Willy.

Jan 20 '06 #20
Willy Denoyette [MVP] wrote:
| Sorry, yes, I misread which kind of ForEach you meant. The difference
| between copying it to an array and calling ForEach is that ForEach
| doesn't need the extra allocation. In other words, although using array
| copy / foreach is faster than using ForEach (if you copy it once and
| then iterate multiple times, at least), it's clearly more expensive in
| terms of memory.

True, there is the memory cost. isn't making "informed and sound design
tradeoffs" the beauty of software design?
:)
| Interesting. I've been running with .NET 1.1. I'll try it later with
| .NET 2.0 and see if that makes a difference.

Hmmmm... generics and anon delegates on v1.1???
Okay, I clearly need to get more sleep...
| Well, there's significantly less codependence in the new version, so
| I'm not entirely surprised. It's a more realistic test in various ways.

Yep, but there is more than the performance drop. The same test runs
correctly on Intel P4 and Mobile HW, but shows this pattern on 32/64 bit AMD
and Intel.


Ah, interesting. Are you investigating much further, or just shipping
it off to MS? It sounds like something they ought to know about, and
it's already a pretty short bit of code to reproduce the problem...

Jon

Jan 20 '06 #21

"Jon Skeet [C# MVP]" <sk***@pobox.com> wrote in message
news:11**********************@g47g2000cwa.googlegr oups.com...
| Willy Denoyette [MVP] wrote:
| > | Sorry, yes, I misread which kind of ForEach you meant. The difference
| > | between copying it to an array and calling ForEach is that ForEach
| > | doesn't need the extra allocation. In other words, although using
array
| > | copy / foreach is faster than using ForEach (if you copy it once and
| > | then iterate multiple times, at least), it's clearly more expensive in
| > | terms of memory.
| >
| > True, there is the memory cost. isn't making "informed and sound design
| > tradeoffs" the beauty of software design?
|
| :)
|
| > | Interesting. I've been running with .NET 1.1. I'll try it later with
| > | .NET 2.0 and see if that makes a difference.
| >
| > Hmmmm... generics and anon delegates on v1.1???
|
| Okay, I clearly need to get more sleep...
|
| > | Well, there's significantly less codependence in the new version, so
| > | I'm not entirely surprised. It's a more realistic test in various
ways.
| >
| > Yep, but there is more than the performance drop. The same test runs
| > correctly on Intel P4 and Mobile HW, but shows this pattern on 32/64 bit
AMD
| > and Intel.
|
| Ah, interesting. Are you investigating much further, or just shipping
| it off to MS? It sounds like something they ought to know about, and
| it's already a pretty short bit of code to reproduce the problem...
|

Yes, it's a short bit of code, but it drives a complex machinery. Actually
(well say next Monday) a guy much smarter than me, will investigate this
further to see whether it's purely an HW issue or not. I'll see what I can
do to keep you informed if you want to.

Willy.
Jan 20 '06 #22
Willy Denoyette [MVP] <wi*************@telenet.be> wrote:

<snip>
| Ah, interesting. Are you investigating much further, or just shipping
| it off to MS? It sounds like something they ought to know about, and
| it's already a pretty short bit of code to reproduce the problem...

Yes, it's a short bit of code, but it drives a complex machinery. Actually
(well say next Monday) a guy much smarter than me, will investigate this
further to see whether it's purely an HW issue or not. I'll see what I can
do to keep you informed if you want to.


Yes, please do :)

--
Jon Skeet - <sk***@pobox.com>
http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet
If replying to the group, please do not mail me too
Jan 20 '06 #23

"Jon Skeet [C# MVP]" <sk***@pobox.com> wrote in message
news:MP************************@msnews.microsoft.c om...
| Willy Denoyette [MVP] <wi*************@telenet.be> wrote:
|
| <snip>
|
| > | Ah, interesting. Are you investigating much further, or just shipping
| > | it off to MS? It sounds like something they ought to know about, and
| > | it's already a pretty short bit of code to reproduce the problem...
| >
| > Yes, it's a short bit of code, but it drives a complex machinery.
Actually
| > (well say next Monday) a guy much smarter than me, will investigate this
| > further to see whether it's purely an HW issue or not. I'll see what I
can
| > do to keep you informed if you want to.
|
| Yes, please do :)
|
| --
| Jon Skeet - <sk***@pobox.com>
| http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet
| If replying to the group, please do not mail me too

Ok, we finished a code analysis on different HW, and it turns out (in short)
that the issue relates to the size of the L2 cache the memory systems
hierarchy and the memory model.
Processors with 512KB or less do have the issue I mentioned above,
processors with 1MB and 2MB caches do not have the issue with the code as
posted, but they show the same behavior with larger List's, and in
real-world applications.
If you change the code line
List<string> list = new List<string>();
into:
List<string> list = new List<string>(Size);
and run the code, you'll see consistent behavior. The reason is that the
former code line creates a List with 132076 entries (null initialized) while
the latter creates a List with 100000 entries, that is, 13076 * 4 and 100000
* 4 bytes respectively. This means that in the first case, the L2 cache has
no more room left after the List is initialized and 'filled', in the latter
case the cache has ~100000 bytes 'free', which is enough to load the
delegates IL and compile it when the second test starts executing.
Willy.
Jan 24 '06 #24
Willy Denoyette [MVP] wrote:

<snip>
Ok, we finished a code analysis on different HW, and it turns out (in short)
that the issue relates to the size of the L2 cache the memory systems
hierarchy and the memory model.
Processors with 512KB or less do have the issue I mentioned above,
processors with 1MB and 2MB caches do not have the issue with the code as
posted, but they show the same behavior with larger List's, and in
real-world applications.
If you change the code line
List<string> list = new List<string>();
into:
List<string> list = new List<string>(Size);
and run the code, you'll see consistent behavior. The reason is that the
former code line creates a List with 132076 entries (null initialized) while
the latter creates a List with 100000 entries, that is, 13076 * 4 and 100000
* 4 bytes respectively. This means that in the first case, the L2 cache has
no more room left after the List is initialized and 'filled', in the latter
case the cache has ~100000 bytes 'free', which is enough to load the
delegates IL and compile it when the second test starts executing.


Wow. Interesting stuff. Doesn't explain the profiler dying, of course
:)

Of course, in the real world the cost of the iteration is usually not
going to be the limiting factor, but it's fascinating to see the
difference this can make.

Jon

Jan 24 '06 #25

"Jon Skeet [C# MVP]" <sk***@pobox.com> wrote in message
news:11*********************@g47g2000cwa.googlegro ups.com...
| Willy Denoyette [MVP] wrote:
|
| <snip>
|
| > Ok, we finished a code analysis on different HW, and it turns out (in
short)
| > that the issue relates to the size of the L2 cache the memory systems
| > hierarchy and the memory model.
| > Processors with 512KB or less do have the issue I mentioned above,
| > processors with 1MB and 2MB caches do not have the issue with the code
as
| > posted, but they show the same behavior with larger List's, and in
| > real-world applications.
| > If you change the code line
| > List<string> list = new List<string>();
| > into:
| > List<string> list = new List<string>(Size);
| > and run the code, you'll see consistent behavior. The reason is that the
| > former code line creates a List with 132076 entries (null initialized)
while
| > the latter creates a List with 100000 entries, that is, 13076 * 4 and
100000
| > * 4 bytes respectively. This means that in the first case, the L2 cache
has
| > no more room left after the List is initialized and 'filled', in the
latter
| > case the cache has ~100000 bytes 'free', which is enough to load the
| > delegates IL and compile it when the second test starts executing.
|
| Wow. Interesting stuff. Doesn't explain the profiler dying, of course
| :)
|
| Of course, in the real world the cost of the iteration is usually not
| going to be the limiting factor, but it's fascinating to see the
| difference this can make.
|
| Jon
|

The VS profiler dying is still under investigation (it's intermittent), all
we know is that it's timing related. People in our joint MS competence
center are further investigating this.
The in-house profiler issue was a bug in the profiler code which incorrectly
handled the anon delegate target creation, something that gets done when the
List.ForEach method runs for the first time (so not at method JIT time).
And that's also the biggest difference between both:

list.ForEach (delegate(string s) { sum += s.Length; });
and:
list.ForEach (action);

In the former case the delegate target is loaded from the IL and JIT
compiled when ForEach executes for the first time (quite interesting to
follow the code path, really). In the latter case the target is built before
'ForEach' runs (but also after the method gets JIT compiled). The resulting
code (after the first iteration) is exactly the same for both cases, BUT the
former is quite disturbing the cache (L1 and L2) in this particular test
(where the cache already contains most of the data to be iterated over).

Willy.



Jan 24 '06 #26
>People in our joint MS competence center are further investigating this.
You work for MS?

--
Regards,
Alvin Bruney [MVP ASP.NET]

[Shameless Author plug]
The Microsoft Office Web Components Black Book with .NET
Now Available @ www.lulu.com/owc
Forth-coming VSTO.NET - Wrox/Wiley 2006
-------------------------------------------------------

"Willy Denoyette [MVP]" <wi*************@telenet.be> wrote in message
news:ed**************@TK2MSFTNGP09.phx.gbl...

"Jon Skeet [C# MVP]" <sk***@pobox.com> wrote in message
news:11*********************@g47g2000cwa.googlegro ups.com...
| Willy Denoyette [MVP] wrote:
|
| <snip>
|
| > Ok, we finished a code analysis on different HW, and it turns out (in
short)
| > that the issue relates to the size of the L2 cache the memory systems
| > hierarchy and the memory model.
| > Processors with 512KB or less do have the issue I mentioned above,
| > processors with 1MB and 2MB caches do not have the issue with the code
as
| > posted, but they show the same behavior with larger List's, and in
| > real-world applications.
| > If you change the code line
| > List<string> list = new List<string>();
| > into:
| > List<string> list = new List<string>(Size);
| > and run the code, you'll see consistent behavior. The reason is that the | > former code line creates a List with 132076 entries (null initialized)
while
| > the latter creates a List with 100000 entries, that is, 13076 * 4 and
100000
| > * 4 bytes respectively. This means that in the first case, the L2 cache has
| > no more room left after the List is initialized and 'filled', in the
latter
| > case the cache has ~100000 bytes 'free', which is enough to load the
| > delegates IL and compile it when the second test starts executing.
|
| Wow. Interesting stuff. Doesn't explain the profiler dying, of course
| :)
|
| Of course, in the real world the cost of the iteration is usually not
| going to be the limiting factor, but it's fascinating to see the
| difference this can make.
|
| Jon
|

The VS profiler dying is still under investigation (it's intermittent), all we know is that it's timing related. People in our joint MS competence
center are further investigating this.
The in-house profiler issue was a bug in the profiler code which incorrectly handled the anon delegate target creation, something that gets done when the List.ForEach method runs for the first time (so not at method JIT time).
And that's also the biggest difference between both:

list.ForEach (delegate(string s) { sum += s.Length; });
and:
list.ForEach (action);

In the former case the delegate target is loaded from the IL and JIT
compiled when ForEach executes for the first time (quite interesting to
follow the code path, really). In the latter case the target is built before 'ForEach' runs (but also after the method gets JIT compiled). The resulting code (after the first iteration) is exactly the same for both cases, BUT the former is quite disturbing the cache (L1 and L2) in this particular test
(where the cache already contains most of the data to be iterated over).

Willy.


Jan 24 '06 #27
Alvin,

No, I don't. Major IHV's (and Universities) and large software companies
(MS, Oracle, SAP, etc..) have established all sort of 'joint' programs. A
'joint competence' program is one where both companies share their
knowledge, a 'joint R&D' program is one where they share research and
development efforts.

Willy.

"Alvin Bruney - ASP.NET MVP" <www.lulu.com/owc> wrote in message
news:u0**************@TK2MSFTNGP14.phx.gbl...
| >People in our joint MS competence center are further investigating this.
| You work for MS?
|
| --
| Regards,
| Alvin Bruney [MVP ASP.NET]
|
| [Shameless Author plug]
| The Microsoft Office Web Components Black Book with .NET
| Now Available @ www.lulu.com/owc
| Forth-coming VSTO.NET - Wrox/Wiley 2006
| -------------------------------------------------------
|
|
|
| "Willy Denoyette [MVP]" <wi*************@telenet.be> wrote in message
| news:ed**************@TK2MSFTNGP09.phx.gbl...
| >
| > "Jon Skeet [C# MVP]" <sk***@pobox.com> wrote in message
| > news:11*********************@g47g2000cwa.googlegro ups.com...
| > | Willy Denoyette [MVP] wrote:
| > |
| > | <snip>
| > |
| > | > Ok, we finished a code analysis on different HW, and it turns out
(in
| > short)
| > | > that the issue relates to the size of the L2 cache the memory
systems
| > | > hierarchy and the memory model.
| > | > Processors with 512KB or less do have the issue I mentioned above,
| > | > processors with 1MB and 2MB caches do not have the issue with the
code
| > as
| > | > posted, but they show the same behavior with larger List's, and in
| > | > real-world applications.
| > | > If you change the code line
| > | > List<string> list = new List<string>();
| > | > into:
| > | > List<string> list = new List<string>(Size);
| > | > and run the code, you'll see consistent behavior. The reason is that
| the
| > | > former code line creates a List with 132076 entries (null
initialized)
| > while
| > | > the latter creates a List with 100000 entries, that is, 13076 * 4
and
| > 100000
| > | > * 4 bytes respectively. This means that in the first case, the L2
| cache
| > has
| > | > no more room left after the List is initialized and 'filled', in the
| > latter
| > | > case the cache has ~100000 bytes 'free', which is enough to load the
| > | > delegates IL and compile it when the second test starts executing.
| > |
| > | Wow. Interesting stuff. Doesn't explain the profiler dying, of course
| > | :)
| > |
| > | Of course, in the real world the cost of the iteration is usually not
| > | going to be the limiting factor, but it's fascinating to see the
| > | difference this can make.
| > |
| > | Jon
| > |
| >
| > The VS profiler dying is still under investigation (it's intermittent),
| all
| > we know is that it's timing related. People in our joint MS competence
| > center are further investigating this.
| > The in-house profiler issue was a bug in the profiler code which
| incorrectly
| > handled the anon delegate target creation, something that gets done when
| the
| > List.ForEach method runs for the first time (so not at method JIT time).
| > And that's also the biggest difference between both:
| >
| > list.ForEach (delegate(string s) { sum += s.Length; });
| > and:
| > list.ForEach (action);
| >
| > In the former case the delegate target is loaded from the IL and JIT
| > compiled when ForEach executes for the first time (quite interesting to
| > follow the code path, really). In the latter case the target is built
| before
| > 'ForEach' runs (but also after the method gets JIT compiled). The
| resulting
| > code (after the first iteration) is exactly the same for both cases, BUT
| the
| > former is quite disturbing the cache (L1 and L2) in this particular test
| > (where the cache already contains most of the data to be iterated over).
| >
| > Willy.
| >
| >
| >
| >
| >
| >
| >
|
|
Jan 25 '06 #28

This discussion thread is closed

Replies have been disabled for this discussion.

Similar topics

5 posts views Thread by Jeffrey Silverman | last post: by
13 posts views Thread by cody | last post: by
9 posts views Thread by garyusenet | last post: by
7 posts views Thread by =?Utf-8?B?RXZhbiBSZXlub2xkcw==?= | last post: by
3 posts views Thread by =?Utf-8?B?YW1pcg==?= | last post: by
reply views Thread by zhoujie | last post: by
By using this site, you agree to our Privacy Policy and Terms of Use.