Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

more love from BenchmarkDotNet to CoreFX #27164

Merged
merged 4 commits into from
Mar 19, 2018
Merged

Conversation

adamsitnik
Copy link
Member

To tell the long story short: I implemented new toolchain for BenchmarkDotNet which generates a self-contained app which can target ANY CoreFX and CoreCLR builds. I combined the knowledge from dogfooding docs from corefx and coreclr. dotnet cli does the job for us, it's just a matter of generating the right .csproj and NuGet.config files.

So you can now:

  1. Benchmark local CoreFX builds
  2. Benchmark nightly CoreFX builds
  3. Benchmark local CoreCLR builds
  4. Benchmark nightly CoreCLR builds
  5. Mix them all together
  6. Compare them
  7. Use Disassembly Diagnoser to get the generated assembly code

To use the new feature you need to add our CI feed to your NuGet.config file and download version 0.10.12.433 or newer.

<packageSources>
  <add key="bdn-CI" value="https://ci.appveyor.com/nuget/benchmarkdotnet" />
</packageSources>

Simple example:

public class LocalCoreFxConfig : ManualConfig
{
	public LocalCoreFxConfig()
	{
		Add(Job.ShortRun.With(CustomCoreClrToolchain.CreateForLocalCoreFxBuild(
			@"C:\Projects\forks\corefx\bin\packages\Release",
			"4.5.0-preview2-26313-0")));

		Add(DefaultConfig.Instance.GetExporters().ToArray());
		Add(DefaultConfig.Instance.GetLoggers().ToArray());
		Add(DefaultConfig.Instance.GetColumnProviders().ToArray());

		Add(DisassemblyDiagnoser.Create(new DisassemblyDiagnoserConfig(printAsm: true, recursiveDepth: 2)));
	}
}

class Program
{
    static void Main(string[] args) 
        => BenchmarkSwitcher.FromAssembly(typeof(Program).GetTypeInfo().Assembly)
            .Run(args, new LocalCoreFxConfig());
}

Comparison:

Add(Job.Default.With(CustomCoreClrToolchain.CreateForNightlyCoreFxBuild("4.5.0-preview2-26214-01", displayName: "before my change")));
Add(Job.Default.With(CustomCoreClrToolchain.CreateForNightlyCoreFxBuild("4.5.0-preview2-26215-01", displayName: "after my change")));

Complex example:

public class LocalCoreClrConfig : ManualConfig
{
	public LocalCoreClrConfig()
	{
		Add(Job.ShortRun.With(
			new CustomCoreClrToolchain(
				"local builds",
				coreClrNuGetFeed: @"C:\Projects\forks\coreclr\bin\Product\Windows_NT.x64.Release\.nuget\pkg",
				coreClrVersion: "2.1.0-preview2-26313-0",
				coreFxNuGetFeed: @"C:\Projects\forks\corefx\bin\packages\Release",
				coreFxVersion: "4.5.0-preview2-26313-0")
		));

		Add(Job.ShortRun.With(
			new CustomCoreClrToolchain(
				"local coreclr myget corefx",
				coreClrNuGetFeed: @"C:\Projects\forks\coreclr\bin\Product\Windows_NT.x64.Release\.nuget\pkg",
				coreClrVersion: "2.1.0-preview2-26313-0",
				coreFxNuGetFeed: "https://dotnet.myget.org/F/dotnet-core/api/v3/index.json",
				coreFxVersion: "4.5.0-preview2-26215-01")
		));

		Add(Job.ShortRun.With(
			new CustomCoreClrToolchain(
				"myget coreclr local corefx",
				coreClrNuGetFeed: "https://dotnet.myget.org/F/dotnet-core/api/v3/index.json",
				coreClrVersion: "2.1.0-preview2-26214-07",
				coreFxNuGetFeed: @"C:\Projects\forks\corefx\bin\packages\Release",
				coreFxVersion: "4.5.0-preview2-26313-0")
		));

		Add(Job.ShortRun.With(
			new CustomCoreClrToolchain(
				"myget builds",
				coreClrNuGetFeed: "https://dotnet.myget.org/F/dotnet-core/api/v3/index.json",
				coreClrVersion: "2.1.0-preview2-26214-07",
				coreFxNuGetFeed: "https://dotnet.myget.org/F/dotnet-core/api/v3/index.json",
				coreFxVersion: "4.5.0-preview2-26215-01")
		));

		// the rest of the config..
	}
}

