How expensive is brush creation?

  • Thread starter Thread starter Tom P.
  • Start date Start date
T

Tom P.

I am doing quite a bit of custom painting and it means I have to
create a lot of brushes (think one for every file system object in a
directory) per paint. How expensive is this? Should I find a way to
create the brushes once, store them in an member variable, and use
them when I need them? Or is creating brushes a throw-away process
that doesn't take a lot of work?

Thanks for the info.

Tom P.
 
I am doing quite a bit of custom painting and it means I have to
create a lot of brushes (think one for every file system object in a
directory) per paint. How expensive is this? Should I find a way to
create the brushes once, store them in an member variable, and use
them when I need them? Or is creating brushes a throw-away process
that doesn't take a lot of work?

Thanks for the info.

Tom P.

It is VERY expensive, not because of the brushes themselves, but
because you're feeding the Garbage Collector with all those brushes on
every Paint event.

You'd be better off using the system Brushes, which are static. Use:

Brushes.Black

instead of:

Brush b = new SolidBrush(Color.Black)

If you frequently use the same colors that are outside the 216
predefined ones, you can do this (in a static class, for instance):

private static Brush orangeyBrush;

public static Brush Orangey
{
get
{
if (orangeyBrush == null)
return (orangeyBrush = new
SolidBrush(Color.FromArgb(223,156,0)));
}
}

This mimics what the framework does internally (lazy initialization).

Michel
 
What's the speed difference when you measure your two proposed  
implementations?  What performance problem are you trying to solve?

If the answers to those questions are "I haven't measured" and/or "no  
particular performance problem", then you are premature in worrying about 
the expense of creating a brush.

Pete

Yes, I assumed "paint" meant "paint event". If it is "paint" as in
"image" or "picturebox" for instance, Pete's right, the improvement
might hamper readability for a negligible gain.

Michel
 
I am doing quite a bit of custom painting and it means I have to
create a lot of brushes (think one for every file system object in a
directory) per paint. How expensive is this? Should I find a way to
create the brushes once, store them in an member variable, and use
them when I need them? Or is creating brushes a throw-away process
that doesn't take a lot of work?

Thanks for the info.

Tom P.

Hi,

I do not know for sure in managed world but in unmanaged a Brush is a
system ersource, so you have to be careful with it.
Why you need that many?
 
What's the speed difference when you measure your two proposed  
implementations?  What performance problem are you trying to solve?

If the answers to those questions are "I haven't measured" and/or "no  
particular performance problem", then you are premature in worrying about 
the expense of creating a brush.

Pete

Nothing I've measured but the effort of redesigning my framework to
accommodate static brushes is no small feat. I am writing a file
manager and I custom paint each listview subitem (using
OnDrawSubItem). I allow custom colors and lineargradient brushes for
each subitem (name, size, and date). I have taken count and I'm
creating over 1000 brushes just to pull up a directory listing of the
root of my dev PC. Now I'm thinking about adding a color option for
Directories vs. Files (and maybe even readonly etc.) and so I'm forced
to wonder if the painting could get expensive.

Tom P.
 
It is VERY expensive, not because of the brushes themselves, but
because you're feeding the Garbage Collector with all those brushes on
every Paint event.

You'd be better off using the system Brushes, which are static. Use:

Brushes.Black

instead of:

Brush b = new SolidBrush(Color.Black)

If you frequently use the same colors that are outside the 216
predefined ones, you can do this (in a static class, for instance):

private static Brush orangeyBrush;

public static Brush Orangey
{
  get
  {
    if (orangeyBrush == null)
       return (orangeyBrush = new
SolidBrush(Color.FromArgb(223,156,0)));
   }

}

This mimics what the framework does internally (lazy initialization).

Michel


I appreciate the help but I've got custom colors and lineargradient
brushes just littering this thing. As well as mouseover brushes and
now I'm kicking around the idea of Directory vs. File brushes and
maybe System or ReadOnly brushes. I've got to find a way to make them
permanent but I've also got the issue of creating the brush within the
correct bounds (what if the user changes the size of the column, I
need to resize the brush accordingly).

I'm gonna have to think this one through. Thanks.

Tom P.
 
Hi,

I do not know for sure in managed world but in unmanaged a Brush is a
system ersource, so you have to be careful with it.
Why you need that many?

You bring up a very good point: what the heck am I doing? I made the
incorrect assumption that this would be a "yes/No" question and I'd be
done with it. My apologies all around.

I am making a file manager that paints each item in custom colors and
gradients at custom angles (lineargradient brushes). The mouseover
event will repaint the item with a custom mouseover brush. With each
subitem getting it's own paint event (OnDrawSubItem) this results in
~1,000 brushes for a simple 10 directory root drive.

Now I'm contemplating the addition of Directory Vs. File brushes and
even System vs. ReadOnly brushes. Hence the question.

Tom P.
 
[...]
What's the speed difference when you measure your two proposed  
implementations?  What performance problem are you trying to solve?

Nothing I've measured but the effort of redesigning my framework to
accommodate static brushes is no small feat.

All the more reason to not do that unless you know for sure you have to.
I am writing a file
manager and I custom paint each listview subitem (using
OnDrawSubItem). I allow custom colors and lineargradient brushes for
each subitem (name, size, and date). I have taken count and I'm
creating over 1000 brushes just to pull up a directory listing of the
root of my dev PC.

1000 brushes?  To display just one directory?

All due respect, I doubt the human brain is capable of comprehending a UI 
presentation with that much variety in it.
Now I'm thinking about adding a color option for
Directories vs. Files (and maybe even readonly etc.) and so I'm forced
to wonder if the painting could get expensive.

Well, it _could_.  Especially if just one directory causes you to create  
1000 different brushes.  But as long as things are performing adequately,  
I don't think you should worry about it.  Even if you do run into  
performance issues, you might reconsider whether it's really necessary to 
use so many different brushes.  That is probably a better solution than 
trying to cache thousands of brushes (as Ignacio points out, the unmanaged  
resource in a brush is relatively scarce and so shouldn't be held on to  
for long periods of time, especially in such large numbers).

Pete

I'm sorry, I must have misspoke. I don't create a thousand different
brushes, I only create about 6 or 9 different brushes... hundreds of
times. Each Item has a NameBrush, a SizeBrush, and a DateBrush for
each circumstance they could be in (unselected, selected, mouseover,
etc.). But they are created over and over and over again for each item
in the filelist. For example, everytime I pass the mouse pointer over
an item I must create three of the unselected brushes to "clear" the
old items look, then I must create three new brushes to paint the new
items mouseover look. That's 6 brushes just to move the mouse from one
item to the next.

Tom P.
 
The GC design is optimized to handle repeated allocation and discarding of  
short-lived objects.  GC is not an issue here at all, though disposal of  
brushes _theoretically_ might be.  I doubt most code would see any issue.



Yes, very true.  If one of the pre-defined brushes is adequate, using that  
is much better than creating and disposing one.

Pete

Still, this :
for (int i = 0; i < 1000000; i++)
{
Brush b = new SolidBrush(Color.Blue);
}
...in release mode takes something like 160 Mb of RAM and lasts
approximately one second. Even optimized, it has to put some pressure
on the GC. But again, when reading Tom's description I incorrectly
assumed "lots" meant "millions of brushes" (I'm working on a rather
complex real-time video thingy atm where every cycle saved is
crucial). You're absolutely right it amounts to nothing in Tom's case.

Michel
 
I appreciate the help but I've got custom colors and lineargradient
brushes just littering this thing. As well as mouseover brushes and
now I'm kicking around the idea of Directory vs. File brushes and
maybe System or ReadOnly brushes. I've got to find a way to make them
permanent but I've also got the issue of creating the brush within the
correct bounds (what if the user changes the size of the column, I
need to resize the brush accordingly).

I'm gonna have to think this one through. Thanks.

Tom P.


Ok, that's fine
but why you need to create XXXXX number of DIFFERENT brushes? I would
assume that you need one per type at most.
 
I'm sorry, I must have misspoke. I don't create a thousand different
brushes, I only create about 6 or 9 different brushes... hundreds of
times. [...]

Right, I finally figured that out.  :)  I might just not have been reading  
carefully enough the first time.

