From c159b5fcab7d4d2bdd5052fdcf248325f930b681 Mon Sep 17 00:00:00 2001 From: Mike Alhayek Date: Sat, 11 Jul 2026 16:26:43 -0700 Subject: [PATCH 01/55] Optimize primitive service performance Reduce allocations in template rendering, document processing, MCP resource merging, AI argument parsing, PostgreSQL filter translation, and template discovery. Add behavior coverage and reproducible benchmarks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8f1d3961-3004-4e83-84c5-337b9be1f85c --- CrestApps.Core.slnx | 4 + .../AIFunctionArgumentsBenchmarks.cs | 190 +++++++++++++ .../CrestApps.Core.Benchmarks.csproj | 20 ++ .../FileSystemTemplateProviderBenchmarks.cs | 193 ++++++++++++++ .../FluidTemplateEngineBenchmarks.cs | 109 ++++++++ .../GeneratedFileWriterBenchmarks.cs | 91 +++++++ .../McpServerResourceServiceBenchmarks.cs | 195 ++++++++++++++ .../PdfIngestionDocumentReaderBenchmarks.cs | 249 ++++++++++++++++++ ...stgreSQLODataFilterTranslatorBenchmarks.cs | 75 ++++++ .../CrestApps.Core.Benchmarks/Program.cs | 3 + benchmarks/README.md | 59 +++++ .../Services/WordGeneratedFileWriter.cs | 2 +- .../Services/PdfGeneratedFileWriter.cs | 2 +- .../Services/PdfIngestionDocumentReader.cs | 48 +++- .../DefaultMcpServerResourceService.cs | 21 +- .../AIFunctionArgumentsExtensions.cs | 2 +- .../PostgreSQLODataFilterTranslator.cs | 4 +- .../CrestApps.Core.Templates.csproj | 1 + .../Providers/FileSystemTemplateProvider.cs | 31 ++- .../Rendering/FluidTemplateEngine.cs | 147 +++++++++-- .../Prompting/AITemplateProviderTests.cs | 84 ++++++ .../Prompting/FluidAITemplateEngineTests.cs | 26 ++ .../Generation/GeneratedFileWriterTests.cs | 75 ++++++ .../PdfIngestionDocumentReaderTests.cs | 22 ++ .../DefaultMcpServerResourceServiceTests.cs | 103 ++++++++ .../PostgreSQLODataFilterTranslatorTests.cs | 36 +++ .../AIFunctionArgumentsExtensionsTests.cs | 49 ++++ 27 files changed, 1797 insertions(+), 44 deletions(-) create mode 100644 benchmarks/CrestApps.Core.Benchmarks/AIFunctionArgumentsBenchmarks.cs create mode 100644 benchmarks/CrestApps.Core.Benchmarks/CrestApps.Core.Benchmarks.csproj create mode 100644 benchmarks/CrestApps.Core.Benchmarks/FileSystemTemplateProviderBenchmarks.cs create mode 100644 benchmarks/CrestApps.Core.Benchmarks/FluidTemplateEngineBenchmarks.cs create mode 100644 benchmarks/CrestApps.Core.Benchmarks/GeneratedFileWriterBenchmarks.cs create mode 100644 benchmarks/CrestApps.Core.Benchmarks/McpServerResourceServiceBenchmarks.cs create mode 100644 benchmarks/CrestApps.Core.Benchmarks/PdfIngestionDocumentReaderBenchmarks.cs create mode 100644 benchmarks/CrestApps.Core.Benchmarks/PostgreSQLODataFilterTranslatorBenchmarks.cs create mode 100644 benchmarks/CrestApps.Core.Benchmarks/Program.cs create mode 100644 benchmarks/README.md create mode 100644 tests/CrestApps.Core.Tests/Core/Mcp/DefaultMcpServerResourceServiceTests.cs diff --git a/CrestApps.Core.slnx b/CrestApps.Core.slnx index 955f7402..92494fba 100644 --- a/CrestApps.Core.slnx +++ b/CrestApps.Core.slnx @@ -10,6 +10,10 @@ + + + + diff --git a/benchmarks/CrestApps.Core.Benchmarks/AIFunctionArgumentsBenchmarks.cs b/benchmarks/CrestApps.Core.Benchmarks/AIFunctionArgumentsBenchmarks.cs new file mode 100644 index 00000000..404d637c --- /dev/null +++ b/benchmarks/CrestApps.Core.Benchmarks/AIFunctionArgumentsBenchmarks.cs @@ -0,0 +1,190 @@ +using System.Text.Json; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Jobs; +using CrestApps.Core.AI.Extensions; +using Microsoft.Extensions.AI; + +namespace CrestApps.Core.Benchmarks; + +/// +/// Measures typed values read from JSON-backed AI function arguments. +/// This class must remain unsealed because BenchmarkDotNet generates a derived benchmark type. +/// +[MemoryDiagnoser] +[SimpleJob(warmupCount: 5, iterationCount: 12)] +[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] +[CategoriesColumn] +public class AIFunctionArgumentsBenchmarks +{ + private JsonDocument _document; + private AIFunctionArguments _arguments; + + /// + /// Creates representative JSON-backed function arguments. + /// + [GlobalSetup] + public void Setup() + { + _document = JsonDocument.Parse(""" + { + "number": 42, + "person": { + "name": "Ada", + "age": 37 + }, + "items": ["one", "two", "three", "four"] + } + """); + + _arguments = new AIFunctionArguments + { + ["number"] = _document.RootElement.GetProperty("number"), + ["person"] = _document.RootElement.GetProperty("person"), + ["items"] = _document.RootElement.GetProperty("items"), + }; + } + + /// + /// Disposes the JSON document. + /// + [GlobalCleanup] + public void Cleanup() + { + _document.Dispose(); + } + + /// + /// Reads an integer argument. + /// + /// The converted integer. + [Benchmark(Baseline = true)] + [BenchmarkCategory("Int32")] + public int ReadInt32Buffered() + { + TryGetFirstLegacy(_arguments, "number", out var value); + + return value; + } + + /// + /// Reads an integer argument without materializing raw JSON text. + /// + /// The converted integer. + [Benchmark] + [BenchmarkCategory("Int32")] + public int ReadInt32Optimized() + { + _arguments.TryGetFirst("number", out var value); + + return value; + } + + /// + /// Reads an object argument. + /// + /// The converted object. + [Benchmark(Baseline = true)] + [BenchmarkCategory("Object")] + public Person ReadObjectBuffered() + { + TryGetFirstLegacy(_arguments, "person", out var value); + + return value; + } + + /// + /// Reads an object argument without materializing raw JSON text. + /// + /// The converted object. + [Benchmark] + [BenchmarkCategory("Object")] + public Person ReadObjectOptimized() + { + _arguments.TryGetFirst("person", out var value); + + return value; + } + + /// + /// Reads an array argument. + /// + /// The converted array. + [Benchmark(Baseline = true)] + [BenchmarkCategory("Array")] + public string[] ReadArrayBuffered() + { + TryGetFirstLegacy(_arguments, "items", out var value); + + return value; + } + + /// + /// Reads an array argument without materializing raw JSON text. + /// + /// The converted array. + [Benchmark] + [BenchmarkCategory("Array")] + public string[] ReadArrayOptimized() + { + _arguments.TryGetFirst("items", out var value); + + return value; + } + + /// + /// Preserves the original raw-text JSON conversion as a benchmark baseline. + /// + /// The target value type. + /// The function arguments. + /// The argument key. + /// Receives the converted value. + /// when conversion succeeds. + private static bool TryGetFirstLegacy( + AIFunctionArguments arguments, + string key, + out T value) + { + value = default; + + if (!arguments.TryGetValue(key, out var unsafeValue) || unsafeValue is null) + { + return false; + } + + if (unsafeValue is T alreadyTyped) + { + value = alreadyTyped; + + return true; + } + + if (unsafeValue is JsonElement jsonElement) + { + value = JsonSerializer.Deserialize( + jsonElement.GetRawText(), + JSOptions.CaseInsensitive); + + return true; + } + + return false; + } + + /// + /// Represents a benchmark function argument. + /// + public sealed class Person + { + /// + /// Gets or sets the person's name. + /// + public string Name { get; set; } + + /// + /// Gets or sets the person's age. + /// + public int Age { get; set; } + } +} diff --git a/benchmarks/CrestApps.Core.Benchmarks/CrestApps.Core.Benchmarks.csproj b/benchmarks/CrestApps.Core.Benchmarks/CrestApps.Core.Benchmarks.csproj new file mode 100644 index 00000000..228319c4 --- /dev/null +++ b/benchmarks/CrestApps.Core.Benchmarks/CrestApps.Core.Benchmarks.csproj @@ -0,0 +1,20 @@ + + + + Exe + false + + + + + + + + + + + + + + + diff --git a/benchmarks/CrestApps.Core.Benchmarks/FileSystemTemplateProviderBenchmarks.cs b/benchmarks/CrestApps.Core.Benchmarks/FileSystemTemplateProviderBenchmarks.cs new file mode 100644 index 00000000..903b9862 --- /dev/null +++ b/benchmarks/CrestApps.Core.Benchmarks/FileSystemTemplateProviderBenchmarks.cs @@ -0,0 +1,193 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; +using CrestApps.Core.Templates; +using CrestApps.Core.Templates.Models; +using CrestApps.Core.Templates.Parsing; +using CrestApps.Core.Templates.Providers; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; + +namespace CrestApps.Core.Benchmarks; + +/// +/// Measures template discovery across many files and registered parsers. +/// This class must remain unsealed because BenchmarkDotNet generates a derived benchmark type. +/// +[MemoryDiagnoser] +[ShortRunJob] +public class FileSystemTemplateProviderBenchmarks +{ + private string _tempDirectory; + private LegacyFileSystemTemplateProvider _legacyProvider; + private FileSystemTemplateProvider _provider; + + /// + /// Gets or sets the number of template files. + /// + [Params(100, 1000)] + public int FileCount { get; set; } + + /// + /// Gets or sets the number of registered parsers. + /// + [Params(1, 20)] + public int ParserCount { get; set; } + + /// + /// Creates template files and parser registrations. + /// + [GlobalSetup] + public void Setup() + { + _tempDirectory = Path.Combine(Path.GetTempPath(), $"CrestAppsTemplateBenchmarks_{Guid.NewGuid():N}"); + var templatesDirectory = Path.Combine(_tempDirectory, FileSystemTemplateProvider.TemplatesDirectoryPath); + Directory.CreateDirectory(templatesDirectory); + + for (var i = 0; i < FileCount; i++) + { + File.WriteAllText(Path.Combine(templatesDirectory, $"template-{i}.md"), "Template content."); + } + + var parsers = new List(ParserCount); + + for (var i = 1; i < ParserCount; i++) + { + parsers.Add(new BenchmarkTemplateParser($".unsupported-{i}")); + } + + parsers.Add(new BenchmarkTemplateParser(".md")); + + var options = new TemplateOptions(); + options.DiscoveryPaths.Add(_tempDirectory); + _legacyProvider = new LegacyFileSystemTemplateProvider(options, parsers); + _provider = new FileSystemTemplateProvider( + Options.Create(options), + parsers, + NullLogger.Instance); + } + + /// + /// Removes the temporary benchmark files. + /// + [GlobalCleanup] + public void Cleanup() + { + Directory.Delete(_tempDirectory, recursive: true); + } + + /// + /// Discovers and parses the configured templates. + /// + /// The discovered templates. + [Benchmark(Baseline = true)] + public Task> GetTemplatesBufferedAsync() + { + return _legacyProvider.GetTemplatesAsync(); + } + + /// + /// Discovers templates using streaming file enumeration and the extension lookup. + /// + /// The discovered templates. + [Benchmark] + public Task> GetTemplatesOptimizedAsync() + { + return _provider.GetTemplatesAsync(); + } + + private sealed class BenchmarkTemplateParser : ITemplateParser + { + public BenchmarkTemplateParser(string extension) + { + SupportedExtensions = [extension]; + } + + public IReadOnlyList SupportedExtensions { get; } + + public TemplateParseResult Parse(string rawContent) + { + return new TemplateParseResult + { + Body = rawContent, + }; + } + } + + private sealed class LegacyFileSystemTemplateProvider + { + private readonly TemplateOptions _options; + private readonly IEnumerable _parsers; + + public LegacyFileSystemTemplateProvider( + TemplateOptions options, + IEnumerable parsers) + { + _options = options; + _parsers = parsers; + } + + public Task> GetTemplatesAsync() + { + var templates = new List