Skip to content

Conversation

@stevenaw
Copy link
Contributor

@stevenaw stevenaw commented Nov 26, 2025

Optimizes the parsing of SMTP extensions, mostly just by using some APIs which likely weren't available when this was last looked at closely.

  • Eliminates allocations
  • Roughly twice as fast
  • Roughly half the code size

Benchmarks

BenchmarkDotNet v0.15.6, Windows 11 (10.0.26100.7171/24H2/2024Update/HudsonValley)
13th Gen Intel Core i7-1355U 1.70GHz, 1 CPU, 12 logical and 10 physical cores
.NET SDK 10.0.100
  [Host]     : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
  DefaultJob : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3

| Method             | Mean     | Error    | StdDev   | Ratio | Code Size | Gen0   | Allocated | Alloc Ratio |
|------------------- |---------:|---------:|---------:|------:|----------:|-------:|----------:|------------:|
| ParseExtension_Old | 48.40 ns | 0.156 ns | 0.138 ns |  1.00 |   1,831 B | 0.0357 |     224 B |        1.00 |
| ParseExtension     | 17.32 ns | 0.021 ns | 0.020 ns |  0.36 |     943 B |      - |         - |        0.00 |
Benchmark Code
BenchmarkRunner.Run<BenchmarkSmtpExtensionParser>();

[SimpleJob, MemoryDiagnoser, DisassemblyDiagnoser]
public class BenchmarkSmtpExtensionParser
{
    string[] extensions = ["SMTPUTF8", "STARTTLS", "AUTH:LOGIN NTLM GSSAPI"];
    private readonly SmtpExtensionParser _parser = new SmtpExtensionParser();

    [Benchmark(Baseline = true)]
    public void ParseExtension_Old() => _parser.ParseExtensions(extensions);

    [Benchmark]
    public void ParseExtension() => _parser.ParseExtensions2(extensions);
}

public class SmtpExtensionParser
{
    private bool _serverSupportsEai;
    private bool _dsnEnabled;
#pragma warning disable CS0414      // Field is not used in test project
    private bool _serverSupportsStartTls;
#pragma warning restore CS0414
    private bool _sawNegotiate;
    private SupportedAuth _supportedAuth = SupportedAuth.None;
    // accounts for the '=' or ' ' character after AUTH
    private const int SizeOfAuthExtension = 4;

    private static readonly char[] s_authExtensionSplitters = new char[] { ' ', '=' };

    private const string AuthExtension = "auth";
    private const string AuthLogin = "login";
    private const string AuthNtlm = "ntlm";
    private const string AuthGssapi = "gssapi";

    internal void ParseExtensions(string[] extensions)
    {
        _supportedAuth = SupportedAuth.None;
        foreach (string extension in extensions)
        {
            if (string.Compare(extension, 0, AuthExtension, 0,
                SizeOfAuthExtension, StringComparison.OrdinalIgnoreCase) == 0)
            {
                // remove the AUTH text including the following character
                // to ensure that split only gets the modules supported
                string[] authTypes = extension.Remove(0, SizeOfAuthExtension).Split(s_authExtensionSplitters, StringSplitOptions.RemoveEmptyEntries);
                foreach (string authType in authTypes)
                {
                    if (string.Equals(authType, AuthLogin, StringComparison.OrdinalIgnoreCase))
                    {
                        _supportedAuth |= SupportedAuth.Login;
                    }
                    else if (string.Equals(authType, AuthNtlm, StringComparison.OrdinalIgnoreCase))
                    {
                        _supportedAuth |= SupportedAuth.NTLM;
                    }
                    else if (string.Equals(authType, AuthGssapi, StringComparison.OrdinalIgnoreCase))
                    {
                        _supportedAuth |= SupportedAuth.GSSAPI;
                    }
                }
            }
            else if (string.Compare(extension, 0, "dsn ", 0, 3, StringComparison.OrdinalIgnoreCase) == 0)
            {
                _dsnEnabled = true;
            }
            else if (string.Compare(extension, 0, "STARTTLS", 0, 8, StringComparison.OrdinalIgnoreCase) == 0)
            {
                _serverSupportsStartTls = true;
            }
            else if (string.Compare(extension, 0, "SMTPUTF8", 0, 8, StringComparison.OrdinalIgnoreCase) == 0)
            {
                _serverSupportsEai = true;
            }
        }
    }

    internal void ParseExtensions2(string[] extensions)
    {
        _supportedAuth = SupportedAuth.None;
        foreach (string extension in extensions)
        {
            if (extension.StartsWith(AuthExtension, StringComparison.OrdinalIgnoreCase))
            {
                // remove the AUTH text including the following character
                // to ensure that split only gets the modules supported
                ReadOnlySpan<char> authTypes = extension.AsSpan(SizeOfAuthExtension + 1);

                foreach (Range i in authTypes.SplitAny(s_authExtensionSplitters))
                {
                    ReadOnlySpan<char> authType = authTypes[i];
                    if (authType.Equals(AuthLogin, StringComparison.OrdinalIgnoreCase))
                    {
                        _supportedAuth |= SupportedAuth.Login;
                    }
                    else if (authType.Equals(AuthNtlm, StringComparison.OrdinalIgnoreCase))
                    {
                        _supportedAuth |= SupportedAuth.NTLM;
                    }
                    else if (authType.Equals(AuthGssapi, StringComparison.OrdinalIgnoreCase))
                    {
                        _supportedAuth |= SupportedAuth.GSSAPI;
                    }
                }
            }
            else if (extension.StartsWith("dsn ", StringComparison.OrdinalIgnoreCase))
            {
                _dsnEnabled = true;
            }
            else if (extension.StartsWith("STARTTLS", StringComparison.OrdinalIgnoreCase))
            {
                _serverSupportsStartTls = true;
            }
            else if (extension.StartsWith("SMTPUTF8", StringComparison.OrdinalIgnoreCase))
            {
                _serverSupportsEai = true;
            }
        }
    }
}

Copilot AI review requested due to automatic review settings November 26, 2025 21:38
@github-actions github-actions bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Nov 26, 2025
Copilot finished reviewing on behalf of stevenaw November 26, 2025 21:40
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Nov 26, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR optimizes SMTP extension parsing by leveraging modern .NET APIs to eliminate allocations and improve performance. The changes achieve approximately 2x speed improvement and 50% reduction in code size while removing all allocations.

Key changes:

  • Replaced string.Compare prefix checks with StartsWith for cleaner code
  • Converted string manipulation to span-based operations using ReadOnlySpan<char> and SplitAny
  • Updated SizeOfAuthExtension constant from 4 to 5 to account for the character after "AUTH"

@stevenaw stevenaw marked this pull request as draft November 26, 2025 22:38
@stevenaw
Copy link
Contributor Author

Closing this for now. I found I'd inadvertently introduced some noise to the first benchmark to run, which happened to be my baseline

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community-contribution Indicates that the PR has been added by a community member needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant