Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b41220a
Optimize qualified name simplification
live1206 Jun 3, 2026
4870164
Handle System member qualified name reduction
live1206 Jun 4, 2026
59eb312
Avoid ambiguous System member pre-reduction
live1206 Jun 4, 2026
5bbd4eb
Revert "Avoid ambiguous System member pre-reduction"
live1206 Jun 4, 2026
47fbfa9
Experiment with manual generated name reduction
live1206 Jun 4, 2026
f25c028
Reduce global aliases in manual name experiment
live1206 Jun 4, 2026
bbf0305
Handle global aliases in cast type arguments
live1206 Jun 4, 2026
8e5c569
Reduce qualified names iteratively
live1206 Jun 4, 2026
d867864
Match generated baselines without Roslyn simplifier
live1206 Jun 4, 2026
6c536ed
Add post-processing benchmark
live1206 Jun 4, 2026
90def0a
Support scaled post-processing benchmark corpora
live1206 Jun 4, 2026
e4019d3
Reduce manual post-processing passes
live1206 Jun 4, 2026
8f6da6a
Profile C# post-processing benchmarks
live1206 Jun 11, 2026
c11ae82
Add provider reference map shadow replacement
live1206 Jun 12, 2026
a5f8d40
Update provider reference map benchmarks
live1206 Jun 12, 2026
df00d67
Track serialization providers in benchmark reference map
live1206 Jun 12, 2026
27e121e
Sync benchmark reference map parity fixes
live1206 Jun 15, 2026
8bb0d29
Benchmark explicit provider dependencies
live1206 Jun 15, 2026
74bd30c
Use explicit client body dependencies
live1206 Jun 15, 2026
8da04b0
Track rest client helper dependencies
live1206 Jun 15, 2026
aa94fa3
Skip stubbed client body dependencies
live1206 Jun 15, 2026
26181a8
Merge remote-tracking branch 'origin/main' into mtg-manual-name-reduc…
live1206 Jun 19, 2026
ac2717b
perf(http-client-csharp): optimize reference map post-processing
live1206 Jun 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: internal
packages:
- "@typespec/http-client-csharp"
---

Improve C# generator post-processing reference-map parity and performance.
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,60 @@ private IReadOnlyList<ParameterProvider> GetClientParameters()

protected override string BuildName() => _inputClient.IsExactName ? _inputClient.Name : _inputClient.Name.ToIdentifierName();

protected override IReadOnlyList<CSharpType> BuildBodyDependencyTypes()
{
var dependencies = new List<CSharpType>();
foreach (var method in Methods.OfType<ScmMethodProvider>())
{
if (method.BodyStatements == null)
{
continue;
}

if (method.CollectionDefinition != null)
{
dependencies.Add(method.CollectionDefinition.Type);
}

if (method.ServiceMethod == null)
{
continue;
}

AddInputTypeDependency(dependencies, method.ServiceMethod.Response.Type);
AddInputTypeDependency(dependencies, method.ServiceMethod.Exception?.Type);
foreach (var parameter in method.ServiceMethod.Parameters)
{
AddInputTypeDependency(dependencies, parameter.Type);
}

foreach (var parameter in method.ServiceMethod.Operation.Parameters)
{
AddInputTypeDependency(dependencies, parameter.Type);
}

foreach (var response in method.ServiceMethod.Operation.Responses)
{
AddInputTypeDependency(dependencies, response.BodyType);
foreach (var header in response.Headers)
{
AddInputTypeDependency(dependencies, header.Type);
}
}
}

return dependencies;
}

private static void AddInputTypeDependency(List<CSharpType> dependencies, InputType? inputType)
{
var type = inputType == null ? null : ScmCodeModelGenerator.Instance.TypeFactory.CreateCSharpType(inputType);
if (type != null)
{
dependencies.Add(type);
}
}