I don't think that caching a dozen or so brushes long-term should be an  
issue, if you decide that's a viable design change.  That's not enough to  
cause any serious problems with resource consumption, I think.

Still, I wouldn't bother until if and when you find an actual performance 
problem.  Just remember to dispose any brushes you create, and you'll  
probably be fine.

Pete

Thanks for the help guys. Pete is right, I should have done this
sooner but I was stuck thinking about something from one direction.
Thanks to Michel for giving me the kick in the head to realize how
simple a short test could be.

Using the following code...

for (long loop = 0; loop > 1000000000;loop++ )
{
Rectangle tempRect = new Rectangle(0,0,100, 18);

tempBrush = new LinearGradientBrush(tempRect,
Color.Blue, Color.Red, 0.01F) ;
tempBrush = null;
}

...takes 0.00013344 secs. (timed using the hardware HiRes API timer).

I think I can spare 0.00013344 secs.

Sorry Pete.

Tom P.
 
Well, that's only about 170 bytes per object.  Doesn't sound like a big 
deal to me.  But, what happens when you fix the code so that it is  
correctly disposing each brush?  At least some of the memory cost is  
coming from the unmanaged resource being allocated.  And more importantly,  
do you have any evidence at all that the GC itself is spending a lot of  
time working on dealing with the allocations?


Even a million short-lived objects shouldn't really be a big deal.  Even  
if you allocate 100,000 objects in quick succession, each allocation  
involves just a single update of a pointer, just as it always does.  
Assuming the objects are no longer reachable the next time garbage  
collection happens, there's no significant overhead to the collection  
process caused by those objects.

If anything, keeping the objects cached statically could increase GC  
overhead, because that adds to the number of objects the GC needs to  
examine as it traverses the graph of reachable objects.

The real issue here is the unmanaged resources, not the garbage collection.

Pete

The only evidence I have is "Effective C#" by Bill Wagner, in which
chapter 16, "Minimize Garbage", deals with the handling of brushes in
paint handlers. It's a coincidence, I read it yesterday, so I thought
I'd jump in. Extracts :

"No matter how you look at it, allocating and destroying a heap-based
object takes more processor time than not allocating and not
destroying a heap-based object. [...] One very common bad practice is
to allocate GDI objects in a Windows Paint handler [...] The static
property Brushes.Black illustrates another technique that you should
use to avoid repeatedly allocating similar objects. Create static
member variables for commonly used instances of the reference types
you need. [...] Every time you need to draw something in your window
using the color Black, you need a black brush. you create and destroy
a huge number of black brushes during the course of a program. [...]
The garbage collector does an efficient job of managing the memory
that your application uses. But remember that creating and destroying
heap objects still takes time. Avoid creating excessive objects; don't
create what you don't need. Also avoid creating multiple objects of
reference types in local functions. Instead, consider promoting local
variables to member variables, or create static objects of the most
common instances of your types."

The first sentence seems to apply quite nicely here.

Oh, and Brush and SolidBrush both implement IDisposable and inherit
from MarshalByRef. So, let me disagree completely with you on that GC
matter...

Michel
 
I'm sorry, I must have misspoke. I don't create a thousand different
brushes, I only create about 6 or 9 different brushes... hundreds of
times. [...]
Right, I finally figured that out.  :)  I might just not have been reading  
carefully enough the first time.
I don't think that caching a dozen or so brushes long-term should be an 
issue, if you decide that's a viable design change.  That's not enough to  
cause any serious problems with resource consumption, I think.
Still, I wouldn't bother until if and when you find an actual performance  
problem.  Just remember to dispose any brushes you create, and you'll 
probably be fine.

Thanks for the help guys. Pete is right, I should have done this
sooner but I was stuck thinking about something from one direction.
Thanks to Michel for giving me the kick in the head to realize how
simple a short test could be.

Using the following code...

for (long loop = 0; loop > 1000000000;loop++ )
            {
                Rectangle tempRect = new Rectangle(0,0,100, 18);

                tempBrush = new LinearGradientBrush(tempRect,
Color.Blue, Color.Red, 0.01F) ;
                tempBrush = null;
            }

...takes 0.00013344 secs. (timed using the hardware HiRes API timer).

I think I can spare 0.00013344 secs.

Sorry Pete.

Tom P.- Masquer le texte des messages précédents -

- Afficher le texte des messages précédents -

Er... Did you copy/paste from Visual Studio ? Because if you did,
maybe you should try again, changing the ">" into a "<"... and remove
three zeros in your loop, otherwise you'll still be waiting
tomorrow... Prepare to be surprised !

Michel
 
Well, I don't know anything about that book.  But you should be careful 
about believing everything you read, as well as about what you apply it to.

Maybe you should read it before advising anything, or assuming it's
some sort of "dummies book"? Also, exactly what makes you think I'm
the credulous type, or have less experience than you have, or am less
cautious in the way I acquire new information?
It's trivially true (and thus uninteresting) that it's faster to not do  
something than to do it.  But that doesn't mean that the act of doing it  
is a significant cost that warrants complicating the code just so you can 
avoid doing it.

I was speaking about this particular case, not in general. The time
saved should be significant enough to warrant *simplifying* the code.
[...]
Oh, and Brush and SolidBrush both implement IDisposable and inherit
from MarshalByRef. So, let me disagree completely with you on that GC
matter...

I have no idea what you mean.  Even if I hadn't specifically pointed out  
that the object needs to be disposed (which I in fact did), what's that  
got to do with whether or not the memory management overhead is  
significant?  What's your point?

This is a reference type (harder on the GC than value types), which
implements IDisposable (two method calls, including
GCSuppressFinalize), and marshalls its data accross boundaries (which
is, sorry to be trivial, longer than not marshalling data accross
boundaries).

In the end, exactly what's your problem with me saying that allocating
unnecessary objects is unnecessary and, in this case, expensive? Could
you explain in simple terms? Did it rub you the wrong way?

Michel
 
What makes you think I think any of that?

The sentence you snipped : "you should be careful about believing
everything you read, as well as about what you apply it to".
I'm simply pointing out that the quote you included doesn't change  
anything I wrote.



Why?  What makes you think it "should be significant enough"?  Did you  
measure it in the real-world context?

Yep, see the loop example above.
If you have specific facts, please share them.  If you don't, then you  
have no basis for arguing in favor of complicating the design.  And yes,  
it's a complication to change from simply allocating objects as needed to 
caching them over time.  I don't see any justification for your claim that  
the change could be described as "*simplifying* the code".

I don't see any justification for your claim that declaring a static
member variable is more complicated than allocating several instance
ones.
[...]
Oh, and Brush and SolidBrush both implement IDisposable and inherit
from MarshalByRef. So, let me disagree completely with you on that GC
matter...
I have no idea what you mean.  Even if I hadn't specifically pointed 
out  
that the object needs to be disposed (which I in fact did), what's that  
 
got to do with whether or not the memory management overhead is  
significant?  What's your point?
This is a reference type (harder on the GC than value types),

Huh?  Value types aren't managed by the GC at all.  That parenthetical  
statement makes no sense at all.  At the very most, it's trivially true 
(and so again, uninteresting).

Lol ! So it's nonsensical BUT trivially true. Reminds me of this :
http://thedailywtf.com/Articles/What_Is_Truth_0x3f_.aspx
So what?  None of that increases the _garbage collector's_ workload.  The  
object itself has more overhead, yes.  But that wasn't the point.

Er... I didn't say those two points increased the GC workload, *you*
assumed I did (reread the sentence); I was talking about the
allocation/deallocation overhead, and THAT was the point of the OP.
You are the one who seems to be rubbed the wrong way.  I am simply  
pointing out that there's no evidence at all that Tom's original "allocate  
as needed" design is in need of changing (other than using the pre-defined  
brushes where possible, which I already agree with you about).

Argumentum ad ignorantiam : http://en.wikipedia.org/wiki/Argument_from_ignorance

You do a lot of those !
If he's measured the cost and found his application is spending too much  
time drawing these items, then yes...there's some value in exploring other  
designs that don't allocate these objects over and over again.  But a)  
even if he found some performance cost that needed fixing, it's  
_extremely_ unlikely that it would be related to the GC (i.e. you'd find  
the same cost just creating GDI brushes in unmanaged code), and b) so far 
there's been no demonstration of such a performance cost in need of fixing.

