-
Notifications
You must be signed in to change notification settings - Fork 292
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
Reduce AOT binary size #3091
base: main
Are you sure you want to change the base?
Reduce AOT binary size #3091
Conversation
Enable partial trimming of System.Diagnostics and one Hashtable
Is System.Text.Json an unconditional dependency on all platforms already? Can it not deseiralize to a byte[] and thus requires the change from |
System.Text.Json is already required by the SqlJson type, although the Azure libraries also require it. I think it's also built in to newer versions of .NET, so we're not dragging a new dependency in for this. I'm not completely happy with the switch to List either. S.T.J only successfully deserialises a base64 string to a byte array, a JSON array of numbers fails. I tested a couple of different approaches:
A single certificate could be 2-4kb and there could be multiple of these, so I'm trying to avoid any data copies. This (along with the associated memory usage) rules approach 1 out. Approach 2 turned into something pretty similar: at the start of the JSON array we don't know how many elements it has, so we'd have to add to a List (and perform the associated copy in ToArray when returning the final value.) |
Try this. Utf8JsonReader reader = new Utf8JsonReader(bytes);
using (MemoryStream stream = new MemoryStream())
{
bool writing = false;
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonTokenType.StartArray:
writing = true;
break;
case JsonTokenType.EndArray:
writing = false;
break;
case JsonTokenType.Number:
if (reader.TryGetByte(out byte value))
{
stream.WriteByte(value);
}
else
{
throw new Exception();
}
break;
default:
break;
}
}
if (!writing)
{
stjBytes = stream.ToArray();
}
} |
It'd definitely be more idiomatic, but the closing IMO, part of the reason this feels messy is because This would make the child implementation responsible for converting a user-provided attestation URL into a "sensible" value, and for getting the set of signing certificates for a given attestation URL. The base class would just orchestrate those two implementations and cache the result. |
Does this happen often enough and waste enough memory that it's worth doing crazy things to avoid an array allocation of a few KB? As far as I can see the json parser affects a very uncommon scenario around login security so it isn't running frequently (in comparison to tds packet allocations for example) and it isn't something that people will hit multiple times per second (like reading from tds packets). I'd say live with the relatively small and uncommon allocation, save your performance effort for places where it will affect the majority unless someone produces a specific use-case where this path is a problem. |
Thanks, and I agree. The performance comparison sparked a bit of curiosity though, so I benchmarked it.
DataContractJsonSerializer can take 1-2ms to execute, and is called directly after a network request. We can safely stick with JsonSerializer and perform the extra array copy, it'll still be faster and allocate less memory! |
Contributes to #1942.
For win-x64, the pre-PR binary size is 21MB. Sizoscope indicates that these changes trim 0.9MB (~4.25%) from the final binary.
This has two small changes and one which requires explanation:
In both cases, I'm just removing references to types which aren't used frequently. The Azure dependency drags the Process class in, but when that dependency's refactored out there'll be nothing else using it in the core library.
The third change removes the only reference to
DataContractJsonSerializer
, which is used to get the signing certificates for the attestation authority. I've built a standalone server running the Host Guardian Services role, and confirmed that the API endpoint returns the certificate data in text format as a JSON array. Example output from this API endpoint is thus the text string[ 128, 0, 35, 0, ... ]
.JsonSerializer only returns a byte[] when passed a base64-encoded string, so I've returned a List instead.