protected override FieldProvider[] BuildFields()
{
List<FieldProvider> fields = [EndpointField];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,22 @@ private bool HasPagingOperationNameCollision(string operationName)
protected override TypeSignatureModifiers BuildDeclarationModifiers()
=> TypeSignatureModifiers.Internal | TypeSignatureModifiers.Partial | TypeSignatureModifiers.Class;

protected override IReadOnlyList<CSharpType> BuildBodyDependencyTypes()
{
var dependencies = new List<CSharpType> { Client.Type, ResponseModelType, NextPagePropertyType };
if (ItemModelType != null)
{
dependencies.Add(ItemModelType);
}

foreach (var field in RequestFields)
{
dependencies.Add(field.Type);
}

return dependencies;
}

protected override FieldProvider[] BuildFields() => [ClientField, .. RequestFields];

protected override CSharpType[] BuildImplements() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ public MrwSerializationTypeDefinition(InputModelType inputModel, ModelProvider m

protected override CSharpType? BuildBaseType() => _model.BaseType;

protected override IReadOnlyList<string> BuildHelperDependencyNames() => _rawDataField != null || _additionalProperties.Value.Length > 0
? ["ChangeTrackingDictionary"]
: [];

protected override SuppressionStatement[] BuildDisabledFileWarnings()
{
if (_model.CanonicalView.Properties.Any(p => ScmModelProvider.IsFileBinaryContentType(p.Type)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,33 @@ protected override FieldProvider[] BuildFields()
return [.. pipelineMessage20xClassifiersFields];
}

protected override IReadOnlyList<string> BuildHelperDependencyNames()
{
var dependencies = new HashSet<string>(StringComparer.Ordinal);
foreach (var serviceMethod in _inputClient.Methods)
{
foreach (var parameter in serviceMethod.Operation.Parameters)
{
if (parameter is not InputHeaderParameter and not InputQueryParameter)
{
continue;
}

var type = ScmCodeModelGenerator.Instance.TypeFactory.CreateCSharpType(parameter.Type);
if (type?.IsDictionary == true)
{
dependencies.Add("ChangeTrackingDictionary");
}
else if (type?.IsCollection == true)
{
dependencies.Add("ChangeTrackingList");
}
}
}

return [.. dependencies];
}

protected override ScmMethodProvider[] BuildMethods()
{
List<ScmMethodProvider> methods = new List<ScmMethodProvider>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;

namespace Microsoft.TypeSpec.Generator.Perf
{
public class FullGenerationBenchmark
{
private const string ProfileEnvironmentVariable = "POSTPROCESSING_BENCHMARK_PROFILE_STEPS";
private const string ProfileOutputDirectoryEnvironmentVariable = "POSTPROCESSING_BENCHMARK_PROFILE_DIR";
private const string ShadowEnvironmentVariable = "TYPESPEC_PROVIDER_REFERENCE_MAP_SHADOW";
private const string UseShadowEnvironmentVariable = "TYPESPEC_PROVIDER_REFERENCE_MAP_USE_SHADOW";
private const string ShadowReportEnvironmentVariable = "TYPESPEC_PROVIDER_REFERENCE_MAP_SHADOW_REPORT";

private bool _profileSteps;

[Params(false, true)]
public bool UseProviderReferenceMap { get; set; }

[GlobalSetup]
public void GlobalSetup()
{
_profileSteps = string.Equals(
Environment.GetEnvironmentVariable(ProfileEnvironmentVariable),
"true",
StringComparison.OrdinalIgnoreCase);
}

[Benchmark]
public async Task<int> GenerateSampleTypeSpecProject()
{
var postProcessingProfile = _profileSteps ? new GeneratedCodeWorkspacePostProcessingProfile() : null;
var generationProfile = _profileSteps ? new GeneratedCodeWorkspacePostProcessingProfile() : null;
GeneratedCodeWorkspace.PostProcessingProfile = postProcessingProfile;
CSharpGen.GenerationProfile = generationProfile;

var benchmarkDirectory = CreateBenchmarkInputDirectory();
var previousShadow = Environment.GetEnvironmentVariable(ShadowEnvironmentVariable);
var previousUseShadow = Environment.GetEnvironmentVariable(UseShadowEnvironmentVariable);
var previousShadowReport = Environment.GetEnvironmentVariable(ShadowReportEnvironmentVariable);
var stopwatch = Stopwatch.StartNew();
try
{
SetProviderReferenceMapEnvironment();
CodeModelGenerator.Instance = new BenchmarkCodeModelGenerator(benchmarkDirectory);
CodeModelGenerator.Instance.Configure();

var csharpGen = new CSharpGen();
await csharpGen.ExecuteAsync();

return Directory.GetFiles(benchmarkDirectory, "*", SearchOption.AllDirectories)
.Where(static path => !path.EndsWith("tspCodeModel.json", StringComparison.Ordinal) &&
!path.EndsWith("Configuration.json", StringComparison.Ordinal))
.Sum(static path => (int)new FileInfo(path).Length);
}
finally
{
stopwatch.Stop();
if (generationProfile != null)
{
WriteProfile(
generationProfile,
$"full-generation-profile-{DateTime.UtcNow:yyyyMMddHHmmssfff}.csv",
$"Full generation invocation elapsed ms: {stopwatch.Elapsed.TotalMilliseconds:F3}{Environment.NewLine}" +
$"Input directory: {benchmarkDirectory}{Environment.NewLine}");
}

if (postProcessingProfile != null)
{
WriteProfile(
postProcessingProfile,
$"full-generation-post-processing-profile-{DateTime.UtcNow:yyyyMMddHHmmssfff}.csv",
$"Full generation post-processing profile{Environment.NewLine}" +
$"Input directory: {benchmarkDirectory}{Environment.NewLine}");
}

CSharpGen.GenerationProfile = null;
GeneratedCodeWorkspace.PostProcessingProfile = null;
Environment.SetEnvironmentVariable(ShadowEnvironmentVariable, previousShadow);
Environment.SetEnvironmentVariable(UseShadowEnvironmentVariable, previousUseShadow);
Environment.SetEnvironmentVariable(ShadowReportEnvironmentVariable, previousShadowReport);
TryDeleteDirectory(benchmarkDirectory);
}
}

private void SetProviderReferenceMapEnvironment()
{
Environment.SetEnvironmentVariable(ShadowEnvironmentVariable, UseProviderReferenceMap ? "true" : null);
Environment.SetEnvironmentVariable(UseShadowEnvironmentVariable, UseProviderReferenceMap ? "true" : null);
Environment.SetEnvironmentVariable(ShadowReportEnvironmentVariable, null);
}

private static void WriteProfile(GeneratedCodeWorkspacePostProcessingProfile profile, string fileName, string header)
{
var profileDirectory = GetProfileOutputDirectory();
Directory.CreateDirectory(profileDirectory);
File.WriteAllText(Path.Combine(profileDirectory, fileName), header + profile.GetSummary());
}

private static string CreateBenchmarkInputDirectory()
{
var sourceDirectory = FindFullGenerationInputDirectory();
var benchmarkDirectory = Path.Combine(Path.GetTempPath(), "typespec-full-generation-benchmark", Guid.NewGuid().ToString("N"));
CopyDirectory(sourceDirectory, benchmarkDirectory);
return benchmarkDirectory;
}

private static string FindFullGenerationInputDirectory()
{
const string relativePath = "packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec";

var directory = new DirectoryInfo(AppContext.BaseDirectory);
while (directory != null)
{
var inputDirectory = Path.Combine(directory.FullName, relativePath);
if (File.Exists(Path.Combine(inputDirectory, "tspCodeModel.json")) &&
File.Exists(Path.Combine(inputDirectory, "Configuration.json")))
{
return inputDirectory;
}

directory = directory.Parent;
}

throw new DirectoryNotFoundException($"Could not find '{relativePath}' from '{AppContext.BaseDirectory}'.");
}

private static void CopyDirectory(string sourceDirectory, string destinationDirectory)
{
Directory.CreateDirectory(destinationDirectory);
foreach (var sourceFile in Directory.GetFiles(sourceDirectory, "*", SearchOption.AllDirectories))
{
var relativePath = Path.GetRelativePath(sourceDirectory, sourceFile);
var destinationFile = Path.Combine(destinationDirectory, relativePath);
Directory.CreateDirectory(Path.GetDirectoryName(destinationFile)!);
File.Copy(sourceFile, destinationFile, overwrite: true);
}
}

private static void TryDeleteDirectory(string directory)
{
try
{
if (Directory.Exists(directory))
{
Directory.Delete(directory, recursive: true);
}
}
catch
{
// Best-effort cleanup for benchmark temp output.
}
}

private static string GetProfileOutputDirectory()
{
var configuredPath = Environment.GetEnvironmentVariable(ProfileOutputDirectoryEnvironmentVariable);
return string.IsNullOrWhiteSpace(configuredPath)
? Path.Combine(Path.GetTempPath(), "typespec-post-processing-profiles")
: Path.GetFullPath(configuredPath);
}

private sealed class BenchmarkCodeModelGenerator : CodeModelGenerator
{
public BenchmarkCodeModelGenerator(string outputPath)
: base(new GeneratorContext(Configuration.Load(outputPath)))
{
}
}
}
}
Loading
Loading