The output is going to contain exact CoreCLR and CoreFX versions used:

BenchmarkDotNet=v0.10.12.20180215-develop, OS=Windows 10 Redstone 3 [1709, Fall Creators Update] (10.0.16299.192)
Intel Core i7-3687U CPU 2.10GHz (Ivy Bridge), 1 CPU, 4 logical cores and 2 physical cores
Frequency=2533308 Hz, Resolution=394.7408 ns, Timer=TSC
.NET Core SDK=2.1.300-preview2-008162
  [Host]     : .NET Core 2.0.5 (CoreCLR 4.6.26020.03, CoreFX 4.6.26018.01), 64bit RyuJIT
  Job-DHYYZE : .NET Core ? (CoreCLR 4.6.26313.0, CoreFX 4.6.26313.0), 64bit RyuJIT
  Job-VGTPFY : .NET Core ? (CoreCLR 4.6.26313.0, CoreFX 4.6.26215.01), 64bit RyuJIT
  Job-IYZFNW : .NET Core ? (CoreCLR 4.6.26214.07, CoreFX 4.6.26215.01), 64bit RyuJIT
  Job-CTQFFQ : .NET Core ? (CoreCLR 4.6.26214.07, CoreFX 4.6.26313.0), 64bit RyuJIT

Corresponding change in BenchmarkDotNet dotnet/BenchmarkDotNet#651

@ViktorHofer @benaadams @stephentoub @JosephTremoulet @maryamariyan @AndyAyersMS

I am open to feedback and willing to improve/change the feature!

@adamsitnik adamsitnik added the documentation Documentation bug or enhancement, does not impact product or test code label Feb 15, 2018
@ViktorHofer
Copy link
Member

Great work Adam. I believe these changes will be beneficial to a lot of our contributors.

Is it somehow possible to benchmark against the local coreclr/corefx build without consuming the nuget packages? In my inner loop scenario I just recompile a single assembly over and over again (e.g. System.Text.RegularExpressions.dll) and don't want to create a whole nuget package with all the coreclr/corefx binaries in it and invoke the .\build script everytime I do a change.

The ideal situation for me would be to do the following:

  1. In-Proc job (e.g. invoked by CoreRun or dotnet toolchain). Here I'm testing with my change directly in the release folder in corefx\bin\runtime...release.... I do compile my cs file with corerun and csc to a dll and the invoke that by CoreRun without any csproj file. For me that's the fastest way to benchmark.
  2. Out of proc job that uses a custom dotnet cli path to a dotnet.exe somewhere on my harddrive with it's own shared runtime (in my case a fresh 2.1 nightly sdk from master). With that job I measure performance without my change.

This is just a suggestion for how you make my life much easier. I'm still very happy to about your changes 👍

@adamsitnik
Copy link
Member Author

@ViktorHofer you should rather not compare in-process vs out-process (too many things can be different)

but speaking of the rebuild: I understand the problem and agree that the solution is not perfect. However, I don't have an idea yet. In general, if this is possible with dotnet cli, then it's possible with BenchmarkDotNet.

My first two ideas:

  1. Add possibility to add/generate a post-publish script which would copy desired files to published app (the toolchain is designed for CoreFX so it can do some tailored things)
  2. Use [KeepBenchmarkFiles] and after each rebuild copy the desired dll to published app and re-run the benchmark by running dotnet mybenchmark.dll

I did not use msbuild/csc & CoreRun to keep things simple (the change was less than 200 lines of code) and less error-prone.

@ViktorHofer
Copy link
Member

cc @danmosemsft @eerhardt as we just talked about this in our standup

@stephentoub
Copy link
Member

Nice. Thanks, @adamsitnik.

@AndyAyersMS
Copy link
Member

Yes, this is very cool.

How hard would it be for BDN to consume and do reasonable things with tests that contain xunit-performance attributes? That way we could still author perf tests like we do now but run them under either perf harness.

@AndreyAkinshin
Copy link
Member