a) Some random writer, apparently a professionnal programmer, seems to
strongly disagree with you, and has even written a chapter about that
in his book; and I should add that "unlikely that it would be related
to the GC" is pretty silly (the GC workload is directly related to the
number of objects created/destroyed, AND DON'T PICK ON THAT ONE, I
mean objects as in reference types), and b) yes there is, try my loop,
and his loop once you correct the typo.

In short, you're trying to establish that he shouldn't try to improve
his program, based on several assumptions on your part : he's using
only a few brushes, the program won't run for long, the paint event
won't be called often, etc.

So, again : yes, Tom, you should go the extra mile to make your
brushes static for a lot of reasons : your code will be clearer,
faster, will use less memory, will spend less time in the GC, and you
will learn good habits in the process.

And, Pete, people who say "All the more reason to not do that unless
you know for sure you have to" shouldn't be giving tips to people who
write, create, invent or try to discover or improve. Programmers ?

Michel
 
On Aug 5, 2:09 pm, "Peter Duniho" <[email protected]>
wrote:
I'm sorry, I must have misspoke. I don't create a thousand different
brushes, I only create about 6 or 9 different brushes... hundreds of
times. [...]
Right, I finally figured that out.  :)  I might just not have been reading  
carefully enough the first time.
I don't think that caching a dozen or so brushes long-term should be an  
issue, if you decide that's a viable design change.  That's not enough to  
cause any serious problems with resource consumption, I think.
Still, I wouldn't bother until if and when you find an actual performance  
problem.  Just remember to dispose any brushes you create, and you'll  
probably be fine.
Pete
Thanks for the help guys. Pete is right, I should have done this
sooner but I was stuck thinking about something from one direction.
Thanks to Michel for giving me the kick in the head to realize how
simple a short test could be.
Using the following code...
for (long loop = 0; loop > 1000000000;loop++ )
            {
                Rectangle tempRect = new Rectangle(0,0,100, 18);
                tempBrush = new LinearGradientBrush(tempRect,
Color.Blue, Color.Red, 0.01F) ;
                tempBrush = null;
            }
...takes 0.00013344 secs. (timed using the hardware HiRes API timer).
I think I can spare 0.00013344 secs.
Sorry Pete.
Tom P.- Masquer le texte des messages précédents -
- Afficher le texte des messages précédents -

Er... Did you copy/paste from Visual Studio ? Because if you did,
maybe you should try again, changing the ">" into a "<"... and remove
three zeros in your loop, otherwise you'll still be waiting
tomorrow... Prepare to be surprised !

Michel

Nope, taken right out of the VS-IDE and pasted in here. If you want
the entire code I suppose I could paste it here as well.

the HiRes timer uses...
[DllImport("kernel32.dll")]
internal extern static UInt32 QueryPerformanceCounter(ref Int64
lpPerformanceCount);

... to get the timings.

Tom P.
 
On Aug 5, 2:09 pm, "Peter Duniho" <[email protected]>
wrote:
I'm sorry, I must have misspoke. I don't create a thousand different
brushes, I only create about 6 or 9 different brushes... hundredsof
times. [...]
Right, I finally figured that out.  :)  I might just not have been reading  
carefully enough the first time.
I don't think that caching a dozen or so brushes long-term should be an  
issue, if you decide that's a viable design change.  That's not enough to  
cause any serious problems with resource consumption, I think.
Still, I wouldn't bother until if and when you find an actual performance  
problem.  Just remember to dispose any brushes you create, and you'll  
probably be fine.
Pete
Thanks for the help guys. Pete is right, I should have done this
sooner but I was stuck thinking about something from one direction.
Thanks to Michel for giving me the kick in the head to realize how
simple a short test could be.
Using the following code...
for (long loop = 0; loop > 1000000000;loop++ )
            {
                Rectangle tempRect = new Rectangle(0,0,100, 18);
                tempBrush = new LinearGradientBrush(tempRect,
Color.Blue, Color.Red, 0.01F) ;
                tempBrush = null;
            }
