-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add StringBuilder pooling in NewtonsoftJsonSerializer #4929
Add StringBuilder pooling in NewtonsoftJsonSerializer #4929
Conversation
…er to goof up later by making static, fix hocon reading
008c0cc
to
7c72de5
Compare
json { | ||
|
||
# Used to set whether to use stringbuilders from a pool | ||
# In memory constrained conditions (i.e. IOT) | ||
# You may wish to turn this off | ||
use-pooled-string-builder = true | ||
|
||
# The starting size of stringbuilders created in pool | ||
# if use-pooled-string-builder is true. | ||
# You may wish to adjust this number, | ||
# For example if you are confident your messages are smaller or larger | ||
pooled-string-builder-minsize = 2048b | ||
|
||
# The maximum retained size of a pooled stringbuilder | ||
# if use-pooled-string-builder is true. | ||
# You may wish to turn this number up if your messages are larger | ||
# But do keep in mind that around 85K you'll wind up | ||
# on the Large Object Heap (which may not be a bad thing...) | ||
pooled-string-builder-maxsize = 32768b | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to make sure to document the settings here. I would love some feedback on the defaults, I'm not sure if maxsize is too low or not. I think MinSize is fair here; I tried 1024B and the benchmarks came out about the same.
Settings = CreateInternalSettings(system, settings,this); | ||
var settingsNoFormat = CreateInternalSettings(system, settings,this); | ||
settingsNoFormat.Formatting = Formatting.None; | ||
_serializer = JsonSerializer.Create(Settings); | ||
_formattingNoneSerializer = JsonSerializer.Create(settingsNoFormat); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so, the idea here is to make sure that even if the user wants formatting for -their- serializer, we have our own with no formatting, so that we avoid making a new serializer every time.
private static JsonSerializerSettings CreateInternalSettings( | ||
ExtendedActorSystem system, NewtonSoftJsonSerializerSettings settings, NewtonSoftJsonSerializer surrogateParent) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made this static because it makes it harder for people to accidentally miswire things (i.e. by accidentally applying to Settings
on the instance when each created JsonSerializerSettings
needs to have it's inits run.)
Looks very promising @to11mtm |
Well, this tripped a bunch of specs, but different depending on environment... I'm guessing the pooling initialization is in some cases causing some of these specs to time out. |
@to11mtm no, these look like some of the usual racy specs to me |
Ready to give this a real review when you say so @to11mtm |
I think this is GTG, although I wonder if we can/should change the max-pool-size. The challenge is that payloads over 32k will wind up invalidating the cache, but larger sizes can wind up on the LOH because of the way StringBuilder.Clear() works. This really isn't a bad bad thing TBH, but worth deciding whether we want to do that. |
I came across this while researching serialization issues of my own and arriving at I'm testing out the following changes: public override byte[] ToBinary(object obj)
{
using (var stream = new MemoryStream())
using (var writer = new StreamWriter(stream))
{
_serializer.Serialize(writer, obj);
return stream.ToArray();
}
}
// snip
public override object FromBinary(byte[] bytes, Type type)
{
using (var stream = new MemoryStream(bytes))
using (var r = new StreamReader(stream))
using (var reader = new JsonTextReader(r))
{
object res = _serializer.Deserialize(reader);
return TranslateSurrogate(res, this, type);
}
} I was planning to submit a PR with this change, but I see that obviously it would clash with this work. Is there some other benefit to using this |
IMHO, send this PR in anyway so we can benchmark it. Would love to see what kind of difference this makes. I don't think neither @to11mtm nor myself have a strong opinion about what approach we go with - we just care that the approach is compatible with the wire / API and is faster than what we have today. |
@Aaronontheweb submitted #5376 |
... I honestly have no idea why we haven't merged this already though. Must have been me getting distracted with other stuff. |
Excellent, thank you @thzinc - I'm about to publish Akka.NET v1.4.28 but I will take a look at your PR! |
Or you want me to go away. That said, Merge this and I'll do the Threadpool deed. :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
@@ -15,6 +15,7 @@ | |||
</ItemGroup> | |||
|
|||
<ItemGroup> | |||
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="5.0.5" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At some point this will get folded into the core runtime when we move on from .NET Standard 2.0, IIRC
{ | ||
ser.Serialize(jw, obj); | ||
} | ||
return Encoding.UTF8.GetBytes(tw.ToString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
This PR Gives us a boost on
NewtonsoftJsonSerializer
's Serialization Throughput.By lowering memory pressure, we see a ~5% gain under a single thread, and >25% gain under a little bit of Task pressure:
Edit- after Fixes, Some tricky thread things going on in
JsonSerializer
...: