Expressions in C#...

A

Atmapuri

Hi!
this gives as result = 7520 ticks (7.5 secs.) with an "average % in GC" =
10.4.% > but again you have a 'bug' in you code...
Data.Length = aLength;

As you noticed the difference only made for 2.5% which is negligable.
The way I got 75% of difference is pinning down the arrays inside
each of the operators and passing the arrays to external SSE2
optimized dll. The cost of the math operation was reduced drastically
and GC cost landed at 75%. The relative cost of pinning down
was negligable...

Furthermore, I evaluated the formula also by reusing the
same arrays (without the expression) and the result was:

1.) Plain C# function working on one element at a time: 1600ms
2.) Expression in C# on 1000 elements with math in SSE2: 1200ms
3.) Expression in C# with 1000 elements with math in C#: 1900ms
4.) Formula evaluation without releasing arrays and without
pinning down: 300ms (no expression but methods passing pointers around
and arrays pinned down before the timer starts).

So, for the case #2, the overhead of GC as measured by
AQTime was 75%.
Also, allocating arrays of doubles are expensive, an array of 1000 or more
doubles ends on the Large Object Heap, this heap is never compacted and
only

Interesting. How would you fix the design to achieve 300ms of
the case #4 for expression in case #3?

The difference is huge. The application is very common and
practical and technology should server the people... and not
vice versa...

Thanks!
Atmapuri
 
M

Marc Gravell

Is there any easy way to tell if an object is on the LOH? Does it's going
straight to gen2 indicate this? (reliably) if so, I get the following (where
"Approx mem size" is Marshal.SizeOf(typeof(T)) * length)

Approx mem size, Type[array size]: initial generation
339948 Boolean[84987]: 0
339952 Boolean[84988]: 2
84987 Byte[84987]: 0
84988 Byte[84988]: 2
84986 Int16[42493]: 0
84988 Int16[42494]: 2
84984 Int32[21246]: 0
84988 Int32[21247]: 2
84984 Int64[10623]: 0
84992 Int64[10624]: 2
84984 Single[21246]: 0
84988 Single[21247]: 2
84976 Decimal[5311]: 0
84992 Decimal[5312]: 2
7992 Double[999]: 0
8000 Double[1000]: 2
 
B

Barry Kelly

Marc said:
Is there any easy way to tell if an object is on the LOH?

A highly reliable way is by debugging the application, examining the
state of the GC heap with the SOS functions. That'll tell you the
segments used for gen0/1, gen2 and LOH.

As a matter of interest: the code generated for allocating an array of
1000 doubles is something like this:

---8<---
00ca009c bae8030000 mov edx,3E8h
00ca00a1 b95a9d1579 mov ecx,offset mscorlib_ni+0x99d5a
(79159d5a)
00ca00a6 e8d521c7ff call 00912280 (JitHelp:
CORINFO_HELP_NEWARR_1_ALIGN8)
--->8---

3E8h = 1000.

Peeking into 00912280, it starts like this:

---8<---
00912280 51 push ecx
00912281 52 push edx
00912282 8b4902 mov ecx,dword ptr [ecx+2]
00912285 81fae8030000 cmp edx,3E8h
0091228b 7367 jae 009122f4
0091228d 0fb701 movzx eax,word ptr [ecx]
--->8---

This CORINFO_HELP_NEWARR_1_ALIGN8 isn't used for longs. For that,
CORINFO_HELP_NEWARR_1_VC is used.

-- Barry
 
W

Willy Denoyette [MVP]

| Is there any easy way to tell if an object is on the LOH? Does it's going
| straight to gen2 indicate this? (reliably) if so, I get the following
(where
| "Approx mem size" is Marshal.SizeOf(typeof(T)) * length)
|

No, small objects always start their life in Gen0 while large objects (>85K
and >8K for doubles) are allocated from the LOH. Objects can never start
their life in Gen1 or Gen2.
One sure way to know the location of your managed objects in the heap is by
loading the SOS.DLL extention in the debugger.

| Approx mem size, Type[array size]: initial generation
| 339948 Boolean[84987]: 0
| 339952 Boolean[84988]: 2
| 84987 Byte[84987]: 0
| 84988 Byte[84988]: 2
| 84986 Int16[42493]: 0
| 84988 Int16[42494]: 2
| 84984 Int32[21246]: 0
| 84988 Int32[21247]: 2
| 84984 Int64[10623]: 0
| 84992 Int64[10624]: 2
| 84984 Single[21246]: 0
| 84988 Single[21247]: 2
| 84976 Decimal[5311]: 0
| 84992 Decimal[5312]: 2
| 7992 Double[999]: 0
| 8000 Double[1000]: 2
|

Looks like Gen 2 and LOH are treated as equal here, don't know how you
obtained this figure, but you should take care what function you use to get
at these counters, most of them initiate a GC defeating the purpose of your
investigation.

Willy.
 
M

Marc Gravell

I was simply asking the CLR via:
T[] data = new T[length];
int gen = GC.GetGeneration(data);

This was purely out of interest, mind. Your point about kicking off a GC is
well taken - this wasn't production code, just a "hmm that's interesting...
I wonder what that looks like..." piece of throw-away code in responnse to
your 1000 double claim, which appears to be entirely accurate. Interesting
that the large objects (appear to) report as gen2, though. I guess I was
actually expecting to see -1 or "GC.MaxGeneration + 1" (if you see what I
mean).

So far, I've reseisted the urge to fight the GC; I'm generally of the
opinion "leave it [the hell] alone", but it can sometimes be illuminating to
peek under the covers a little bit. Code monkey voyeurism.

Thanks both Willy and Barry - some interseting info.

Marc
 
A

Atmapuri

Hi!
No, small objects always start their life in Gen0 while large objects
(>85K
and >8K for doubles) are allocated from the LOH. Objects can never start

I also noticed a small dip when the double array exceeded 1000 elements.
But that was only about 15% or so and in compare to to the total cost
of GC almost irrelevant..

Thanks!
Atmapuri
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top