@AndyAyersMS, it should not be hard. In fact, the implementation of BenchmarkRunner.Run is pretty simple:

public static Summary Run<T>(IConfig config = null) =>
    BenchmarkRunnerCore.Run(BenchmarkConverter.TypeToBenchmarks(typeof(T), config), ToolchainExtensions.GetToolchain);

public static Summary Run(Type type, IConfig config = null) =>
    BenchmarkRunnerCore.Run(BenchmarkConverter.TypeToBenchmarks(type, config), ToolchainExtensions.GetToolchain);

public static Summary Run(Type type, MethodInfo[] methods, IConfig config = null) =>
    BenchmarkRunnerCore.Run(BenchmarkConverter.MethodsToBenchmarks(type, methods, config), ToolchainExtensions.GetToolchain);

BenchmarkConverter just takes method attributes and transform it to BenchmarkRunInfo which contains information about how to run the benchmarks without any knowledge about original attributes (see source code:
https://github.com/dotnet/BenchmarkDotNet/blob/v0.10.12/src/BenchmarkDotNet.Core/Running/BenchmarkConverter.cs)
It's pretty easy to make it pluggable and write XunitPerformanceBenchmarkConverter which transforms xunit-performance attributes to the BenchmarkDotNet represtnation.
I like this idea because it will allow to run all existed xunit benchmarks on BenchmarkDotNet without massive modifications of the source code.

@adamsitnik
Copy link
Member Author

@ViktorHofer what is the fastest way to rebuild a part of corefx that developer is working on? do you always run build.cmd[sh] and it does the incremental build? or do you build the VS solution?

I was wondering if the simplest way to run benchmarks after small code change would not be just copy+overwrite of selected files after BDN does dotnet publish.

From the user perspective it would be sth like:

Add(Job.ShortRun.With(CustomCoreClrToolchain.CreateForLocalCoreFxBuild(
    pathToNuGetFolder: @"C:\Projects\forks\corefx\bin\packages\Release", 
    privateCoreFxNetCoreAppVersion: "4.5.0-preview2-26313-0",
    filesToCopy: new [] {
        @"C:\Projects\forks\corefx\bin\AnyOS.AnyCPU.Release\System.Text.RegularExpressions\netcoreapp\System.Text.RegularExpressions.dll"
    })));

or

Add(Job.ShortRun.With(CustomCoreClrToolchain.CreateForLocalCoreFxBuild(
    pathToNuGetFolder: @"C:\Projects\forks\corefx\bin\packages\Release", 
    privateCoreFxNetCoreAppVersion: "4.5.0-preview2-26313-0",
    filesToCopy: new [] {
        typeof(Regex).Assembly.Location
    })));

@stephentoub
Copy link
Member

@adamsitnik, a developer working on a specific library generally just builds that library and runs the tests for that library. Once you've built the whole repo once, you can then build and test individual projects, e.g.

cd src\System.IO.FileSystem\src
msbuild /t:rebuild
cd ..\tests
msbuild /t:rebuildandtest

@adamsitnik
Copy link
Member Author

@stephentoub thanks!

I have written following benchmark:

public class RegexBenchmarks
{
    string pattern = @"^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$";
    string[] partNos = { "A1C$", "A4", "A4$", "A1603D$", "A1603D#" };

    [Benchmark]
    public int Sample()
    {
        int result = 0;

        foreach (var input in partNos)
            if (Regex.IsMatch(input, pattern))
                result++;

        return result;
    }
}

Modified the Regex.IsMatch by adding some dummy code to slow it down:

public static bool IsMatch(string input, string pattern)
{
    int slow = 0;

    for (int i = 0; i < 10000; i++)
        slow += i;

    return IsMatch(input, pattern, RegexOptions.None, DefaultMatchTimeout) && slow != 1345;
}

And rebuilded it as explained by Stephen: msbuild /t:rebuild /p:Configuration=Release

After this I defined custom config:

public class LocalCoreFxConfig : ManualConfig
{
	public LocalCoreClrConfig()
	{
		Add(Job
			.ShortRun
			.With(CustomCoreClrToolchain.CreateForLocalCoreFxBuild(
				pathToNuGetFolder: @"C:\Projects\forks\corefx\bin\packages\Release", 
				privateCoreFxNetCoreAppVersion: "4.5.0-preview2-26313-0",
				displayName: "before"))
			.AsBaseline()
			.WithId("before"));

		Add(Job
			.ShortRun
			.With(CustomCoreClrToolchain.CreateForLocalCoreFxBuild(
				pathToNuGetFolder: @"C:\Projects\forks\corefx\bin\packages\Release", 
				privateCoreFxNetCoreAppVersion: "4.5.0-preview2-26313-0",
				displayName: "after",
				filesToCopy: new [] {
					@"c:\Projects\forks\corefx\bin\AnyOS.AnyCPU.Release\System.Text.RegularExpressions\netcoreapp\System.Text.RegularExpressions.dll"
				}))
			.WithId("after"));

		KeepBenchmarkFiles = true;
		
		// rest of the config..
	}
}

And run the benchmark:

BenchmarkDotNet=v0.10.12.20180216-develop, OS=Windows 10 Redstone 3 [1709, Fall Creators Update] (10.0.16299.248)
Intel Core i7-3687U CPU 2.10GHz (Ivy Bridge), 1 CPU, 4 logical cores and 2 physical cores
Frequency=2533321 Hz, Resolution=394.7388 ns, Timer=TSC
.NET Core SDK=2.1.300-preview2-008162
  [Host] : .NET Core 2.0.5 (CoreCLR 4.6.26020.03, CoreFX 4.6.26018.01), 64bit RyuJIT
  after  : .NET Core ? (CoreCLR 4.6.26130.05, CoreFX 4.6.26314.0), 64bit RyuJIT
  before : .NET Core ? (CoreCLR 4.6.26130.05, CoreFX 4.6.26313.0), 64bit RyuJIT

LaunchCount=1  TargetCount=3  WarmupCount=3  
Method Job Toolchain IsBaseline Mean Error StdDev Scaled ScaledSD
Sample after after Default 35.077 us 3.363 us 0.1900 us 8.64 0.15
Sample before before True 4.060 us 1.465 us 0.0828 us 1.00 0.00

The result shows the difference, but to verify that the new feature work I verified the files in publish folders of both apps (if you set KeepBenchmarkFiles=true BDN does not remove them)

image

moreover, we can check the produced asm:

System.Text.RegularExpressions.Regex.IsMatch(System.String, System.String)	
   mov     rcx,7FF7E1837B88h
   mov     edx,6
   call    coreclr!coreclr_execute_assembly+0x18580
   mov     rcx,1FE36946C88h
   mov     rcx,qword ptr [rcx]
   mov     rbx,qword ptr [rcx+8]
   mov     rcx,7FF7E1838420h
   call    coreclr!coreclr_execute_assembly+0x18180
   mov     rbp,rax
   mov     dword ptr [rsp+20h],1
   mov     rcx,rbp
   mov     rdx,rdi
   mov     r9,rbx
   xor     r8d,r8d
   call    System.Text.RegularExpressions.Regex..ctor(System.String, System.Text.RegularExpressions.RegexOptions, System.TimeSpan, Boolean)
   mov     rcx,rbp
   mov     rdx,rsi
   mov     rax,7FF7E1749238h
System.Text.RegularExpressions.Regex.IsMatch(System.String, System.String)	
   xor     ebx,ebx
   xor     ecx,ecx
M01_L00
   add     ebx,ecx
   inc     ecx
   cmp     ecx,2710h
   jl      M01_L00
   mov     rcx,7FF7E1837740h
   mov     edx,6
   call    coreclr!coreclr_execute_assembly+0x18580
   mov     rcx,2ACD13B6C88h
   mov     rcx,qword ptr [rcx]
   mov     rbp,qword ptr [rcx+8]
   mov     rcx,7FF7E1837FD8h
   call    coreclr!coreclr_execute_assembly+0x18180
   mov     r14,rax
   mov     dword ptr [rsp+20h],1
   mov     rcx,r14
   mov     rdx,rdi
   mov     r9,rbp
   xor     r8d,r8d
   call    System.Text.RegularExpressions.Regex..ctor(System.String, System.Text.RegularExpressions.RegexOptions, System.TimeSpan, Boolean)
   mov     rcx,r14
   mov     rdx,rsi
   call    System.Text.RegularExpressions.Regex.IsMatch(System.String)
   test    al,al
   je      M01_L01
   cmp     ebx,541h
   setne   al
   movzx   eax,al
   add     rsp,30h
   pop     rbx
   pop     rbp
   pop     rsi
   pop     rdi
   pop     r14
   ret
M01_L01
   xor     eax,eax

@stephentoub @ViktorHofer Is adding the ugly (but working) filesToCopy acceptable? Would it help you with perf improvements during development?

Btw I have improved the perf of BechmarkDotNet for 0.10.12, it took only Total time: 00:00:25 (25.97 sec) on my small Dell XPS 13 to measure the diff and get the asm. (more details dotnet/BenchmarkDotNet#550)

@ViktorHofer
Copy link
Member

ViktorHofer commented Feb 16, 2018

Amazing, I will check it out over the weekend 👍

As Stephen stated we build individual projects like (after doing a full build):

cd ~\git\corefx\src\System.Text.RegularExpressions\src
msbuild /t:Rebuild /p:ConfigurationGroup=Release

And the path you posted can even be shortened, as we copy all the assemblies into the runtime folder. But that's just a config setting.

~\git\corefx\bin\runtime\netcoreapp-Windows_NT-Release-x64\System.Text.RegularExpressions.dll.

I think your changes make sense and could help us a lot. As you pointed out, the filesToCopy feature isn't ideal but maybe there are also other use cases for it outside of corefx/coreclr.

@danmoseley
Copy link
Member

Instead of /t:rebuild you can do /t:build so it doesn't clean and /p:forceruntests if you want to rerun them without product changes.

@ViktorHofer
Copy link
Member

@danmosemsft are you talking about msbuild in a project folder or about the build script in root?

@danmoseley
Copy link
Member

@ViktorHofer MSBuild in a project folder.

@adamsitnik
Copy link
Member Author

@eerhardt I updated the docs, there is no need to create a dedicated config type

@adamsitnik
Copy link
Member Author

@ViktorHofer did you have some time to try it?

@ViktorHofer
Copy link
Member

Sorry Adam, haven't had time yet. I'll reserve some time for it today.

@ViktorHofer
Copy link
Member

ViktorHofer commented Mar 1, 2018

As discussed with Adam offline this currently doesn't work for me as I get some weird dotnet restore errors that shouldn't occur because of the PackageConflictPreferredPackages property but my corefx build could be faulted. I will try again and will let Adam know by tomorrow.

@karelz karelz added area-Infrastructure-libraries and removed documentation Documentation bug or enhancement, does not impact product or test code labels Mar 7, 2018
@karelz
Copy link
Member

karelz commented Mar 12, 2018

@adamsitnik what is status of the PR? Is it blocked on further validation? (all of it vs. just a part?)

@adamsitnik
Copy link
Member Author

@karelz it works on my dev box and both VMs I possess, but I still need somebody to test the feature that I have implemented in BenchmarkDotNet before I merge the docs change.

@karelz
Copy link
Member

karelz commented Mar 18, 2018

@adamsitnik maybe it would be best to just check it in (it is "just" documentation after all, and moreover it works for you at least).
If it doesn't work for someone else later on, they can file issue/questions ... maybe it is better than waiting way too long for additional validation? Just my 2 cents (without any knowledge what exactly you're changing 😉) ...

@adamsitnik adamsitnik merged commit 969541f into dotnet:master Mar 19, 2018
@adamsitnik
Copy link
Member Author

@karelz good point! I just merged it.

@karelz karelz added this to the 2.1.0 milestone Mar 27, 2018
ericstj pushed a commit to ericstj/corefx that referenced this pull request Mar 28, 2018
* more love from BenchmarkDotNet to CoreFX

* describe the idea of copying extra files

* don't require the users to create a dedicated config type

* update the version number
@adamsitnik adamsitnik deleted the bdnDocs branch June 17, 2018 15:44
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
* more love from BenchmarkDotNet to CoreFX

* describe the idea of copying extra files

* don't require the users to create a dedicated config type

* update the version number


Commit migrated from dotnet/corefx@969541f
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants