-
Notifications
You must be signed in to change notification settings - Fork 5.1k
Description
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 DirectoryControl
s. 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.