![]() | |
![]() |
| | Thread Tools | Search this Thread | Display Modes |
#1
| |||
| |||
|
#2
| ||||
| ||||
|
|
A colleague put me onto string.Intern, but this won't help as by the time I'm calling that method, I've already allocated the string. |
|
Note that these strings are very short lived. After deserialisation, they will be processed and (for the most part) garbage collected before they get promoted to generation 1. This happens several thousand times a second under normal conditions, giving the garbage collector (what I assume is) a lot of work. I'm seeing the classic sawtooth pattern in a heap timeline but with very high frequency. |
|
I'd like to know whether this is a situation in which I can improve performance. I can envisage some sort of structure (perhaps a Trie) that hones in on the stored string as we progress through the byte sequence. However this structure cannot be pre-populated (the strings will be determined at runtime). |
|
The big question is: do the benefits of reducing string allocation justify the overhead in finding a stored string? This, no doubt, depends upon the implementation. |
#3
| ||||||
| ||||||
|
|
Also, there is no way to "un-Intern" - an interned string lasts until the process terminates. |
|
Aside from the frequency of garbage-collection, this is not a bad pattern: Short-lived strings do not need to be relocated, and gc costs will be low. |
|
One consequence of any scheme that caches strings - whether a trie, a hashtable (Dictionary<string,bool>), or string interning - is that each string (even the non-repeats) lasts a long time, and needs to be promoted and relocated from Gen 0 to Gen 1 to Gen 2. |
|
Seems to me you have three broad ranges, here: * A trie scheme (or the equivalent) would immortalize all strings, but would reduce allocations, and hence the number of garbage collections. Fewer, more expensive garbage collections; higher memory consumption. * Any interning scheme immortalizes strings, without reducing allocations. No real impact on gc; just a bit slower over-all, and higher memory consumption. * The current scheme has many, comparatively cheap garbage collections, and doesn't have high fixed memory use, |
|
I'd guess that whether 1 or 3 is faster is depends on the cost of each gc - how many roots you have, and how many of them change from gc to gc. If most gc-s are triggered by the creation of temp strings that are already garbage, I'd expect each gc to be pretty cheap. |
|
That is, my best GUESS is that a trie may save you a few percentage points of runtime costs (maybe 10% to as much as 25%) at the cost of several hours work and higher memory use. |
#4
| |||
| |||
|
|
Right now, I'm using BinaryReader.ReadString() which gives the correct result, however it creates a new instance of System.String for each byte sequence. What I'd really like is to detect the repeated byte sequence, and return a reference to an existing deserialised version. |
#5
| |||
| |||
|
#6
| |||
| |||
|
#7
| ||||
| ||||
|
|
the process terminates. I was under the impression that a reference count is maintained for interned strings, and that this applies across AppDomains. This allows strings to be uninterned. |
|
What might be useful is to understand some details of how interning performs it's string lookup based upon value. Surely that's a similar problem to the one I'm facing. |
|
I don't mind promoting the most common strings, as this is a long-lived process and memory isn't such an issue. However as this list needs to be built at runtime there is likely to be a list of candidate strings used to ensure the lookup structure contains only those which are most common. These will be around long enough to make it out of generation 0 I expect, and a potential performance problem. I envisage a candidate list holding strings awaiting inclusion in the lookup structure. Whenever a string does not reside in the lookup, it's added to the candidate list -- a short list of the most recent strings, sorted by the number of times they're seen. Items get knocked off the list (and gc'd) over time if they don't appear often enough. Those which work up the list are added to the lookup structure. |
|
Why do the number of roots impact the time taken for GC? By roots I take it you mean objects referenced directly from any level of any thread's stack. |
![]() |
| Thread Tools | Search this Thread |
| Display Modes | |
| |