Skip to content

BerConverter does not use AsnWriter, AsnDecoder APIs #97540

@edwardneal

Description

@edwardneal

At the moment, the System.DirectoryServices.Protocols.BerConverter class calls the underlying LDAP library's ber_printf and ber_scanf functions. With the more recent System.Formats.Asn1 library, this is no longer needed; we can call the various AsnWriter and AsnDecoder functions instead.

The AsnDecoder functions outperform BerConverter by a fair margin, although I don't think it's called enough times to make a measurable difference. The main value lies in being able to remove all of the BerPal class except for FreeBerElement (which is called by LdapConnection.) It'll mean taking a dependency on System.Formats.Asn1, which seems reasonable for an LDAP library.

Performance benchmarks - 3-6x execution time improvement, 3.4-5x memory allocation reduction

These performance benchmarks are for a sequence of 10,000 integers. The AsnWriter uses an appropriately-sized initial buffer, which can be estimated from the format string; without this buffer, the memory allocation of AsnWriterEncode explodes by a factor of 9, to triple that of BerConverterEncode.

BenchmarkDotNet v0.13.12, Windows 11 (10.0.22621.3007/22H2/2022Update/SunValley2)
Intel Core i7-8565U CPU 1.80GHz (Whiskey Lake), 1 CPU, 8 logical and 4 physical cores
.NET SDK 8.0.101
[Host] : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 [AttachedDebugger]
DefaultJob : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2

Method Mean Error StdDev Ratio Gen0 Gen1 Gen2 Allocated Alloc Ratio
BerConverterDecode 1,727.3 us 21.59 us 20.20 us 1.00 138.6719 68.3594 41.0156 842.52 KB 1.00
AsnDecoderDecode 279.2 us 4.33 us 3.84 us 0.16 40.5273 6.3477 - 167.41 KB 0.20
BerConverterEncode 547.2 us 5.49 us 5.14 us 1.00 66.4063 - - 273.55 KB 1.00
AsnWriterEncode 187.2 us 3.68 us 3.94 us 0.34 19.0430 1.2207 - 78.21 KB 0.29

This will change the BER output on Windows, but Windows is already a slight anomaly. At present, Windows' ber_printf encodes a { (an ASN.1 SEQUENCE) with a four-byte length; OpenLDAP and AsnWriter will only use the number of bytes actually required to encode the sequence length. An example of the changed output for a very simple sequence of one integer (0x10) is:

Platform SequenceTag LengthHeader Length1 Length2 Length3 Length4 IntegerTag IntegerLength IntegerValue
Windows 0x30 0x84 0x00 0x00 0x00 0x03 0x02 0x01 0x10
AsnWriter 0x30 - - - - 0x03 0x02 0x01 0x10

There's also a difference in the ASN.1 BIT STRING. BerConverter doesn't appear to include the unused bit count, while AsnWriter and OpenLDAP do. An example of the changed output for this for the BIT STRING needed for a value of 4 (and two unused bits) is:

Library BitStringTag LengthBitString UnusedBits BitStringValue
BerConverter 0x03 0x01 - 0x04
AsnWriter 0x03 0x02 0x02 0x04

Both of these are already noted to a certain degree in BerConverterTests, and switching to AsnWriter and AsnDecoder would make the output consistent across platforms.

Although it's a public API, it's worth nothing that this is only called within the library when building the list of DirectoryControls. There might also be a performance improvement if we change the callers to use AsnDecoder directly, to avoid the overhead of processing a format string which will always be static.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions