Skip to content
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 well-known OIDs to System.Formats.Asn1 #75485

Merged
merged 2 commits into from Sep 13, 2022
Merged

Conversation

bartonjs
Copy link
Member

@bartonjs bartonjs commented Sep 12, 2022

This adds most of the OIDs that are read or written during the execution of any of the crypto tests or SslStream tests as a well-known pattern of bytes and a pre-allocated/ const/interned string.

Reading a well-known OID drops from ~300ns to ~60ns and saves on allocating the output string, and possibly an int[] in BigInteger for large-RID OIDs, while increasing the cost for a less well-known OID by about 40ns.

Writing a well-known OID drops from ~1200ns to ~90ns, and increases the cost for a less well-known OID by only about 10ns.

For purposes of this initial list, an oid was well-known if (more or less):

  • It was the output of ReadObjectIdentifier more than twice, and
  • It was not a facetious/test value (e.g. 0.0), and
  • It was not a vendor-specific Certificate Practices Statement; or
  • It was an obviously linked OID potentially skewed out by our test data
    • e.g. EcdsaWithSha512 would be automatically included by closeness to EcdsaWithSha256.

This adds most of the OIDs that are read or written during the execution of any of the
crypto tests or SslStream tests as a well-known pattern of bytes and a pre-allocated/
const/interned string.

Reading a well-known OID drops from ~300ns to ~60ns and saves on allocating the output
string, and possibly an `int[]` in BigInteger for large-RID OIDs, while increasing the
cost for a less well-known OID by about 40ns.

Writing a well-known OID drops from ~1000ms to ~90ms, and increases the cost for a less
well-known OID by only about 10ms.

For purposes of this initial list, an oid was well-known if (more or less):
* It was the output of ReadObjectIdentifier more than twice, and
* It was not a facetious/test value (e.g. 0.0), and
* It was not a vendor-specific Certificate Practices Statement; or
* It was an obviously linked OID potentially skewed out by our test data
  * e.g. EcdsaWithSha512 would be automatically included by closeness to EcdsaWithSha256.
@bartonjs bartonjs added this to the 8.0.0 milestone Sep 12, 2022
@bartonjs bartonjs self-assigned this Sep 12, 2022
@ghost
Copy link

ghost commented Sep 12, 2022

Tagging subscribers to this area: @dotnet/area-system-formats-asn1, @vcsjones
See info in area-owners.md if you want to be subscribed.

Issue Details

This adds most of the OIDs that are read or written during the execution of any of the crypto tests or SslStream tests as a well-known pattern of bytes and a pre-allocated/ const/interned string.

Reading a well-known OID drops from ~300ns to ~60ns and saves on allocating the output string, and possibly an int[] in BigInteger for large-RID OIDs, while increasing the cost for a less well-known OID by about 40ns.

Writing a well-known OID drops from ~1000ms to ~90ms, and increases the cost for a less well-known OID by only about 10ms.

For purposes of this initial list, an oid was well-known if (more or less):

  • It was the output of ReadObjectIdentifier more than twice, and
  • It was not a facetious/test value (e.g. 0.0), and
  • It was not a vendor-specific Certificate Practices Statement; or
  • It was an obviously linked OID potentially skewed out by our test data
    • e.g. EcdsaWithSha512 would be automatically included by closeness to EcdsaWithSha256.
Author: bartonjs
Assignees: bartonjs
Labels:

tenet-performance, area-System.Formats.Asn1

Milestone: 8.0.0

@bartonjs
Copy link
Member Author

Low-level benchmarks:

Method Runtime Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
WriteEcdsaWithSha384Oid PR 80.90 ns 0.342 ns 0.320 ns 0.08 0.00 - - NA
WriteEcdsaWithSha384Oid .NET 7p7 992.62 ns 4.597 ns 4.075 ns 1.00 0.00 - - NA
WritePkcs9X509Oid PR 85.89 ns 0.928 ns 0.823 ns 0.07 0.00 - - NA
WritePkcs9X509Oid .NET 7p7 1,224.82 ns 8.718 ns 8.155 ns 1.00 0.00 - - NA
WritePkcs9SdsiOid PR 1,226.37 ns 8.140 ns 7.216 ns 1.01 0.01 - - NA
WritePkcs9SdsiOid .NET 7p7 1,212.40 ns 11.105 ns 10.388 ns 1.00 0.00 - - NA
ReadEcdsaWithSha384Oid PR 55.72 ns 0.169 ns 0.158 ns 0.17 0.00 - - 0.00
ReadEcdsaWithSha384Oid .NET 7p7 323.15 ns 3.850 ns 3.215 ns 1.00 0.00 0.0057 200 B 1.00
ReadPkcs9X509Oid PR 56.13 ns 0.158 ns 0.147 ns 0.15 0.00 - - 0.00
ReadPkcs9X509Oid .NET 7p7 365.15 ns 5.016 ns 4.692 ns 1.00 0.00 0.0062 224 B 1.00
ReadPkcs9SdsiOid PR 417.89 ns 7.437 ns 6.957 ns 1.13 0.03 0.0062 224 B 1.00
ReadPkcs9SdsiOid .NET 7p7 368.70 ns 6.345 ns 5.935 ns 1.00 0.00 0.0062 224 B 1.00

Higher-level operations (measured on Linux/Ubuntu 18.04; 38% drop in allocations for reading a PFX):

Method Runtime Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
ReadPfx PR 6,250,618.82 ns 79,472.821 ns 74,338.926 ns 0.98 0.01 - 6009 B 0.62
ReadPfx .NET 7p7 6,359,275.50 ns 18,161.444 ns 16,099.647 ns 1.00 0.00 - 9681 B 1.00
ExportPfx PR 5,493,824.80 ns 58,961.080 ns 55,152.230 ns 0.95 0.01 - 12627 B 0.98
ExportPfx .NET 7p7 5,769,931.53 ns 48,468.300 ns 40,473.226 ns 1.00 0.00 - 12833 B 1.00
CreateSelfSignedCert PR 8,586,412.80 ns 48,423.584 ns 45,295.450 ns 1.00 0.01 - 37202 B 0.97
CreateSelfSignedCert .NET 7p7 8,601,930.11 ns 38,323.883 ns 33,973.124 ns 1.00 0.00 - 38211 B 1.00

{
return contents switch
{
[ 0x2A, 0x86, 0x48, 0xCE, 0x38, 0x04, 0x01, ] => "1.2.840.10040.4.1",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wrote a program to generate this file, so one can generally trust that the name/dotted-decimal/encoded-contents are the same in all three places. It didn't seem like we'd be changing it enough often enough to need to check it in... adding one OID at a time is easy to verify.

            (string Oid, string Name, byte[]? Encoded)[] data =
            {
                ("1.2.840.10040.4.1", "DSA", null),
                ("1.2.840.10040.4.3", "DSAWithSha1", null),
                ("1.2.840.10045.2.1", "EC", null),
...

(the nulls got updated by calling WriteObjectIdentifier and saving the contents slice)

@vcsjones
Copy link
Member

Writing a well-known OID drops from ~1000ms to ~90ms, and increases the cost for a less well-known OID by only about 10ms.

Is that the right unit? Writing an OID went from a whole second to 90ms?

}
#endif

string ret = ReadObjectIdentifier(contents);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably as a follow up, but since most dotted-decimal OIDs fit in a stackalloc char[256], maybe this path could use ValueStringBuilder instead of StringBuilder.

@bartonjs
Copy link
Member Author

Is that the right unit? Writing an OID went from a whole second to 90ms?

No, per the table I meant ns, not ms :)

@bartonjs bartonjs merged commit 046fc40 into dotnet:main Sep 13, 2022
@bartonjs bartonjs deleted the asn_oid_perf branch September 13, 2022 15:29
@dotnet dotnet locked as resolved and limited conversation to collaborators Oct 13, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants