.NET Forum / .NET Framework / Performance / February 2004
Reducing load on GC
|
|
Thread rating:  |
Lord Crc - 05 Feb 2004 21:35 GMT Hi
Im making a raytracer in c#. I tried profiling my app with optimizeit and found that after a short while, on average 60-80% of the total time is spent in the GC. Several times it goes for a while to 100%.
I presume this is because i create a lot of temporary vectors and other classes in my calculations.
Is there any way to help the GC so that it doesn't choke?
And yes, i've tried to reduce the number of temps created, but there's a limit to what i can do without it severly impacting the "codeability".
- Asbjørn
David Browne - 05 Feb 2004 22:28 GMT > Hi > [quoted text clipped - 10 lines] > a limit to what i can do without it severly impacting the > "codeability". One method is to replace reference objects with value objects.
Another is to recycle your temporary objects.
Replacing
Vector v = new Vector(10); with Vector v = MyVectorPool.Get(10); ... MyVectorPool.Put(v);
David
Lord Crc - 05 Feb 2004 23:11 GMT >One method is to replace reference objects with value objects. > >Another is to recycle your temporary objects. so structs are better on the GC?
the problem with recycling is that it has to be thread safe, and it seem that it kills the peformance...
- Asbjørn
David Browne - 06 Feb 2004 01:08 GMT > >One method is to replace reference objects with value objects. > > > >Another is to recycle your temporary objects. > > so structs are better on the GC? Yes. They live on the stack, not the heap so they generate no garbage. Unless they aggregate reference types like Arrays or Strings, etc. Arrays are the killer. It's hard to live without arrays, and arrays always live on the heap.
But so long as they contain only other value types, there's no garbage.
> the problem with recycling is that it has to be thread safe, and it > seem that it kills the peformance... For thread-safe pooling, nothing beats the [ThreadStatic] attribute. It turns any static field into a ThreadStatic field, so each thread has its own copy.
class Vector : IDisposable { [ThreadStatic] static Stack pool = new Stack(); public static Vector newVector() { if (pool.Count > 0) return (Vector)pool.Pop(); else return new Vector(); } public void Dispose() { //clean up instance fields for reuse //then put it back in the pool pool.Push(this); } //Vector implementation }
Then using (Vector v = Vector.newVector()) { //do stuff };
Exiting the using block will Dispose the Vector, which returns it to the pool.
David
Niall - 09 Feb 2004 05:43 GMT It's also important to look at the ageing behaviour of your objects. GCs of gen 0 are fast, gen 1 are fastish and gen 2 are slow. From Rico Mariani's site, he says a gen 0 collection every second, a gen 1 collection every 10 seconds, and (I think) a gen 2 collection every couple of minutes are ok. Obviously this depends on your specific program situation.
Use the Performance Monitor to look at the sizes of your gen 0, gen 1, gen 2 heaps, how often the collections for each generation are happening, and how much is promoted into the next generation. I find it hard to believe that if the bulk of collected objects were in gen 0, your ray tracer would be spending so much time in GC. After all, there is a lot of computationally intensive work in a tracer - at some point it has to stop allocating memory and actually calculate some image data.
If it is the case that you are getting collections in gen 1 and gen 2 too often, then you need to change your code to allow objects to be released as early as possible. The more you can keep in gen 0, the better.
Niall
> > >One method is to replace reference objects with value objects. > > > [quoted text clipped - 46 lines] > > David Lord Crc - 09 Feb 2004 12:02 GMT >If it is the case that you are getting collections in gen 1 and gen 2 too >often, then you need to change your code to allow objects to be released as >early as possible. The more you can keep in gen 0, the better. Ok, so setting things to null as soon as im done with them is good?
- Asbjørn
Niall - 09 Feb 2004 23:32 GMT Randomly setting to null isn't good, you need to have a specific reason to null out a reference.
The only time it will do much good is if you have a long-lived object holding a reference to a short lived object. In this case, if you null out the reference to the short lived object, you make it collectible (presuming nothing else is referencing the object).
Niall
> >If it is the case that you are getting collections in gen 1 and gen 2 too > >often, then you need to change your code to allow objects to be released as [quoted text clipped - 3 lines] > > - Asbj?rn Lord Crc - 10 Feb 2004 21:09 GMT >Randomly setting to null isn't good, you need to have a specific reason to >null out a reference. i was thinking when im done with something inside a method. some profiling shows that gen 0 and gen 1 are fairly constant, but gen 2 just grows and grows and grows...
- Asbjørn
Niall - 11 Feb 2004 01:23 GMT If it's a local variable inside the method, setting it to null when you're finished with it usually doesn't help.
This is because if a GC happens in the middle of the function, the JIT can tell if you are no longer using the object, even if its reference is still in scope, and hence allow the GC to collect the object. I think the only time you could outsmart the jit might be a situation like this:
SomeClass SomeObj = new SomeClass();
for (int i = 0; i < 10; i++) { if (i < 5) { SomeObj.DoSomething(); ... } else { ... } }
In this case, if a GC happens during the loop, at, say i = 6, I doubt the JIT will be able to tell that you won't be using the object any more. So nulling it out may cause the object to be collected slightly earlier. However, you should realise that all this hinges on when the GC collects. If it collects after the loop, then you won't need to null out the reference. So this code only helps if it's quite likely there will be a GC during the loop, but after you are finished with the object.
Gen 0 and 1 must be growing in some way, because objects need to pass there on their way through to Gen 2. How frequent are collections on Gen 0, Gen 1 and Gen 2? I think you should give the CLR Profiler a try, as Eric suggested. You can see what types of objects are taking up all the space in Gen 2, and work out how to reduce the number of allocations of those objects.
I don't know how complex your tracer is, but I found from the nature of mine that a lot of allocations are locals only, hence they die quickly. Most of the objects that my tracer uses are very temporary - just colours, vectors, etc. They're used for a couple of calculations, then they're useless. So they should always die in gen 0. If you're looking to reduce the number of allocations, you might pool simple types like vectors, and hence they will end up in gen 2, but as long as the pool size is small, gen 2 should not grow by much...
Niall
> >Randomly setting to null isn't good, you need to have a specific reason to > >null out a reference. [quoted text clipped - 4 lines] > > - Asbj?rn Ice - 18 Feb 2004 11:51 GMT can someone explain why this code leaks? this is borrowed code from another thread.
public class Test { [STAThread] static void Main(string[] args) { int Iterations; Iterations = args.Length == 1 ? int.Parse(args[0]) : 10000; /* an array of arrays (jagged array) so that I can allocate multiple arrays in the test loop If there is a problem with the GC, this will leak like the Titanic */ byte[][] byteArray = new byte[Iterations][]; byte[] K; Console.WriteLine(Iterations.ToString()); for (int iCount = 0;iCount < Iterations;iCount++) { K = new byte[4000000]; byteArray[iCount] = K; // fill the array -- just in case the elements must be assigned in order to leak FillByteArray(K); // set my reference variable to null K = null;
// this time, don't null the reference to my array. This will cause // it to stick around in memory. A leak, but not due to a GC flaw. //byteArray[iCount] = null;
Console.WriteLine("Iteration: {0:d}",iCount); } } static void FillByteArray(byte[] byteArray) { for (int iLoopCount = 0;iLoopCount < byteArray.Length; iLoopCount++) byteArray[iLoopCount] = 255; } } }
ice
---------------------------------------------------------------------------- ----
> If it's a local variable inside the method, setting it to null when you're > finished with it usually doesn't help. [quoted text clipped - 54 lines] > > > > - Asbj?rn Jon Skeet [C# MVP] - 18 Feb 2004 12:21 GMT > can someone explain why this code leaks? this is borrowed code from another > thread. What makes you think it's leaking? Running on my box, it was relatively slow but made it to iteration 121 before I killed it, with Task Manager showing a working set of only 37M.
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet If replying to the group, please do not mail me too
Ice - 18 Feb 2004 17:19 GMT i just needed confirmation (besides my own) that it didn't leak. i i didn't see it leak but another poster suggested it did.
thanks. ice
> > can someone explain why this code leaks? this is borrowed code from another > > thread. > > What makes you think it's leaking? Running on my box, it was relatively > slow but made it to iteration 121 before I killed it, with Task Manager > showing a working set of only 37M. Jon Skeet [C# MVP] - 09 Feb 2004 10:10 GMT > Yes. They live on the stack, not the heap so they generate no garbage. Value types only live on the stack if they're either the values of local variables or the values of fields in a value type which is on the stack itself.
See http://www.pobox.com/~skeet/csharp/memory.html
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet If replying to the group, please do not mail me too
Lord Crc - 10 Feb 2004 23:58 GMT >For thread-safe pooling, nothing beats the [ThreadStatic] attribute. >It turns any static field into a ThreadStatic field, so each thread has its >own copy. Thanks for the IDisposable example. Reverting to struct's will require some effort because i've relied on the reference nature of the object in several places, so i'll have to review all my code carefully. But i will try it, cause it sounds like it might be worth it.
Thanks for all contributions, i'll be back with more info :) - Asbjørn
Eric Gunnerson [MS] - 09 Feb 2004 22:44 GMT Structs may be a valid approach, especially for vectors.
You should definitely download the clr profiler and look at the behavior of your app:
http://www.microsoft.com/downloads/details.aspx?FamilyId=86CE6052-D7F4-4AEB-9B7A -94635BEEBDDA&displaylang=en
 Signature Eric Gunnerson
Visit the C# product team at http://www.csharp.net Eric's blog is at http://weblogs.asp.net/ericgu/
This posting is provided "AS IS" with no warranties, and confers no rights.
> Hi > [quoted text clipped - 12 lines] > > - Asbj?rn Frank Hileman - 10 Feb 2004 01:46 GMT Eric is right, you never want to use classes for things like vectors, unless you need to box them all the time. I also recommend using structs for 3x2 float matrices, even though they are larger than the recommended size for structs. Not only will your code be faster, but the value semantic is more natural, when performing vector/matrix arithmetic, and overloading operators.
Regards, Frank Hileman Prodige Software Corporation
check out VG.net: www.vgdotnet.com Animated vector graphics system Integrated VS.net graphics editor
> Hi > [quoted text clipped - 12 lines] > > - Asbj?rn Eric Gunnerson [MS] - 11 Feb 2004 00:48 GMT I'd like to expand a bit on the "when to use structs" question.
The framework design guidelines give a guideline on size, but it's not a strict guideline. It's mostly a balance between the overhead of having a heap allocated type versus the cost of passing a value type around. As the value type gets bigger, the cost of passing it around gets larger, and at some point this cost will be bigger than the cost of it being a heap allocated type and passing around the reference.
This point obviously depends upon how much you pass them around, and also the number of instance you create. It also depends on how you store them - if you get into boxing, you generally lose any benefit of structs in the boxing overhead.
I think Frank's advice for 3x2 matrices is on the right track, as they would only be 24 bytes.
Oh, and you can get value semantics for a reference type merely by making it an immutable type (ie not methods or properties that modify state). That will get you value semantics with a reference type, and works fairly well with operator overloading since it doesn't allow any mutators.
Hope that helps.
 Signature Eric Gunnerson
Visit the C# product team at http://www.csharp.net Eric's blog is at http://weblogs.asp.net/ericgu/
This posting is provided "AS IS" with no warranties, and confers no rights.
> Eric is right, you never want to use classes for things like vectors, unless > you need to box them all the time. I also recommend using structs for 3x2 [quoted text clipped - 27 lines] > > > > - Asbj?rn Frank Hileman - 11 Feb 2004 01:16 GMT Eric,
> Oh, and you can get value semantics for a reference type merely by making it > an immutable type (ie not methods or properties that modify state). That > will get you value semantics with a reference type, and works fairly well > with operator overloading since it doesn't allow any mutators. But every operator now allocates a new object on the heap. A complex expression might allocate a bunch of objects, all of which are temporary. Structs are a natural fit for operator overloading, since memory usage and locality of reference are optimal. Actually, I am curious, at what size a float matrix no longer becomes a good candidate for a struct? That is, if you want operator overloading, complex expressions, etc?
- Frank
Eric Gunnerson [MS] - 11 Feb 2004 20:54 GMT > Eric, > [quoted text clipped - 10 lines] > float matrix no longer becomes a good candidate for a struct? That is, if > you want operator overloading, complex expressions, etc? Yes, that would be true. At some point, the overhead of doing that allocation will be lower than the overhead of returning the object by value if it were a value type.
But I agree that in general, structs are more likely to be a good choice, but if you have a variably-sized object, you can't do it with structs.
As I said in my last post, there's no hard-and-fast rule for when it becomes better to use a class over a struct. My best advice is to measure the speed both ways.
 Signature Eric Gunnerson
Visit the C# product team at http://www.csharp.net Eric's blog is at http://weblogs.asp.net/ericgu/
This posting is provided "AS IS" with no warranties, and confers no rights.
Lord Crc - 11 Feb 2004 21:37 GMT >Eric is right, you never want to use classes for things like vectors, unless >you need to box them all the time. I also recommend using structs for 3x2 >float matrices, even though they are larger than the recommended size for >structs. Not only will your code be faster, but the value semantic is more >natural, when performing vector/matrix arithmetic, and overloading >operators. When porting my vector lib i ran a few tests, and found that even when not boxing, using classes for vectors and matrices was noticeably faster. This might be because i use double's and not floats. I will however try to convert the vectors to structs, and see what happens. In any case, i've managed to reduce the GC time to a point where it hovers between 30-40%. I still think there's a lot of room for improvment though :)
- Asbjørn
Lord Crc - 24 Feb 2004 18:28 GMT Thanks for all the suggestions. By implementing various ideas from this thread i've managed to get the GC workload down to a constant 12-15%, which i find much more acceptable.
- Asbjørn
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|