...takes 0.00013344 secs. (timed using the hardware HiRes API timer).
I think I can spare 0.00013344 secs.
Sorry Pete.
Tom P.- Masquer le texte des messages précédents -
- Afficher le texte des messages précédents -
Er... Did you copy/paste from Visual Studio ? Because if you did,
maybe you should try again, changing the ">" into a "<"... and remove
three zeros in your loop, otherwise you'll still be waiting
tomorrow... Prepare to be surprised !

Nope, taken right out of the VS-IDE and pasted in here. If you want
the entire code I suppose I could paste it here as well.

the HiRes timer uses...
[DllImport("kernel32.dll")]
internal extern static UInt32 QueryPerformanceCounter(ref Int64
lpPerformanceCount);

... to get the timings.

Tom P.- Masquer le texte des messages précédents -

- Afficher le texte des messages précédents -

Yes, I know, I'm trying to tell you that you did a typo in your
loop !! It executes only once ! Come on, allocating one billion
objects can't take a millisecond ! Change :

for (long loop = 0; loop > 1000000000;loop++ )

to :

for (long loop = 0; loop < 1000000000;loop++ )

...and time again, you'll see what I mean. And maybe that'll change
your mind...

Michel
 
On Aug 5, 2:57 pm, (e-mail address removed) wrote:
On Aug 5, 2:09 pm, "Peter Duniho" <[email protected]>
wrote:
I'm sorry, I must have misspoke. I don't create a thousand different
brushes, I only create about 6 or 9 different brushes... hundreds of
times. [...]
Right, I finally figured that out.  :)  I might just not havebeen reading  
carefully enough the first time.
I don't think that caching a dozen or so brushes long-term shouldbe an  
issue, if you decide that's a viable design change.  That's notenough to  
cause any serious problems with resource consumption, I think.
Still, I wouldn't bother until if and when you find an actual performance  
problem.  Just remember to dispose any brushes you create, and you'll  
probably be fine.
Pete
Thanks for the help guys. Pete is right, I should have done this
sooner but I was stuck thinking about something from one direction.
Thanks to Michel for giving me the kick in the head to realize how
simple a short test could be.
Using the following code...
for (long loop = 0; loop > 1000000000;loop++ )
            {
                Rectangle tempRect = new Rectangle(0,0,100, 18);
                tempBrush = new LinearGradientBrush(tempRect,
Color.Blue, Color.Red, 0.01F) ;
                tempBrush = null;
            }
...takes 0.00013344 secs. (timed using the hardware HiRes API timer).
I think I can spare 0.00013344 secs.
Sorry Pete.
Tom P.- Masquer le texte des messages précédents -
- Afficher le texte des messages précédents -
Er... Did you copy/paste from Visual Studio ? Because if you did,
maybe you should try again, changing the ">" into a "<"... and remove
three zeros in your loop, otherwise you'll still be waiting
tomorrow... Prepare to be surprised !
Michel
Nope, taken right out of the VS-IDE and pasted in here. If you want
the entire code I suppose I could paste it here as well.
the HiRes timer uses...
[DllImport("kernel32.dll")]
internal extern static UInt32 QueryPerformanceCounter(ref Int64
lpPerformanceCount);
... to get the timings.
Tom P.- Masquer le texte des messages précédents -
- Afficher le texte des messages précédents -

Yes, I know, I'm trying to tell you that you did a typo in your
loop !! It executes only once ! Come on, allocating one billion
objects can't take a millisecond ! Change :

for (long loop = 0; loop > 1000000000;loop++ )

to :

for (long loop = 0; loop < 1000000000;loop++ )

...and time again, you'll see what I mean. And maybe that'll change
your mind...

Michel

Don't tell my PM, please.

THIS loop...
tempTimer.Start();
for (int loop = 0; loop < 1000000; loop++)
{
tempRectangle = new Rectangle(0, 0, 100, 18);
tempBrush = new LinearGradientBrush(tempRectangle,
Color.Blue, Color.Red, 0.01F);

tempBrush.Dispose();
}
tempTimer.Stop();

...takes about 5.5 sec.
A mere 1000 brushes takes about 0.005 sec.

Which brings me to another question... Which is best, object.Dispose()
or Object = null?

Object = null only takes 4.3 secs but I can never keep straight which
one calls the GC twice or under what circumstances. (I can't keep the
stupid "for" loop straight, what are the odds I get this right?)

Tom P.
 
The sentence you snipped : "you should be careful about believing
everything you read, as well as about what you apply it to".

Do you disagree with the sentence?  If not, I don't see what your problem  
is.  The quote you provided is nothing as compared to a real-world test..
[...]
I was speaking about this particular case, not in general. The time
saved should be significant enough to warrant *simplifying* the code..
Why?  What makes you think it "should be significant enough"?  Didyou  
measure it in the real-world context?
Yep, see the loop example above.

The loop in which you forgot to dispose the brush you allocated, forcing  
the GC to have to do finalization on them (eventually)?  And yet in which  
you still were able to create 1,000,000 in a second?  At 1000 brushes per  
frame (per Tom's comments), that's 1000 frames per second, about ten times  
faster than any typical computer display can handle.  Even if Tom upped 
his brush count to 10,000 Brush instances per frame, he could still draw  
100 frames in a second.  That's plenty fast for a 1st-person-shooter,  
never mind an application displaying information about a file system.

And of course, all that assumes that brush creation accounts for the vast 
majority of time spent drawing, which is unlikely.  So in practice it's 
even more absurd to worry about the performance of the Brush  
creation/destruction than demonstrated by your own analysis.

Again, where's the performance problem here?
[...]
I don't see any justification for your claim that declaring a static
member variable is more complicated than allocating several instance
ones.

I never suggesting "allocating [sic]" instance variables.  My impression  
of Tom's design is that he's allocating brushes in a method, storing them 
in a local variable just long enough for that method to execute.  My  
suggestion was that there's nothing fundamentally wrong with that design. 
The only reference to "static" I made was in the context of an object that  
remains allocated (i.e. "statically") rather than being recreated with  
each use (i.e. "dynamically").

My use of the word has nothing to do with static or instance members of  
classes.  A long-term cache of brushes could in fact be implemented either  
way (i.e. stored in static or instance members), but that's not what I'm  
talking about at all.
[...]
This is a reference type (harder on the GC than value types),
Huh?  Value types aren't managed by the GC at all.  That parenthetical  
statement makes no sense at all.  At the very most, it's trivially true  
 
(and so again, uninteresting).
Lol ! So it's nonsensical BUT trivially true.

"At the very most", it's trivially true.  In practice, since the GC  
doesn't manage value types at all, it's nonsensical to compare the  
difficulty of the GC in managing reference types versus value types.

I'm sorry you don't comprehend the subtleties in my statement.  But the 
fact remains, it doesn't make sense to discuss how "hard" management of  
value types is on the garbage collector.  The GC just doesn't manage them  
at all.

Yes, in a sense that means it's no work at all for the GC.  But only in 
the same sense that it's no work at all for you to mow my lawn.  Do you 
really think it makes sense to talk about how much harder it is for you to  
mow your own lawn than mine, given that you don't do the latter at all?
[...]
So what?  None of that increases the _garbage collector's_ workload. 
The  
object itself has more overhead, yes.  But that wasn't the point.
Er... I didn't say those two points increased the GC workload, *you*
assumed I did (reread the sentence); I was talking about the
allocation/deallocation overhead, and THAT was the point of the OP.

This whole sub-thread started here:

The GC design is optimized to handle repeated allocation and discarding 
of short-lived objects.  GC is not an issue here at all, though disposal  
of brushes _theoretically_ might be.  I doubt most code would see any 
issue.

As you can see, my only contribution here has always been about the GC  
overhead.  Nothing more.  If you choose to infer a broader discussion,  
that's your mistake.  If you choose to inject comments irrelevant to the  
original discussion, don't expect me to find them relevant.



Uh.  You should probably read that Wikipedia article more closely.  It has  
nothing to do with this discussion.  From a purely practical matter, it 
makes no sense to optimize code until you know it's a piece of code in  
need of optimizing.  This is completely different from "argumentum ad  
ignorantiam".
You do a lot of those !

Ad hominem will get you nowhere.
[...]
a) Some random writer, apparently a professionnal programmer, seems to
strongly disagree with you,

