Expressions in C#...

A

Atmapuri

Hi!
for an in-depth post about why .NET doesn't use reference counting.

It has isolated itself from the subjects that do require deterministic
finalization to run efficiently.

One of the key flaws in the conclusions
made in that discussion is this assumption:

Assuming that everything is an object what is the cost of the reference
counting on the smallest (4byte) object?

Reference counting is actually very efficient for "large" objects
and GC looses there... Why would you want to reference count
an integer value?

Few more false statements:
1.) With reference counting you can only pass variables by reference.
I have code that does not do that in C++
2.) Copy constructors are needed.
Nop. See Delphi 2006. It does it perfectly... It allows you to
implement reference counting on records, without copy
constructors and with the ability to pass variables by value.

a := b;

does an actual deep copy but there is no copy constructor.

Thanks!
Atmapuri
 
J

Jon Skeet [C# MVP]

Arne Vajhøj said:
Java does not use reference counting in any of the
big current implementations.

Indeed.
Memory leaks and poor performance.

I use Java all the time and see neither of them.
PHP and Python uses reference counting. And it works fine
for them. But they have a sligtly different scope and
their "framework" is written in C.

By "it works fine for them", how do they deal with cyclic references
etc?

Did you read the post I referenced above?
 
J

Jon Skeet [C# MVP]

Atmapuri said:
It has isolated itself from the subjects that do require deterministic
finalization to run efficiently.

Well, it's forced deterministic finalization to be manual, and C# has
made this easier for most scenarios with the "using" statement.

Is .NET the perfect environment for every type of task? No, and I don't
think anyone seriously claims it is. That doesn't mean it's made any
bad design decisions (it has, but not in this area IMO) - it just means
that there's a balancing act involved.
One of the key flaws in the conclusions
made in that discussion is this assumption:

Assuming that everything is an object what is the cost of the reference
counting on the smallest (4byte) object?

Reference counting is actually very efficient for "large" objects
and GC looses there... Why would you want to reference count
an integer value?

You're assuming the cost is in memory. Reference counting is still
inefficient in *time* if references to the object are frequently
used/discarded. (I believe someone modified Rotor to be reference
counted, and found that it was costly in a significant number of
scenarious.)
Few more false statements:
1.) With reference counting you can only pass variables by reference.
I have code that does not do that in C++

Where was that statement? I can't find it on a quick scan through...
2.) Copy constructors are needed.
Nop. See Delphi 2006. It does it perfectly... It allows you to
implement reference counting on records, without copy
constructors and with the ability to pass variables by value.

Well, as far as I can see the copy constructor discussion is limited to
stack allocated local value types. It would be perfectly possible to
have reference types being reference counted without copy constructors,
and the post doesn't say otherwise.
a := b;

does an actual deep copy but there is no copy constructor.

Actually no copy constructor, or just none explicitly defined?
Something's got to be doing the copying - does it matter whether we
call that a copy constructor or not? How deep should the copy be?
Should something in the system "know" that for strings, you don't need
to copy the data, because strings are immutable - a reference copy
would be enough?
 
?

=?ISO-8859-1?Q?Arne_Vajh=F8j?=

Jon said:
I use Java all the time and see neither of them.

No. Because Java does not use reference counting.

If it did you would see them.
By "it works fine for them", how do they deal with cyclic references
etc?

Did you read the post I referenced above?

Yes. Well - most of it - there was no so much new in it.

I think the bottom line is that the problems are not
a big problem in the context those languages are
primarily used in.

Python has an optional cycle detector to detect cyclic
references.

Arne
 
J

Jon Skeet [C# MVP]

Arne Vajhøj said:
No. Because Java does not use reference counting.

If it did you would see them.

Ah. I see. Your post wasn't really clear about it - I thought you meant
that Java *did* have memory leaks and performance problems because it
doesn't use reference counting.
Yes. Well - most of it - there was no so much new in it.

I think the bottom line is that the problems are not
a big problem in the context those languages are
primarily used in.

Sure - and that context is fairly different to .NET, I believe.
Python has an optional cycle detector to detect cyclic
references.

Hmm... one of these days I must get round to learning Python. And
Smalltalk. Oh for more time on my hands...
 
W

Willy Denoyette [MVP]

| Hi!
|
| > The object inside the struct would be an instance of Vec1 right?
|
| I would implement another Vec1Container that would have the finalizer
| and a pointer to Vec1 and Vector1 would only point to Vec1Container.
| When struct Vector1 would be released from the stack, the finalizer
| will get its chance..
|
| I was thinking that struct's are allocated on the stack and not on the
heap
| and the stack is not cleared up until the expression has been evaluted.
| This is not the same as for objects, where only the reference is stored
| on the stack...
|
| > a dirty hack meant to solve a problem which should'n even exist (a bug
| > introduced by YOU)... could look like this:
| >
| > public class Vec1
| > {
| > internal Vector1 vRef; // store a reference to the containing
| > instance
| > of Vector1
|
| That would not work, because the finalizer would never get called,
| because the Vec1 has a second reference that is never set to nil until
| the end of app life.
|

That's why a said a dirty hack, that solves the problem you illustrate with
the sample you posted.

| > Please try to answer these simple questions:
| > why must the value (Length) be reset?
| > if you have a valid reason for this (but honestly I don't see any),
why
| > are you relying on a non-deterministic destructor to do this? You are
| > never
| > sure when it will be done, nor if it will ever be done.
|
| With critical finalizers you are... Besides, my finalizer is very very
| short.

No, they are still non deterministic, and can only be used in hosted
environments!
But still you don't answer the other (most important) questions.

| That means that it will always be executed and if the app is destroyed
| before that happens... no problem...
|
| > The only workaround is - get rid of this Dispose pattern altogether -,
you
| > don't need it, it put extreme high pressure on the GC which slows down
| > your
| > code for no good reason (well, I don't see any).
|
| The unmanaged resource is unmanaged memory.

But your finalizer does not release unmanaged memory, it moifies an shared
object instance , and this is not what you should do i a finalizer.


When you write expressions
| you create a lot of temporary objects especially if you execute in a loop.
| This gives great pressure on the garbage collector. One other problem
| is that the garbage collector allocates memory over great spans of memory
| which is not very close together and thus can not be cached by the CPU.
|

This is not true, I suggest you read something about how the GC is working.
Short living objects aren't out-living the Gen0 part of the GC heap
(provided they don't have finalizers), the CLR tries to keep the size of
Gen0 more or less the same as the size of the L2 cache of the CPU the CLR
runs on (but there are othe heuristics, like high allocation frequency which
makes that the Gen0 grows beyond that size). Of course finalizable objects
(like yours) will survive the next GC collection and will end on Gen1, this
way you are disturbing the optimum circumstances for the GC to do it's job.
Anyway, the Gen0 is always empty after a GC run and follows the Gen1 and
Gen2 parts in the heap, that means that the Gen0 is always at the start of a
large piece of contigious free memory, new object allocations are adjacent
in Gen0 and are in sequence of their allocation time, so they never span a
large area.


| So, the idea is to allocate unmanaged memory and have that memory
| preallocated
| in a pool. Each time you allocate an object, it simply obtains a pointer
| from the pool. Each time you free an object (via finalizer), the pointer
| is released to the pool.
|
Not sure what you are talking about, managed objects cannot be allocated in
unmanaged memory, they always end in the GC heap.

| I was running some tests and due to tighter memory pool, the
| performance gains of vectorized against plain function
| for the function posted are around 5x and
| that despite the fact that I call GC.Collect inside the
| constructor every now and then...
|
| The proper solution to this problem however would be to introduce
| proper reference counted classes that have their destructors called
| by the IL immediately after the function ends if their reference count
| is 0. But this is only something that Microsoft can do...
| .

By the IL? You must be kidding, right?

Willy.
 
A

Atmapuri

Hi!

You have not commented if the alternative workaround is fail safe:
| I would implement another Vec1Container that would have the finalizer
| and a pointer to Vec1 and Vector1 would only point to Vec1Container.
| When struct Vector1 would be released from the stack, the finalizer
| will get its chance..
|
| I was thinking that struct's are allocated on the stack and not on the
heap
| and the stack is not cleared up until the expression has been evaluted.
| This is not the same as for objects, where only the reference is stored
| on the stack...

But your finalizer does not release unmanaged memory, it moifies an shared
object instance , and this is not what you should do i a finalizer.

In my full source code it does..
This is not true, I suggest you read something about how the GC is
working.
Short living objects aren't out-living the Gen0 part of the GC heap
(provided they don't have finalizers), the CLR tries to keep the size of
Gen0 more or less the same as the size of the L2 cache of the CPU the CLR
runs on (but there are othe heuristics, like high allocation frequency
which
makes that the Gen0 grows beyond that size). Of course finalizable objects
(like yours) will survive the next GC collection and will end on Gen1,
this
way you are disturbing the optimum circumstances for the GC to do it's
job.
Anyway, the Gen0 is always empty after a GC run and follows the Gen1 and
Gen2 parts in the heap, that means that the Gen0 is always at the start of
a
large piece of contigious free memory, new object allocations are adjacent
in Gen0 and are in sequence of their allocation time, so they never span a
large area.

In theory :). I did my tests. I ran the math function with different vector
lenghts: 10,100,1000,10000 etc.. and timed them.

If CPU cache would have been used, then the speed at 10..10000 elements
would be up to 6x higher as with 100000 elements long vectors
which can not fit in the cache. But guess what... There was no dip,
absolutely
no gain. The curve decayed exponentially towards longer vectors...
You can check that exponential decaying curve yourself. The source is
attached below...

And with my "weird" design the performance gain over the garbage
collected vectors was 400% for lengths between 500 and 3000.

So the GC and CPU cache pairing in this case gives 0.0.
| So, the idea is to allocate unmanaged memory and have that memory
| preallocated
| in a pool. Each time you allocate an object, it simply obtains a pointer
| from the pool. Each time you free an object (via finalizer), the pointer
| is released to the pool.
|
Not sure what you are talking about, managed objects cannot be allocated
in
unmanaged memory, they always end in the GC heap.

Unless you use the Marsall.AllocHGlobal.
| I was running some tests and due to tighter memory pool, the
| performance gains of vectorized against plain function
| for the function posted are around 5x and
| that despite the fact that I call GC.Collect inside the
| constructor every now and then...
|
| The proper solution to this problem however would be to introduce
| proper reference counted classes that have their destructors called
| by the IL immediately after the function ends if their reference count
| is 0. But this is only something that Microsoft can do...
| .

By the IL? You must be kidding, right?

If the SafeHandle class would have its Dispose method called
when its compiler managed reference count would fall to zero,
that would merge the best of both worlds.
Delphi implements a reference counter and when the
procedures exits, it checks if the reference count for local variables
is zero and frees them...

That would be piece of cake to implement for SafeHandle in IL.

Regards!
Atmapuri

public class Vec1
{
private int fLength;
public double[] Data;
public Vec1()
{
Length = 100;
}
public int Length
{
get
{
return this.fLength;
}
set
{
Data = new double[value];
fLength = value;
}
}
}


public class Vector1
{
public Vec1 Data;
static private int VecIterCount = 0;
static private int GCInterval = 200;
static private int GCGeneration = 0;

protected virtual void Dispose(bool disposing)
{
if (Data != null)
{
Data.Length = 0;
}
}


public Vector1(int aLength)
{
Data = null;
Data = new Vec1();
Data.Length = aLength;
}

public Vector1(Vec1 Src)//: base(IntPtr.Zero, true)
{
Data = null;
Data = new Vec1();
Data.Length = Src.Length;
}

public static implicit operator Vec1 (Vector1 AValue)
{
return AValue.Data;
}

public int Length
{
get
{
return this.Data.Length;
}
set
{
this.Data.Length = value;
}
}

public static Vector1 operator *(Vector1 Left, double Right)
{
Vector1 vector = new Vector1(Left.Length);
double[] SrcLeft, Dst;
SrcLeft = Left.Data.Data;
Dst = vector.Data.Data;
int Len = vector.Length;
for (int i = 0; i < Len; i++)
{
Dst = SrcLeft * Right;
}
return vector;
}
public static Vector1 operator *(double Left, Vector1 Right)
{
Vector1 vector = new Vector1(Right.Length);
double[] SrcRight, Dst;
SrcRight = Right.Data.Data;
Dst = vector.Data.Data;
int Len = vector.Length;
for (int i = 0; i < Len; i++)
{
Dst = Left * SrcRight;
}
return vector;
}
public static Vector1 operator *(Vector1 Left, Vec1 Right)
{
Vector1 vector = new Vector1(Left.Length);
double[] SrcLeft, SrcRight, Dst;
SrcLeft = Left.Data.Data;
SrcRight = Right.Data;
Dst = vector.Data.Data;
int Len = vector.Length;
for (int i = 0; i < Len; i++)
{
Dst = SrcLeft * SrcRight;
}
return vector;
}
public static Vector1 operator *(Vec1 Left, Vector1 Right)
{
Vector1 vector = new Vector1(Left.Length);
double[] SrcLeft, SrcRight, Dst;
SrcLeft = Left.Data;
SrcRight = Right.Data.Data;
Dst = vector.Data.Data;
int Len = vector.Length;
for (int i = 0; i < Len; i++)
{
Dst = SrcLeft * SrcRight;
}
return vector;
}
public static Vector1 operator *(Vector1 Left, Vector1 Right)
{
Vector1 vector = new Vector1(0);
vector.Length = Left.Length;
double[] SrcLeft, SrcRight, Dst;
SrcLeft = Left.Data.Data;
SrcRight = Right.Data.Data;
Dst = vector.Data.Data;
int Len = vector.Length;
for (int i = 0; i < Len; i++)
{
Dst = SrcLeft * SrcRight;
}
return vector;
}
}

public class Vec1Math
{
public static Vector1 Exp(Vec1 x)
{
Vector1 vector = new Vector1(0);
vector.Length = x.Length;
double[] Src, Dst;
Src = x.Data;
Dst = vector.Data.Data;
int Len = vector.Length;
for (int i = 0; i < Len; i++)
{
Dst = Math.Exp(Src);
}
return vector;
}

public static Vector1 Sqrt(Vec1 x)
{
Vector1 vector = new Vector1(0);
vector.Length = x.Length;
double[] Src, Dst;
Src = x.Data;
Dst = vector.Data.Data;
int Len = vector.Length;
for (int i = 0; i < Len; i++)
{
Dst = Math.Sqrt(Src);
}
return vector;
}
}

//The function benchmarked

private double MaxwellExpression1(int Iterations)
{
double a = 1;
Vector1 xx, res1;
res1 = null;
int counter = Environment.TickCount;
for (int i = 0; i < Iterations; i++)
{
xx = x1 * x1;
res1 = Math.Sqrt(4 * Math387.INVTWOPI * a) * a * xx *
Vec1Math.Exp(-0.5 * a * xx);
}
int result = Environment.TickCount - counter;
return result;
}

Iternations count was: 10,100,1000,10000,100000,1000000
Short vectors are not faster than long vecors, which prooves that
CPU cache is not being leveraged at all..
 
A

Atmapuri

Hi!
You're assuming the cost is in memory. Reference counting is still
inefficient in *time* if references to the object are frequently
used/discarded. (I believe someone modified Rotor to be reference
counted, and found that it was costly in a significant number of
scenarious.)

Only if objects are small and operations on them short.
Actually no copy constructor, or just none explicitly defined?
Something's got to be doing the copying - does it matter whether we
call that a copy constructor or not? How deep should the copy be?
Should something in the system "know" that for strings, you don't need
to copy the data, because strings are immutable - a reference copy
would be enough?

A copy remains reference copy until it needs to be changed.
So, you can read from it and the copy wont be made, but as soon
as you write to it, it will detach and make an extra copy...

(Of course, if you pass variables by reference, an extra copy
is not made....)

Regards!
Atmapuri
 
J

Jon Skeet [C# MVP]

Atmapuri said:
Only if objects are small and operations on them short.

No. How can the size of the object make any difference to the *time*
performance cost due to assignments etc (anything which changes the
reference count)?
A copy remains reference copy until it needs to be changed.
So, you can read from it and the copy wont be made, but as soon
as you write to it, it will detach and make an extra copy...

(Of course, if you pass variables by reference, an extra copy
is not made....)

Interesting. I'd have to examine that scenario further to comment,
frankly.
 
A

Atmapuri

Hi!
No. How can the size of the object make any difference to the *time*
performance cost due to assignments etc (anything which changes the
reference count)?

Because large objects contain a lot of data. And a lot of data
needs a lot of processing. If you have a function call that works
on a lot of data, the overhead of reference count is basically zero.

In case of garbage collector however, the overhead is growing
with the size of the object. In the example I posted, if you allocate
arrays of double and of Length, and also perform the
computations, you will noticed that garbage collector takes 75%
of time... and computation only 25%. I measured that with a
profiler..

Thats because if you allocate 100 000 objects of 8kB within
a tight loop the GC is working like crazy.... and reference
count is not affected by object size. Its overhead is constant
and for large objects basically zero.

Regards!
Atmapuri
 
J

Jon Skeet [C# MVP]

Atmapuri said:
Because large objects contain a lot of data. And a lot of data
needs a lot of processing. If you have a function call that works
on a lot of data, the overhead of reference count is basically zero.

The amount of data isn't always related to the amount of processing
required on it. You might need to do a huge amount of work on a few
numbers, or you might need to just increment every byte within a huge
array.
In case of garbage collector however, the overhead is growing
with the size of the object. In the example I posted, if you allocate
arrays of double and of Length, and also perform the
computations, you will noticed that garbage collector takes 75%
of time... and computation only 25%. I measured that with a
profiler..

Thats because if you allocate 100 000 objects of 8kB within
a tight loop the GC is working like crazy.... and reference
count is not affected by object size. Its overhead is constant
and for large objects basically zero.

Again, that entirely depends on what you do with the objects. If you
pass the object references around more than you deal with the actual
data, then it's *not* zero.
 
A

Atmapuri

Hi!
Again, that entirely depends on what you do with the objects. If you
pass the object references around more than you deal with the actual
data, then it's *not* zero.

Yes. But that is "theory". In reality the cost of summing in an expression
together two double arrays of 1000 elements 100000x with reference counting
takes
100% of time for the math operation and 24KB of memory.

With garbage collection it takes cca 50MB of memory and 75% of CPU
is used by the garbage collector..

Regards!
Atmapuri
 
J

Jon Skeet [C# MVP]

Atmapuri said:
Yes. But that is "theory". In reality the cost of summing in an expression
together two double arrays of 1000 elements 100000x with reference counting
takes
100% of time for the math operation and 24KB of memory.

With garbage collection it takes cca 50MB of memory and 75% of CPU
is used by the garbage collector..

You seem determined to work against garbage collection, so I suggest
you use a different platform. It's almost never a good idea to work in
an environment which is fundamentally opposed to the way you like to
work. If Delphi 2006 works well for you, why not use that? You could
always write most of your performance intensive code in that and use
P/Invoke to access it via .NET if you need to interoperate with other
..NET code.
 
A

Atmapuri

Hi!
You seem determined to work against garbage collection, so I suggest
you use a different platform. It's almost never a good idea to work in
an environment which is fundamentally opposed to the way you like to
work.

It has nothing to do with the way I like to work, but with the way
equations are written in the books and that is how people like to
write them. It is my job to make them as happy as possible
when they work in .NET and therefore have to turn .NET/C#/GC inside out...
:)

I dont see C# as being too far from being able to handle
that efficiently. I think GC is good, but needs some tweaking.
How and what exactly... is left open... If the SafeHandle object
would be disposed by the compiler, when its reference count would fall to
zero
at the end of procedure it would already solve 90% of the issues..

And why stick with nondeterministic finalization if it is obvous
that you can also provide an alternative? Its not the question
of one or the other... Let the programmer choose depending
on the problem...

Thanks!
Atmapuri
 
W

Willy Denoyette [MVP]

| Hi!
|
| >> Only if objects are small and operations on them short.
| >
| > No. How can the size of the object make any difference to the *time*
| > performance cost due to assignments etc (anything which changes the
| > reference count)?
|
| Because large objects contain a lot of data. And a lot of data
| needs a lot of processing. If you have a function call that works
| on a lot of data, the overhead of reference count is basically zero.
|
| In case of garbage collector however, the overhead is growing
| with the size of the object. In the example I posted, if you allocate
| arrays of double and of Length, and also perform the
| computations, you will noticed that garbage collector takes 75%
| of time... and computation only 25%. I measured that with a
| profiler..
|
| Thats because if you allocate 100 000 objects of 8kB within
| a tight loop the GC is working like crazy.... and reference
| count is not affected by object size. Its overhead is constant
| and for large objects basically zero.
|
| Regards!
| Atmapuri
|
|

Well I ran your code on my box, with following code in Main and
MaxwellExpression1:

class Tester
{
static void Main()
{

int iters = 100000;
double res = MaxwellExpression1(iters);
Console.WriteLine(res);
}
static double MaxwellExpression1(int Iterations)
{
double a = 1;
Vector1 xx, res1, x1;
res1 = null;
x1 = new Vector1(1000);
int counter = Environment.TickCount;
for (int i = 0; i < Iterations; i++)
{
xx = x1 * x1;
res1 = Math.Sqrt(4 * 2 * a) * a * xx * Vec1Math.Exp(-0.5 * a *
xx);
}
int result = Environment.TickCount - counter;
return result;
}
}

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...
[1] your Vec1 contructor looks like this:

public Vec1()
{
Length = 100;
}

and your Vector1 constructor:
public Vector1(int aLength)
{
Data = null;
Data = new Vec1();
Data.Length = aLength;
that means that you firts create an instance of Vec1 with a size of 100
elements, directly followed by a new instantiation now with Lenth size, the
first Vec1 instantiation is of no use, it only increases the GC pressure.

The sme test with Length = 100; removed resutls in:
result ~ 7440 ticks with 8.7% Average GC%.

Your 75% in GC is not normal, and makes me think that you are watching this
in a profiler, something you should never do, simply run your code and watch
the perf counters in Permon.
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
collected when a the GC does a full collect (this collects the whole heap
Gen0,1,2 and LOH), quite expensive.
To illustrate this you can run the test with 999 elements and watch the
difference.
On my box it gives result = ~5000 ticks with ~4% Average in GC.

Another remark is that you don't allocat100000 * 8K objects you actually
allocate 3 * 8K objects 100000 times (100000 iterations) + the 100000 times
800 bytes (see [1] above).

Other questions are- what OS and Framework are you running this on?
How did you compile the program? (debug/release/optimized?)

Willy.
 
J

Jon Skeet [C# MVP]

Atmapuri said:
It has nothing to do with the way I like to work, but with the way
equations are written in the books and that is how people like to
write them. It is my job to make them as happy as possible
when they work in .NET and therefore have to turn .NET/C#/GC inside out...
:)

I dont see C# as being too far from being able to handle
that efficiently. I think GC is good, but needs some tweaking.
How and what exactly... is left open... If the SafeHandle object
would be disposed by the compiler, when its reference count would fall to
zero at the end of procedure it would already solve 90% of the issues..

Except that that requires reference counting...
And why stick with nondeterministic finalization if it is obvous
that you can also provide an alternative? Its not the question
of one or the other... Let the programmer choose depending
on the problem...

The "some types being reference counted" idea is addressed in the
article I posted a link to.
 
J

Jon Skeet [C# MVP]

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
collected when a the GC does a full collect (this collects the whole heap
Gen0,1,2 and LOH), quite expensive.

Just to clarify - did you really mean 1000 or more, or did you mean
10000 or more? I thought the LOH only kicked in around 80K, not 8K.
 
W

Willy Denoyette [MVP]

| Hi!
|
| > You seem determined to work against garbage collection, so I suggest
| > you use a different platform. It's almost never a good idea to work in
| > an environment which is fundamentally opposed to the way you like to
| > work.
|
| It has nothing to do with the way I like to work, but with the way
| equations are written in the books and that is how people like to
| write them. It is my job to make them as happy as possible
| when they work in .NET and therefore have to turn .NET/C#/GC inside out...
| :)
|
| I dont see C# as being too far from being able to handle
| that efficiently. I think GC is good, but needs some tweaking.
| How and what exactly... is left open... If the SafeHandle object
| would be disposed by the compiler, when its reference count would fall to
| zero
| at the end of procedure it would already solve 90% of the issues..
|

This is pure nonsense, but if you think it's all that simple , just grab a
copy of Rotor and implement your 'solution', we will all be happy to
investigate the results.


| And why stick with nondeterministic finalization if it is obvous
| that you can also provide an alternative? Its not the question
| of one or the other... Let the programmer choose depending
| on the problem...
|
| Thanks!
| Atmapuri
|
|

Your conclusions are based on false results, post a complete sample
(preferably a console sample)that compiles without us needing to tweak the
code to make it compile, post how you compiled (preferable from the command
line) and how you measure CPU and GC consumption, also post the test
environment HW (CPU memory and cache size(s)), OS and CLR versions.

Willy.
 
W

Willy Denoyette [MVP]

|
| <snip - interesting to see the different figures you got, Willy!>
|

Well, it is not surprising after all. There is a lot more to tell how this
code behaves on the CLR, ut I really have no time nor the ambition for the
moment.

| > 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
| > collected when a the GC does a full collect (this collects the whole
heap
| > Gen0,1,2 and LOH), quite expensive.
|
| Just to clarify - did you really mean 1000 or more, or did you mean
| 10000 or more? I thought the LOH only kicked in around 80K, not 8K.
|

Yep, I really mean 1000 elements of doubles, 999 elements end on the
Generational heap.

The LOH is used to allocate objects (array's) of > 85Kb, EXCEPT for arrays
of doubles, here the threshold is 8000 bytes (the array). I was highly
surprised myself and really thought it was a bug, the first time I saw this,
but after some digging with the debugger I noticed that a different
allocation path was chosen for double types array, so it looks like it's
intentional. This whole discussion reminds me that I should file a RFC on
this.

Willy.
 
J

Jon Skeet [C# MVP]

Yep, I really mean 1000 elements of doubles, 999 elements end on the
Generational heap.

The LOH is used to allocate objects (array's) of > 85Kb, EXCEPT for arrays
of doubles, here the threshold is 8000 bytes (the array). I was highly
surprised myself and really thought it was a bug, the first time I saw this,
but after some digging with the debugger I noticed that a different
allocation path was chosen for double types array, so it looks like it's
intentional. This whole discussion reminds me that I should file a RFC on
this.

Wow. That's really strange. Thanks for the heads up...
 

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