-
-
- (1291)
-
premium membership
-
Posts
355
|
Avoiding int.ToString() GC garbage
|
I am currently reducing the number of managed bytes allocated to stop GC glitching. By commenting out most of my code I have narrowed a problem. Every time I need an int in string form ie.
| displayText = "Lives Used: " + game.LivesUsed.ToString(); |
it allocates 7KB of managed memory every second (in 240 objects). This is trivial for just one but if I'm doing this in 30 different places then I'm going to get hit with a GC collect every 5 seconds or so *just* from one tiny section of the over all game...
Does anyone know a good way around this? I hope I'm not going to have to store a text version of every int I want to display and update it every time the int changes!
Kick back and chat with fellow Creator's Club members on XNA Chat! Check out one of this year's Dream Build Play finalists: Creed Arena. Fight your opponents in a stadium full of 100,000 spectators! Coming soon to XBOX Live Indie Games.
|
|
-
-
- (615)
-
premium membership
-
Posts
281
|
Re: Avoiding int.ToString() GC garbage
|
One way is to only update the displayText in the set of LivesUsed property for eg.
public int LivesUsed
{
get{return _livesUsed;}
set{
_displayText="Lives Used: "+value.ToString();
_livesUsed=value;
}
That way its only ever set when the property changes instead of 60 times per second. this should save you a lot of garbage
Also for added benefit, add "Lives Used: " to a resource file.
|
|
-
-
- (1291)
-
premium membership
-
Posts
355
|
Re: Avoiding int.ToString() GC garbage
|
conkerjo:
set{
_displayText="Lives Used: "+value.ToString();
_livesUsed=value;
}
I'm hoping to avoid that approach because most of them aren't really set() as propertys like that...
Kick back and chat with fellow Creator's Club members on XNA Chat! Check out one of this year's Dream Build Play finalists: Creed Arena. Fight your opponents in a stadium full of 100,000 spectators! Coming soon to XBOX Live Indie Games.
|
|
-
|
|
Re: Avoiding int.ToString() GC garbage
|
Have you tried using a StringBuilder for the string concatenation? This will not create additional garbage when adding two strings.
|
|
-
-
- (734)
-
premium membership
MVP
-
Posts
898
|
Re: Avoiding int.ToString() GC garbage
|
I'm not sure that's true Melvil ... in fact, I'm pretty sure it's quite the opposite ... when you only have a trivial concatenation to do such as two or three strings, it's better to just use the + method. StringBuilder is useful if you have to build up a large string from an unknown number of iterations, such as when building an html table in asp.net.
The suggestion to only update the displaytext when the value actually changes is the best one ... it ensures that you only allocate when necessary (you can't avoid allocating a new string when you have to change it ... it's a value type). You probably want to refactor your code so that it works with the set technique :-)
Joel Martinez - XNA MVP Blog: http://codecube.netPlay Videos on an XNA Texture: Scurvy MediaXNA Unit Testing: Scurvy Test
|
|
-
|
|
Re: Avoiding int.ToString() GC garbage
|
Joel Martinez:I'm not sure that's true Melvil ... in fact, I'm pretty sure it's quite the opposite ... when you only have a trivial concatenation to do such as two or three strings, it's better to just use the + method.
This of course totally depends on the use case. While it might be overhead for this simple case, it might be faster when he later plans to concatenate more strings. In such cases I always try both methods, see which produce less garbage and go with the better one. Of course, combined with the property method that only updates the string when necessary you can reduce the garbage creation to a minimum, that's why I'd also suggest this solution.
|
|
-
-
- (1291)
-
premium membership
-
Posts
355
|
Re: Avoiding int.ToString() GC garbage
|
Joel Martinez:The suggestion to only update the displaytext when the value actually changes is the best one ... it ensures that you only allocate when necessary (you can't avoid allocating a new string when you have to change it ... it's a value type).
But the fact that it's a value type means that it shouldn't be putting anything on to the GC heap right? That was my understanding anyways...
Kick back and chat with fellow Creator's Club members on XNA Chat! Check out one of this year's Dream Build Play finalists: Creed Arena. Fight your opponents in a stadium full of 100,000 spectators! Coming soon to XBOX Live Indie Games.
|
|
-
-
- (12250)
-
premium membership
MVP
-
Posts
8,670
|
Re: Avoiding int.ToString() GC garbage
|
reedake2:
Joel Martinez:The suggestion to only update the displaytext when the value actually changes is the best one ... it ensures that you only allocate when necessary (you can't avoid allocating a new string when you have to change it ... it's a value type).
But the fact that it's a value type means that it shouldn't be putting anything on to the GC heap right? That was my understanding anyways...
An int is a value type, but a string is not. In this small example:
string text = "Lives: " + lives.ToString();
You actually are allocating three strings, two garbage and one stored in text (which likely goes out of scope at the end of the method and becomes garbage itself). It's always best to only update strings when the data changes. This is a prime reason why you should use properties for things like these. Then you could easily implement the suggestion above and be done. If you don't have it set up that way now, I would change it to set up that way. This tiny operation is allocating 180 strings per second, tons of wasted garbage that is sure to ruin your day on the Xbox 360 if you do it with other variables as well.
|
|
-
-
- (10437)
-
Team XNA
-
Posts
7,916
|
Re: Avoiding int.ToString() GC garbage
|
Strings are reference types. They are a primitive type, and they are immutable (which in many ways makes them behave similar to what people are used to with value types, and means you cannot create new ones without heap allocations) but they are allocated on the heap, not the stack.
XNA Framework Developer -
blog - homepage
|
|
-
-
- (1291)
-
premium membership
-
Posts
355
|
Re: Avoiding int.ToString() GC garbage
|
Nick Gravelyn:This is a prime reason why you should use properties for things like these. Then you could easily implement the suggestion above and be done.
Yeah, it's one of those new things to me that I've not yet got into a habbit of doing. Do you generally have *all* of your public variables as properties? Because that would be a very tedious process for some of my classes...
Kick back and chat with fellow Creator's Club members on XNA Chat! Check out one of this year's Dream Build Play finalists: Creed Arena. Fight your opponents in a stadium full of 100,000 spectators! Coming soon to XBOX Live Indie Games.
|
|
-
-
- (12250)
-
premium membership
MVP
-
Posts
8,670
|
Re: Avoiding int.ToString() GC garbage
|
reedake2:
Nick Gravelyn:This is a prime reason why you should use properties for things like these. Then you could easily implement the suggestion above and be done.
Yeah, it's one of those new things to me that I've not yet got into a habbit of doing. Do you generally have *all* of your public variables as properties? Because that would be a very tedious process for some of my classes...
I don't do it for all values, no. In fact the large majority of my variables are just public fields. I generally make a property when either A) I need to do some sort of data constraint on the input values (such as making sure a value is clamped between two values, or something similar) or B) I will be displaying the data as text. Otherwise I generally stick with public fields.
I also take into account the data type. For things like ints, floats, and strings, there's not much difference between using a public field and property. However for a Vector2 or other struct, there is because you can no longer change a single component on the property since the value returned is actually a copy of the original data. So for those I usually use public fields so I can change a single component of the vector more easily.
|
|
-
-
- (69)
-
premium membership
-
Posts
68
|
Re: Avoiding int.ToString() GC garbage
|
Well, if you're absolutely set on getting rid of those allocations you can write your own string formatting routines that format text directly into a char[ ] and then write your own text rendering system and give it a DrawString( char[ ] textChars, int charsToDraw ) method so you can draw straight out of that array without having to call ToString all over the place. Then you just make one big text buffer at load time and keep reusing it. It's a fair bit of work to go to to get rid of a few allocations, but as you said even a few ToStrings per frame can add up.
That's what I've done in my project. Come to think of it I should probably clean up the code and release it or something...
|
|
-
-
- (1291)
-
premium membership
-
Posts
355
|
Re: Avoiding int.ToString() GC garbage
|
nop:Come to think of it I should probably clean up the code and release it or something...
It'd be great to have a look at! Because as you say it would be a lot of work. I think maybe making the ints properties would be the path of least resistance at the moment.
Kick back and chat with fellow Creator's Club members on XNA Chat! Check out one of this year's Dream Build Play finalists: Creed Arena. Fight your opponents in a stadium full of 100,000 spectators! Coming soon to XBOX Live Indie Games.
|
|
-
-
- (7627)
-
premium membership
MVP
-
Posts
5,871
|
Re: Avoiding int.ToString() GC garbage
|
Strings are garbage. There's not much you can do about it. I do one of three things:
1) update the strings only when the data changes and the new string is needed, and live with the little garbage.
2) keep a cache/hash of number -> string. This is useful if the range of numbers is small (say, 0 .. 100).
3) use an array of char instead, and draw the strings as textured billboards (a la spritefont, but with a char[/length instead of a string)
Jon Watte, Direct3D MVP Tweets, occasionallykW X-port 3ds Max .X exporter kW Animation source code
|
|
-
|
|
Re: Avoiding int.ToString() GC garbage
|
Very well put Jon. I'd advocate options 1 and 2 where possible, but performance issues lead me down the dark, winding road of option 3.
The only way I've managed to avoid horrendous garbage issues when drawing large amounts of text (or small amounts of regularly-changing text; or a moderate amount of text which needs to word wrap in any sort of resizeable "window") is to discard the string data type as a means of holding displayed text, and implement my own font rendering based on efficient text buffers rather along the lines of MemoryStream (though cutting out that particular middle man). On the face of it it might seems ludicrous and backward to do so, but the performance gain has been more than quite noticable. It's a last resort, but it works.
--formerly Genstein
|
|
-
-
- (1291)
-
premium membership
-
Posts
355
|
Re: Avoiding int.ToString() GC garbage
|
sigh, sometime I miss the days of and free() and delete() lol.
Kick back and chat with fellow Creator's Club members on XNA Chat! Check out one of this year's Dream Build Play finalists: Creed Arena. Fight your opponents in a stadium full of 100,000 spectators! Coming soon to XBOX Live Indie Games.
|
|
-
-
- (16684)
-
premium membership
MVP
-
Posts
11,273
|
Re: Avoiding int.ToString() GC garbage
|
Strings are only immutable if you stay in safe code ;-) Its not too hard to work out how the string is laid out and modify it without creating garbage. Of course then you end up implementing your own number formating code but thats much easier than writing your own font rendering IMO
Another idea I got from Frank's talk at GameFest is to use a struct that overlays a byte[ with a string. Than you can modify the byte[ and pass the now modified string into the .Draw method. You don't even need unsafe code to do that. I did a proof of concept but I didn't do any serious coding.
Playtest Kissy Poo - a game for 4 year olds on Xbox and windows The ZBuffer News and information for XNA Follow The Zman on twitter, Email me Please read the forum FAQs - Bug/Feature reporting Don't forget to mark good answers and good playtest feedback when you see it!!!
|
|
-
-
- (69)
-
premium membership
-
Posts
68
|
Re: Avoiding int.ToString() GC garbage
|
The ZMan:Strings are only immutable if you stay in safe code ;-)
Been there. Done that. Couldn't figure out how to set the length... The approach works fairly well, though I ended up formatting into a char[ ], and then unsafely copying into a preallocated string once the final length was known. I was basically at the point of setting up a caching system for all the different-length strings I was using when I decided to just bite the bullet and write my own font renderer (and sneak in a few extra features as well).
The ZMan: Of course then you end up implementing your own
number formating code but thats much easier than writing your own font
rendering IMO
You end up doing both if you go the custom font route. Of course if you do the custom font rendering engine you can do things like rendering a font at multiple sizes and picking the closest one at runtime based on screen resolution and desired vertical height, or inserting formatting codes (just color changes in my case) into strings rather than breaking them up ahead of time and drawing them with multiple Measure/Draw calls (which can get ugly if you switch colors mid-word in oddly kerned fonts).
|
|
-
-
- (16684)
-
premium membership
MVP
-
Posts
11,273
|
Re: Avoiding int.ToString() GC garbage
|
nop:
The ZMan:Strings are only immutable if you stay in safe code ;-)
Been there. Done that. Couldn't figure out how to set the length... The approach works fairly well, though I ended up formatting into a char[ ], and then unsafely copying into a preallocated string once the final length was known. I was basically at the point of setting up a caching system for all the different-length strings I was using when I decided to just bite the bullet and write my own font renderer (and sneak in a few extra features as well).
Off the top of my head the 1st 2 (4?) bytes are the length... but note that only enough memory is there for the initial string. So you need to make a nice long string to 'reserve' the memory. Then you can just fill it with any shorter string and reset the length.
I should tidy up the struct approach - worked pretty well in my PoC...
You are right about handling all the other stuff though. Do you use the standard font XNB files or did you have to write your own exporter too.
Playtest Kissy Poo - a game for 4 year olds on Xbox and windows The ZBuffer News and information for XNA Follow The Zman on twitter, Email me Please read the forum FAQs - Bug/Feature reporting Don't forget to mark good answers and good playtest feedback when you see it!!!
|
|
-
-
- (10437)
-
Team XNA
-
Posts
7,916
|
Re: Avoiding int.ToString() GC garbage
|
In 3.0, you can pass a StringBuilder directly to SpriteBatch.DrawString.
You have to be careful to format into a StringBuilder without causing allocations, but this is possible if you tickle it just right, and easier than implementing your own system entirely from scratch.
XNA Framework Developer -
blog - homepage
|
|
-
-
- (69)
-
premium membership
-
Posts
68
|
Re: Avoiding int.ToString() GC garbage
|
The ZMan:Do you use the standard font XNB files or did you have to write your own exporter too.
I rolled my own. Simple enough task really. Render character into bitmap. Trim away blank space. Pack characters into Alpha8 texture. I used interop to get at the kerning data, and stopped at calling GetCharABCWidthsFloat. Still not sure if I should go all the way and use GetKerningPairs. It hasn't really seemed necessary yet.
Most of the work was on the runtime side. When I create a font I pick a set of point sizes to render it at, and at runtime I specify font size as a fraction of the screen height and it picks the closest matching set of glyphs based on screen resolution and the available font sizes. This has been pretty useful on the XBOX because I can do things like scalling my font sizes in code by some global amount and the layout/render just works without having to have a _pc and _xbox (the same as _pc, but bigger) set of fonts. The final glyph layout stuff is dead simple but the actual line layout (word wrap, center text in rectangle, clip/don't clip, etc) is a loop of pure insanity, written with raw char pointers (since I wanted to be able to use it with either char[ ] or string data) and probably has a few bugs in it.
|
|
-
-
- (7627)
-
premium membership
MVP
-
Posts
5,871
|
Re: Avoiding int.ToString() GC garbage
|
That's a step forward, and perhaps enough. However, I wonder why there's no "DrawString(char[ ], int length, ...)" as that would be the easiest fall-back for many possible cases. If it were me, that's the method I would have implemented first, and then put the strings on top of that...
Jon Watte, Direct3D MVP Tweets, occasionallykW X-port 3ds Max .X exporter kW Animation source code
|
|
-
-
- (462)
-
premium membership
Team XNA
-
Posts
562
|
Re: Avoiding int.ToString() GC garbage
|
As suggested earlier, you could use a dictionary of int->string.
This technique is called memoization: http://en.wikipedia.org/wiki/Memoization
Dictionary healthMemos = new Dictionary();
string GetHealthString(int health)
{
string s;
if (!healthMemos.TryGetValue(health, out s))
{
s = "Health: " + health;
healthMemos[health] = s;
}
return s;
}
Brandon Bloom Software Design Engineer XNA Platform and Tools
|
|
-
|
|
Re: Avoiding int.ToString() GC garbage
|
You can get nasty and unexpected side effects by making strings mutable via pointer manipulation or reflection. Beyond the uncertainty in your own code caused by strings being mutable, string interning may cause you to get unexpected results, and mutable strings are unreliable as keys in a Dictionary<> or similar.
The additional of a StringBuilder overload to DrawString() etc. is great, but still not quite far enough if you're doing a lot of text manipulation. You can't use string.Format() or StringBuilder.AppendFormat() without generating garbage. You can't append individual integers, floats etc. via StringBuilder.Append() or .Insert() without generating garbage. If you want allocation-free string formatting you have to write it yourself, but even appending individual characters to a StringBuilder isn't guaranteed to happen without allocation. Moreover, that's one function call per character, and if you're dealing with large volumes of text at high frame rates, the whole thing can be rather unperformant.
Much as it's unpleasant and extreme, custom text output, formatting and word wrap based on custom memory buffers does avoid these problems. You don't break string itself, and you can get back to strings at any point. However, I still wouldn't advocate it except in extreme cases, as it's something of a pain to create.
--formerly Genstein
|
|
-
-
- (16684)
-
premium membership
MVP
-
Posts
11,273
|
Re: Avoiding int.ToString() GC garbage
|
Ego:
You can get nasty and unexpected side effects by making strings mutable via pointer manipulation or reflection. Beyond the uncertainty in your own code caused by strings being mutable, string interning may cause you to get unexpected results, and mutable strings are unreliable as keys in a Dictionary<> or similar.
Absolutly, my intention was to hide the whole thing inside another class so the implementation was totally hidden and the mutable strings themselves were never exposed. Its not perfect but it would cut down on the major issues....
Playtest Kissy Poo - a game for 4 year olds on Xbox and windows The ZBuffer News and information for XNA Follow The Zman on twitter, Email me Please read the forum FAQs - Bug/Feature reporting Don't forget to mark good answers and good playtest feedback when you see it!!!
|
|
|