Well, technically I disagree with him.  But whatever.
and has even written a chapter about that
in his book;

The fact that it's in print doesn't mean it's true, never mind applicable 
to the implementation at hand.
and I should add that "unlikely that it would be related
to the GC" is pretty silly

I didn't write that.  I wrote "_extremely_ unlikely...".
(the GC workload is directly related to the
number of objects created/destroyed, AND DON'T PICK ON THAT ONE,

Why not?  It's patently false.  The workload is not _directly_ related to  
the number of objects created/destroyed.  In fact, short-lived objects  
incur a MUCH lower per-object cost to the GC than  
long-lived-but-still-transient objects (that is, objects that age out of  
generation 0).

There is not a direct correlation.  There's an indirect relationship, but  
no direct correlation.

More importantly, whether there's a direct correlation or not, it's the  
actual _cost_ that's at issue here.  And even if the cost was directly  
proportional to the number of objects, the per-object cost is so tiny  
relative to what else is going on in the code, the GC overhead is  
"_extremely_ unlikely" to be significant.
I mean objects as in reference types), and b) yes there is, try my loop,
and his loop once you correct the typo.

Your loop still has a bug in it, but even so it fails to prove that the GC  
cost is significant.  It's much more likely that the cost of allocating 
and disposing the _unmanaged_ resources overwhelms whatever insignificant 
cost the GC incurs.

You can do the test yourself.  Time these two loops and compare:

             for (long i = 0; i < 1000000; i++)
             {
                 object obj = new object();
             }

and:

             for (long i = 0; i < 1000000; i++)
             {
                 using (Brush brush = new SolidBrush(Color.Blue))
                 {
                 }
             }

On my computer, the second completes in about 1.1 seconds (similar to your  
results).  The first completes in about 0.02 seconds.

In other words, in a loop that is _only_ creating and destroying a Brush, 
the GC-related activity incurs only 2% of the total cost of the loop.  
That's before doing _anything_ else interesting in the loop.

No, it's pretty clear: when dealing with the Brush class, most of the cost  
incurred managing the object has nothing to do with the GC.  Inasmuch as  
there's any expense at all, it's the object itself that is costly (and as 
your own test shows, it's not even that costly, when you compare it to  
what is considered a reasonable frame rate from the user's point of view)..

I'm sorry you took such offense at my point of correction to your original  
statement, and I'm especially sorry that it's taking so much effort to  
explain myself.  But facts are facts.
In short, you're trying to establish that he shouldn't try to improve
his program, based on several assumptions on your part : he's using
only a few brushes, the program won't run for long, the paint event
won't be called often, etc.

No.  I _am_ establishing that he shouldn't bother _changing_ his program  
until he has some specific indication that doing so would be an  
_improvement_.  And I've made none of the assumptions that you claim I  
have (in fact, quite the opposite).

You are engaging in tautology, having assumed that the change would be an 
improvement before you have actually proven it to be so.


So, again : yes, Tom, you should go the extra mile to make your
brushes static for a lot of reasons : your code will be

...

plus de détails »- Masquer le texte des messages précédents -

- Afficher le texte des messages précédents -

Well, let's agree to disagree completely on all matters : I maintain
that GC has a lot of work to do because the OP's NOT disposing of the
brushes, which was obvious from his first post, and which is confirmed
by his last. In this case, the Dispose method IS called by the GC, or
called by whatever and then fed to the GC, which for all purposes and
intents, is pretty much the same. You're just nitpicking on details
and in the end, you're trying to establish that taking up 0.1% or a
processor's horsepower ONLY for brushes management (when it's
completely unnecessary) is acceptable ? But then, why take 0.1% when
you can easily - and with a better design - take 0.0001% ?

You forgot the original question, and your whole argument is based on
"if it works, don't fix it". Well, we should have remained with just
fire and the wheel, which worked brilliantly. The rest is just
optimization and I suspect if you were to say anything, we wouldn't
have gone further.

I'm out, I have designs to improve and microseconds to save !

Michel
 
Back
Top