diff --git a/Benchmarks/Benchmarks.csproj b/Benchmarks/Benchmarks.csproj new file mode 100644 index 000000000..4552e80ba --- /dev/null +++ b/Benchmarks/Benchmarks.csproj @@ -0,0 +1,55 @@ + + + + Exe + x64;x86 + netcoreapp2.0;net5.0;netcoreapp3.1 + false + false + + true + Benchmarks.manifest + + + + + + + + + + + + + + + + BenchmarkComponent.dll + PreserveNewest + True + + + + $(MSBuildThisFileDirectory)..\_build\$(Platform)\$(Configuration)\BenchmarkComponent\bin\BenchmarkComponent\BenchmarkComponent.winmd + true + + + + + Benchmarks.manifest + PreserveNewest + True + + + + + + + + + + + + + + diff --git a/Benchmarks/Benchmarks.manifest b/Benchmarks/Benchmarks.manifest new file mode 100644 index 000000000..74774097c --- /dev/null +++ b/Benchmarks/Benchmarks.manifest @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/Benchmarks/Program.cs b/Benchmarks/Program.cs new file mode 100644 index 000000000..89255f080 --- /dev/null +++ b/Benchmarks/Program.cs @@ -0,0 +1,95 @@ +using BenchmarkDotNet.Running; +using System; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Toolchains.CsProj; +using BenchmarkDotNet.Toolchains.DotNetCli; +using BenchmarkDotNet.Toolchains; +using BenchmarkDotNet.Loggers; +using BenchmarkDotNet.Characteristics; +using System.IO; + +namespace Benchmarks +{ + public class Program + { + static void Main(string[] args) => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, new CustomConfig().Config); + + private class CustomConfig : Attribute, IConfigSource + { + public IConfig Config { get; } = DefaultConfig.Instance; + + public CustomConfig() + { + // Test CsWinRT projection + var job = Job.Default + .WithPlatform(BenchmarkDotNet.Environments.Platform.X64) + .WithArguments( + new Argument[] { + new MsBuildArgument("/p:platform=x64") + } + ).AsDefault(); + + // Test WinMD support +#if NETCOREAPP3_1 + // BenchmarkDotNet will rebuild the project with a project reference to this project when this project's output exe is ran. It + // will be ran from the same folder as where we have the application manifest binplaced which we want to embed in the new exe. + string manifestFile = Path.Combine( + Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), + "Benchmarks.manifest"); + + var winmdJob = Job.Default + .WithPlatform(BenchmarkDotNet.Environments.Platform.X64) + .WithToolchain(new NetCore3ToolChainWithNativeExecution()) + .WithArguments( + new Argument[] { + new MsBuildArgument("/p:platform=x64"), + new MsBuildArgument("/p:ApplicationManifest=" + manifestFile), + new MsBuildArgument("/p:BenchmarkWinmdSupport=true") + } + ) + .WithId("WinMD NetCoreApp31"); + + // Optimizer needs to be diabled as it errors on WinMDs + Config = Config.WithOption(ConfigOptions.DisableOptimizationsValidator, true) + .AddJob(winmdJob); +#else + Config = Config.AddJob(job); +#endif + } + } + + // Custom tool chain for building the benchmark with WinMDs as we need to execute the + // exe version of the benchmark rather than the dll version which runs under dotnet cli. + // This is because we need to be able to embed a side by side manifest for reg free winrt + // and we need COM to be able to find the WinMDs. + private class NetCore3ToolChainWithNativeExecution : Toolchain + { + public NetCore3ToolChainWithNativeExecution() + : base("netcoreapp3.1-native", + new CsProjGeneratorWithNativeExe(NetCoreAppSettings.NetCoreApp31), + CsProjCoreToolchain.NetCoreApp31.Builder, + new Executor()) + { + } + + public override bool IsSupported(BenchmarkCase benchmarkCase, ILogger logger, IResolver resolver) + { + return CsProjCoreToolchain.NetCoreApp31.IsSupported(benchmarkCase, logger, resolver); + } + } + + private class CsProjGeneratorWithNativeExe : CsProjGenerator + { + public CsProjGeneratorWithNativeExe(NetCoreAppSettings settings) + :base(settings.TargetFrameworkMoniker, settings.CustomDotNetCliPath, settings.PackagesPath, settings.RuntimeFrameworkVersion) + { + } + + protected override string GetExecutableExtension() + { + return ".exe"; + } + } + } +} diff --git a/Benchmarks/QueryInterface.cs b/Benchmarks/QueryInterface.cs new file mode 100644 index 000000000..567ef3aa9 --- /dev/null +++ b/Benchmarks/QueryInterface.cs @@ -0,0 +1,79 @@ +using BenchmarkComponent; +using BenchmarkDotNet.Attributes; +using Windows.ApplicationModel.Chat; + +namespace Benchmarks +{ + [MemoryDiagnoser] + public class QueryInterfacePerf + { + ClassWithMultipleInterfaces instance; + ChatMessage message; + + [GlobalSetup] + public void Setup() + { + instance = new ClassWithMultipleInterfaces(); + message = new ChatMessage(); + } + + [Benchmark] + public int QueryDefaultInterface() + { + return instance.DefaultIntProperty; + } + + [Benchmark] + public int QueryNonDefaultInterface() + { + return instance.IntProperty; + } + + [Benchmark] + public bool QueryNonDefaultInterface2() + { + return instance.BoolProperty; + } + + [Benchmark] + public void QueryDefaultInterfaceSetProperty() + { + instance.DefaultIntProperty = 4; + } + + [Benchmark] + public void QueryNonDefaultInterfaceSetProperty() + { + instance.IntProperty = 4; + } + + [Benchmark] + public bool QuerySDKDefaultInterface() + { + return message.IsForwardingDisabled; + } + + [Benchmark] + public bool QuerySDKNonDefaultInterface() + { + return message.IsSeen; + } + + // The following 2 benchmarks try to benchmark the time taken for the first call + // rather than the mean time over several calls. It has the overhead of the object + // construction, but it can be used to track regressions to performance. + [Benchmark] + public int ConstructAndQueryDefaultInterfaceFirstCall() + { + ClassWithMultipleInterfaces instance2 = new ClassWithMultipleInterfaces(); + return instance2.DefaultIntProperty; + } + + [Benchmark] + public int ConstructAndQueryNonDefaultInterfaceFirstCall() + { + ClassWithMultipleInterfaces instance2 = new ClassWithMultipleInterfaces(); + return instance2.IntProperty; + } + } +} \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index 573d6c7eb..f8dd4e42b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -42,6 +42,7 @@ Windows + 10.0.18362.0 diff --git a/Projections/Benchmark/Benchmark.csproj b/Projections/Benchmark/Benchmark.csproj new file mode 100644 index 000000000..02265c124 --- /dev/null +++ b/Projections/Benchmark/Benchmark.csproj @@ -0,0 +1,65 @@ + + + + netstandard2.0;net5.0 + x64;x86 + 8 + + + + true + true + 8305;0618 + + + + full + true + + + + + + + + + + + + + + + + + + high + $(GeneratedFilesDir)cswinrt_benchmark.rsp + $(CsWinRTExe) %40"$(CsWinRTResponseFile)" + + + +-verbose +-in 10.0.18362.0 +-in @(ReferenceWinMDs->'"%(FullPath)"', ' ') +-out "$(GeneratedFilesDir.TrimEnd('\'))" +-exclude Windows +-include BenchmarkComponent + + + + + + + + + + + + + + + + + + + diff --git a/Projections/Test/Test.csproj b/Projections/Test/Test.csproj index 803485d94..399e05808 100644 --- a/Projections/Test/Test.csproj +++ b/Projections/Test/Test.csproj @@ -3,7 +3,6 @@ netstandard2.0;net5.0 x64;x86 - 10.0.18362.0 8 diff --git a/Projections/WinUI/WinUI.csproj b/Projections/WinUI/WinUI.csproj index 7d072ba81..7d5287727 100644 --- a/Projections/WinUI/WinUI.csproj +++ b/Projections/WinUI/WinUI.csproj @@ -3,7 +3,6 @@ netstandard2.0;net5.0 x64;x86 - 10.0.18362.0 8 diff --git a/Projections/Windows/Windows.csproj b/Projections/Windows/Windows.csproj index fd75e76b9..8d96377ee 100644 --- a/Projections/Windows/Windows.csproj +++ b/Projections/Windows/Windows.csproj @@ -3,7 +3,6 @@ netstandard2.0;net5.0 x64;x86 - 10.0.18362.0 8 diff --git a/README.md b/README.md index 0836f9f0d..f9e6ff868 100644 --- a/README.md +++ b/README.md @@ -117,12 +117,16 @@ The **/TestComponentCSharp** folder contains an implementation of a WinRT test c ## /Projections -The **/Projections** folder contains several projects for generating and building projections from the Windows SDK, WinUI, and Test metadata (produced by the TestWinRT and TestComponentCSharp projects). +The **/Projections** folder contains several projects for generating and building projections from the Windows SDK, WinUI, Benchmark (produced by the BenchmarkComponent project), and Test metadata (produced by the TestWinRT and TestComponentCSharp projects). ## /UnitTest The **/UnitTest** folder contains unit tests for validating the Windows SDK, WinUI, and Test projections generated above. All pull requests should ensure that this project executes without errors. +## /Benchmarks + +The **/Benchmarks** folder contains benchmarks written using BenchmarkDotNet to track the performance of scenarios in the generated projection. To run the benchmarks using the CsWinRT projection, run **benchmark.cmd**. To run the same benchmarks using the built-in WinMD support in NET Core 3.1 to compare against as a baseline, run **benchmark_winmd.cmd**. + ## /WinUIDesktopSample The **/WinUIDesktopSample** contains an end-to-end sample app that uses the Windows SDK and WinUI projections generated above. diff --git a/benchmark.cmd b/benchmark.cmd new file mode 100644 index 000000000..4ebba8bd6 --- /dev/null +++ b/benchmark.cmd @@ -0,0 +1,2 @@ +msbuild Benchmarks\Benchmarks.csproj -t:restore -t:build /p:platform=x64 /p:configuration=release +dotnet %~dp0Benchmarks\bin\x64\Release\netcoreapp2.0\Benchmarks.dll -filter * --runtimes netcoreapp2.0 netcoreapp3.1 netcoreapp5.0 \ No newline at end of file diff --git a/benchmark_winmd.cmd b/benchmark_winmd.cmd new file mode 100644 index 000000000..feaa12298 --- /dev/null +++ b/benchmark_winmd.cmd @@ -0,0 +1,4 @@ +msbuild Benchmarks\Benchmarks.csproj -t:restore -t:clean;rebuild /p:BenchmarkWinmdSupport=true /p:platform=x64 /p:configuration=release /p:TargetFramework=netcoreapp3.1 +%~dp0Benchmarks\bin\x64\Release\netcoreapp3.1\Benchmarks.exe -filter * +rem Clean project to prevent mismatch scenarios with the typical benchmark.cmd scenario. +msbuild Benchmarks\Benchmarks.csproj -t:restore -t:clean /p:BenchmarkWinmdSupport=true /p:platform=x64 /p:configuration=release /p:TargetFramework=netcoreapp3.1 >nul \ No newline at end of file diff --git a/cswinrt.sln b/cswinrt.sln index e9b85aa6f..40d1ee8d6 100644 --- a/cswinrt.sln +++ b/cswinrt.sln @@ -54,6 +54,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Nuget", "Nuget", "{5A94EFDF nuget\SignConfig.xml = nuget\SignConfig.xml EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "Benchmarks\Benchmarks.csproj", "{B34C96F4-3660-4B2D-8ABD-A4B428166DC7}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BenchmarkComponent", "TestWinRT\BenchmarkComponent\BenchmarkComponent.vcxproj", "{78D85F23-7CB1-44A1-9238-6DF2C76754E4}" + ProjectSection(ProjectDependencies) = postProject + {6ACFD2B2-E8AA-4CD4-AAD8-213CE8BB2637} = {6ACFD2B2-E8AA-4CD4-AAD8-213CE8BB2637} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmark", "Projections\Benchmark\Benchmark.csproj", "{03EEF460-2F10-4FBE-AFFA-53477D3FC8D5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -208,6 +217,48 @@ Global {0A991D5F-BFEE-4D2F-9AAD-6AD06470A5DF}.Release|x64.Build.0 = Release|x64 {0A991D5F-BFEE-4D2F-9AAD-6AD06470A5DF}.Release|x86.ActiveCfg = Release|x86 {0A991D5F-BFEE-4D2F-9AAD-6AD06470A5DF}.Release|x86.Build.0 = Release|x86 + {B34C96F4-3660-4B2D-8ABD-A4B428166DC7}.Debug|Any CPU.ActiveCfg = Debug|x86 + {B34C96F4-3660-4B2D-8ABD-A4B428166DC7}.Debug|ARM.ActiveCfg = Debug|x86 + {B34C96F4-3660-4B2D-8ABD-A4B428166DC7}.Debug|ARM64.ActiveCfg = Debug|x86 + {B34C96F4-3660-4B2D-8ABD-A4B428166DC7}.Debug|x64.ActiveCfg = Debug|x64 + {B34C96F4-3660-4B2D-8ABD-A4B428166DC7}.Debug|x64.Build.0 = Debug|x64 + {B34C96F4-3660-4B2D-8ABD-A4B428166DC7}.Debug|x86.ActiveCfg = Debug|x86 + {B34C96F4-3660-4B2D-8ABD-A4B428166DC7}.Debug|x86.Build.0 = Debug|x86 + {B34C96F4-3660-4B2D-8ABD-A4B428166DC7}.Release|Any CPU.ActiveCfg = Release|x86 + {B34C96F4-3660-4B2D-8ABD-A4B428166DC7}.Release|ARM.ActiveCfg = Release|x86 + {B34C96F4-3660-4B2D-8ABD-A4B428166DC7}.Release|ARM64.ActiveCfg = Release|x86 + {B34C96F4-3660-4B2D-8ABD-A4B428166DC7}.Release|x64.ActiveCfg = Release|x64 + {B34C96F4-3660-4B2D-8ABD-A4B428166DC7}.Release|x64.Build.0 = Release|x64 + {B34C96F4-3660-4B2D-8ABD-A4B428166DC7}.Release|x86.ActiveCfg = Release|x86 + {B34C96F4-3660-4B2D-8ABD-A4B428166DC7}.Release|x86.Build.0 = Release|x86 + {78D85F23-7CB1-44A1-9238-6DF2C76754E4}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {78D85F23-7CB1-44A1-9238-6DF2C76754E4}.Debug|ARM.ActiveCfg = Debug|Win32 + {78D85F23-7CB1-44A1-9238-6DF2C76754E4}.Debug|ARM64.ActiveCfg = Debug|Win32 + {78D85F23-7CB1-44A1-9238-6DF2C76754E4}.Debug|x64.ActiveCfg = Debug|x64 + {78D85F23-7CB1-44A1-9238-6DF2C76754E4}.Debug|x64.Build.0 = Debug|x64 + {78D85F23-7CB1-44A1-9238-6DF2C76754E4}.Debug|x86.ActiveCfg = Debug|Win32 + {78D85F23-7CB1-44A1-9238-6DF2C76754E4}.Debug|x86.Build.0 = Debug|Win32 + {78D85F23-7CB1-44A1-9238-6DF2C76754E4}.Release|Any CPU.ActiveCfg = Release|Win32 + {78D85F23-7CB1-44A1-9238-6DF2C76754E4}.Release|ARM.ActiveCfg = Release|Win32 + {78D85F23-7CB1-44A1-9238-6DF2C76754E4}.Release|ARM64.ActiveCfg = Release|Win32 + {78D85F23-7CB1-44A1-9238-6DF2C76754E4}.Release|x64.ActiveCfg = Release|x64 + {78D85F23-7CB1-44A1-9238-6DF2C76754E4}.Release|x64.Build.0 = Release|x64 + {78D85F23-7CB1-44A1-9238-6DF2C76754E4}.Release|x86.ActiveCfg = Release|Win32 + {78D85F23-7CB1-44A1-9238-6DF2C76754E4}.Release|x86.Build.0 = Release|Win32 + {03EEF460-2F10-4FBE-AFFA-53477D3FC8D5}.Debug|Any CPU.ActiveCfg = Debug|x86 + {03EEF460-2F10-4FBE-AFFA-53477D3FC8D5}.Debug|ARM.ActiveCfg = Debug|x86 + {03EEF460-2F10-4FBE-AFFA-53477D3FC8D5}.Debug|ARM64.ActiveCfg = Debug|x86 + {03EEF460-2F10-4FBE-AFFA-53477D3FC8D5}.Debug|x64.ActiveCfg = Debug|x64 + {03EEF460-2F10-4FBE-AFFA-53477D3FC8D5}.Debug|x64.Build.0 = Debug|x64 + {03EEF460-2F10-4FBE-AFFA-53477D3FC8D5}.Debug|x86.ActiveCfg = Debug|x86 + {03EEF460-2F10-4FBE-AFFA-53477D3FC8D5}.Debug|x86.Build.0 = Debug|x86 + {03EEF460-2F10-4FBE-AFFA-53477D3FC8D5}.Release|Any CPU.ActiveCfg = Release|x86 + {03EEF460-2F10-4FBE-AFFA-53477D3FC8D5}.Release|ARM.ActiveCfg = Release|x86 + {03EEF460-2F10-4FBE-AFFA-53477D3FC8D5}.Release|ARM64.ActiveCfg = Release|x86 + {03EEF460-2F10-4FBE-AFFA-53477D3FC8D5}.Release|x64.ActiveCfg = Release|x64 + {03EEF460-2F10-4FBE-AFFA-53477D3FC8D5}.Release|x64.Build.0 = Release|x64 + {03EEF460-2F10-4FBE-AFFA-53477D3FC8D5}.Release|x86.ActiveCfg = Release|x86 + {03EEF460-2F10-4FBE-AFFA-53477D3FC8D5}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -216,6 +267,7 @@ Global {C6D580C5-7037-4733-B933-916FF400AFE2} = {6D41796B-9904-40B8-BBCB-40B2D1BAE44B} {FFA9A78B-F53F-43EE-AF87-24A80F4C330A} = {6D41796B-9904-40B8-BBCB-40B2D1BAE44B} {0A991D5F-BFEE-4D2F-9AAD-6AD06470A5DF} = {6D41796B-9904-40B8-BBCB-40B2D1BAE44B} + {03EEF460-2F10-4FBE-AFFA-53477D3FC8D5} = {6D41796B-9904-40B8-BBCB-40B2D1BAE44B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5AE8C9D7-2613-4E1A-A4F2-579BAC28D0A2} diff --git a/get_testwinrt.cmd b/get_testwinrt.cmd index 971973dc6..d78f9490d 100644 --- a/get_testwinrt.cmd +++ b/get_testwinrt.cmd @@ -12,7 +12,7 @@ git checkout -f master if ErrorLevel 1 popd & exit /b !ErrorLevel! git fetch -f if ErrorLevel 1 popd & exit /b !ErrorLevel! -git reset -q --hard 427f66c2cd0837e81005a840472b0ef1a1d8639d +git reset -q --hard 6d72afbcb51ab3981c6cd620d24954020f4d2bbc if ErrorLevel 1 popd & exit /b !ErrorLevel! echo Restoring Nuget ..\.nuget\nuget.exe restore