Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,6 @@ StyleCop.Cache

# Ignore SelfSignedCertificate autogenerated files
test/tools/Modules/SelfSignedCertificate/

# BenchmarkDotNet artifacts
test/perf/BenchmarkDotNet.Artifacts/
3 changes: 3 additions & 0 deletions .spelling
Original file line number Diff line number Diff line change
Expand Up @@ -1309,3 +1309,6 @@ codesign
release-BuildJson
yml
centos-7
- test/perf/benchmarks/README.md
benchmarked
BenchmarkDotNet
2 changes: 2 additions & 0 deletions .vsts-ci/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ trigger:
- /.vsts-ci/misc-analysis.yml
- /.github/ISSUE_TEMPLATE/*
- /.dependabot/config.yml
- test/perf/*
pr:
branches:
include:
Expand All @@ -30,6 +31,7 @@ pr:
- .vsts-ci/windows.yml
- .vsts-ci/windows/*
- test/common/markdown/*
- test/perf/*
- tools/releaseBuild/*
- tools/releaseBuild/azureDevOps/templates/*

Expand Down
2 changes: 2 additions & 0 deletions .vsts-ci/mac.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ trigger:
- /.vsts-ci/misc-analysis.yml
- /.github/ISSUE_TEMPLATE/*
- /.dependabot/config.yml
- test/perf/*
pr:
branches:
include:
Expand All @@ -31,6 +32,7 @@ pr:
- /.vsts-ci/windows.yml
- /.vsts-ci/windows/*
- test/common/markdown/*
- test/perf/*
- tools/packaging/*
- tools/releaseBuild/*
- tools/releaseBuild/azureDevOps/templates/*
Expand Down
2 changes: 1 addition & 1 deletion .vsts-ci/misc-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ jobs:
condition: succeededOrFailed()

- bash: |
mdspell '**/*.md' '!**/Pester/**/*.md' --ignore-numbers --ignore-acronyms --report --en-us;
mdspell '**/*.md' '!**/Pester/**/*.md' '!**/dotnet-tools/**/*.md' --ignore-numbers --ignore-acronyms --report --en-us;
displayName: Test Spelling in Markdown
condition: succeededOrFailed()
workingDirectory: '$(repoPath)'
Expand Down
2 changes: 2 additions & 0 deletions .vsts-ci/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ trigger:
- /.vsts-ci/misc-analysis.yml
- /.github/ISSUE_TEMPLATE/*
- /.dependabot/config.yml
- test/perf/*
pr:
branches:
include:
Expand All @@ -28,6 +29,7 @@ pr:
- .github/ISSUE_TEMPLATE/*
- .vsts-ci/misc-analysis.yml
- test/common/markdown/*
- test/perf/*
- tools/packaging/*
- tools/releaseBuild/*
- tools/releaseBuild/azureDevOps/templates/*
Expand Down
13 changes: 1 addition & 12 deletions src/System.Management.Automation/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,13 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("powershell-tests,PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
[assembly: InternalsVisibleTo("powershell-perf,PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
Comment thread
daxian-dbw marked this conversation as resolved.
Outdated

[assembly: InternalsVisibleTo("Microsoft.Test.Management.Automation.GPowershell.Analyzers,PublicKey=00240000048000009400000006020000002400005253413100040000010001003f8c902c8fe7ac83af7401b14c1bd103973b26dfafb2b77eda478a2539b979b56ce47f36336741b4ec52bbc51fecd51ba23810cec47070f3e29a2261a2d1d08e4b2b4b457beaa91460055f78cc89f21cd028377af0cc5e6c04699b6856a1e49d5fad3ef16d3c3d6010f40df0a7d6cc2ee11744b5cfb42e0f19a52b8a29dc31b0")]

#if NOT_SIGNED
// These attributes aren't every used, it's just a hack to get VS to not complain
// about access when editing using the project files that don't actually build.
[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Commands.Utility")]
[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Commands.Management")]
[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Security")]
[assembly: InternalsVisibleTo(@"System.Management.Automation.Remoting")]
[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.ConsoleHost")]
#else
[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Commands.Utility" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Commands.Management" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Security" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
[assembly: InternalsVisibleTo(@"System.Management.Automation.Remoting" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.ConsoleHost" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
#endif

namespace System.Management.Automation
{
Expand Down
28 changes: 28 additions & 0 deletions test/perf/benchmarks/Categories.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace MicroBenchmarks
{
public static class Categories
{
/// <summary>
/// Benchmarks belonging to this category are executed for CI jobs.
/// </summary>
public const string Components = "Components";

/// <summary>
/// Benchmarks belonging to this category are executed for CI jobs.
/// </summary>
public const string Engine = "Engine";

/// <summary>
/// Benchmarks belonging to this category are targeting internal APIs.
/// </summary>
public const string Internal = "Internal";

/// <summary>
/// Benchmarks belonging to this category are targeting public APIs.
/// </summary>
public const string Public = "Public";
}
}
23 changes: 23 additions & 0 deletions test/perf/benchmarks/Engine.Parser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Management.Automation.Language;
using BenchmarkDotNet.Attributes;
using MicroBenchmarks;

namespace Engine
{
[BenchmarkCategory(Categories.Engine, Categories.Public)]
public class Parsing
{
[Benchmark]
public Ast UsingStatement()
{
const string Script = @"
using module moduleA
using Assembly assemblyA
using namespace System.IO";
return Parser.ParseInput(Script, out _, out _);
}
}
}
77 changes: 77 additions & 0 deletions test/perf/benchmarks/Engine.ScriptBlock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
using MicroBenchmarks;

namespace Engine
{
[BenchmarkCategory(Categories.Engine, Categories.Public)]
public class Scripting
{
private Runspace runspace;
private ScriptBlock scriptBlock;

private void SetupRunspace()
{
// Unless you want to run commands from any built-in modules, using 'CreateDefault2' is enough.
runspace = RunspaceFactory.CreateRunspace(InitialSessionState.CreateDefault2());
Comment thread
daxian-dbw marked this conversation as resolved.
runspace.Open();
Runspace.DefaultRunspace = runspace;
}

#region Invoke-Method

[ParamsSource(nameof(ValuesForScript))]
public string InvokeMethodScript { get; set; }

public IEnumerable<string> ValuesForScript()
{
yield return @"'String'.GetType()";
yield return @"[System.IO.Path]::HasExtension('')";

// Test on COM method invocation.
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
yield return @"$sh=New-Object -ComObject Shell.Application; $sh.Namespace('c:\')";
yield return @"$fs=New-Object -ComObject scripting.filesystemobject; $fs.Drives";
}
}

[GlobalSetup(Target = nameof(InvokeMethod))]
public void GlobalSetup()
{
SetupRunspace();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we make sure that the PowerShell / .NET telemetry is off.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good question. Maybe we should disable it and I will do that from the command line level.

scriptBlock = ScriptBlock.Create(InvokeMethodScript);

// Run it once to get the C# code jitted and the script compiled.
// The first call to this takes relatively too long, which makes the BDN's heuristic incorrectly
// believe that there is no need to run many ops in each interation. However, the subsequent runs
// of this method is much faster than the first run, and this causes 'MinIterationTime' warnings
// to our benchmarks and make the benchmark results not reliable.
// Calling this method once in 'GlobalSetup' is a workaround.
// See https://github.com/dotnet/BenchmarkDotNet/issues/837#issuecomment-828600157
scriptBlock.Invoke();
Comment thread
daxian-dbw marked this conversation as resolved.
}

[Benchmark]
public Collection<PSObject> InvokeMethod()
{
return scriptBlock.Invoke();
}

#endregion

[GlobalCleanup]
public void GlobalCleanup()
{
runspace.Dispose();
Runspace.DefaultRunspace = null;
}
}
}
56 changes: 56 additions & 0 deletions test/perf/benchmarks/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Extensions;

namespace MicroBenchmarks
{
public class Program
{
public static int Main(string[] args)
{
var argsList = new List<string>(args);
int? partitionCount;
int? partitionIndex;
List<string> exclusionFilterValue;
List<string> categoryExclusionFilterValue;
bool getDiffableDisasm;

// Parse and remove any additional parameters that we need that aren't part of BDN (BenchmarkDotnet)
try
{
CommandLineOptions.ParseAndRemoveIntParameter(argsList, "--partition-count", out partitionCount);
CommandLineOptions.ParseAndRemoveIntParameter(argsList, "--partition-index", out partitionIndex);
CommandLineOptions.ParseAndRemoveStringsParameter(argsList, "--exclusion-filter", out exclusionFilterValue);
CommandLineOptions.ParseAndRemoveStringsParameter(argsList, "--category-exclusion-filter", out categoryExclusionFilterValue);
CommandLineOptions.ParseAndRemoveBooleanParameter(argsList, "--disasm-diff", out getDiffableDisasm);

CommandLineOptions.ValidatePartitionParameters(partitionCount, partitionIndex);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

to be honest with you I don't think that you are going to need any of these custom arguments in the near future:

  • we have added --partition-count and --partition-index after our portfolio has reached three thousands of benchmarks and running all of them was taking 5 hours. The partitioning allows for distributing this work amongst many machines.
  • we have added --exclusion-filter and --category-exclusion-filter after WebAssembly (WASM) supports was added to .NET Runtime and it turned out that we could not run multithreaded benchmarks for WASM because it's single threaded by design.
  • --disasm-diff this is something that we have added just to simplify the process of exporting generated disassembly to our ReportingSystem. If you would also like to do that we should just add it to BenchmarkDotNet and remove the reflection hacks.

Copy link
Copy Markdown
Member Author

@daxian-dbw daxian-dbw Apr 26, 2021

Choose a reason for hiding this comment

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

That's good to know. As for the --disasm-diff flag, we'd like to generate the same reporting as .NET performance, so that sounds like something we may need.

and remove the reflection hacks.

where is the reflection hack?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The --partition-count and --partition-index sound useful to keep, though we won't need it for the foreseeable future. I hope we can also grow the number of our perf tests to the point that we need to use those 2 flags 😄

The --exclusion-filter and --category-exclusion-filter are also useful to us. We are planning to have Interal and Public categories, for benchmarks targeting the internal APIs and public APIs respectively. For benchmarks that targets the internal APIs, they won't run with the existing 7.0.x and 7.1.x PowerShell SDK packages because it requires InternalVisible attribute to be declared in the PowerShell assemblies. So we may need those 2 flags when running our benchmarks targeting the 7.0.x and 7.1.x packages.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

}
catch (ArgumentException e)
{
Console.WriteLine("ArgumentException: {0}", e.Message);
return 1;
}

return BenchmarkSwitcher
.FromAssembly(typeof(Program).Assembly)
.Run(
argsList.ToArray(),
RecommendedConfig.Create(
artifactsPath: new DirectoryInfo(Path.Combine(Path.GetDirectoryName(typeof(Program).Assembly.Location), "BenchmarkDotNet.Artifacts")),
mandatoryCategories: ImmutableHashSet.Create(Categories.Components, Categories.Engine),
partitionCount: partitionCount,
partitionIndex: partitionIndex,
exclusionFilterValue: exclusionFilterValue,
categoryExclusionFilterValue: categoryExclusionFilterValue,
getDiffableDisasm: getDiffableDisasm))
.ToExitCode();
}
}
}
88 changes: 88 additions & 0 deletions test/perf/benchmarks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
## Micro Benchmarks

This folder contains micro benchmarks that test the performance of PowerShell Engine.

### Requirement

1. A good suite of benchmarks
Something that measures only the thing that we are interested in and _produces accurate, stable and repeatable results_.
2. A set of machine with the same configurations.
3. Automation for regression detection.

### Design Decision

1. This project is internal visible to `System.Management.Automation`.
We want to be able to target some internal APIs to get measurements on specific scoped scenarios,
such as measuring the time to compile AST to a delegate by the compiler.
2. This project makes `ProjectReference` to other PowerShell assemblies.
This makes it easy to run benchmarks with the changes made in the codebase.
To run benchmarks with a specific version of PowerShell,
just replace the `ProjectReference` with a `PackageReference` to the `Microsoft.PowerShell.SDK` NuGet package of the corresponding version.

### Quick Start

You can run the benchmarks directly using `dotnet run` in this directory:
1. To run the benchmarks in Interactive Mode, where you will be asked which benchmark(s) to run:
```
dotnet run -c release
```

2. To list all available benchmarks ([read more](https://github.com/dotnet/performance/blob/main/docs/benchmarkdotnet.md#Listing-the-Benchmarks)):
```
dotnet run -c release --list [flat/tree]
```

3. To filter the benchmarks using a glob pattern applied to `namespace.typeName.methodName` ([read more](https://github.com/dotnet/performance/blob/main/docs/benchmarkdotnet.md#Filtering-the-Benchmarks)]):
```
dotnet run -c Release -f net6.0 --filter *script* --list flat
```

4. To profile the benchmarked code and produce an ETW Trace file ([read more](https://github.com/dotnet/performance/blob/main/docs/benchmarkdotnet.md#Profiling))
```
dotnet run -c Release -f net6.0 --filter *script* --profiler ETW
```

You can also use the function `Start-Benchmarking` from the module [`perf.psm1`](../perf.psm1) to run the benchmarks:
```powershell
Start-Benchmarking [[-TargetPSVersion] <string>] [[-List] <string>] [[-Filter] <string[]>] [[-Artifacts] <string>] [-KeepFiles] [<CommonParameters>]
```
Run `Get-Help Start-Benchmarking -Full` to see the description of each parameter.

### Regression Detection
Comment thread
daxian-dbw marked this conversation as resolved.

We use the tool [`ResultsComparer`](../dotnet-tools/ResultsComparer) to compare the provided benchmark results.
See the [README.md](../dotnet-tools/ResultsComparer/README.md) for `ResultsComparer` for more details.

The module `perf.psm1` also provides `Compare-BenchmarkResult` that wraps `ResultsComparer`.
Here is an example of using it:

```
## Run benchmarks targeting the current code base
PS:1> Start-Benchmarking -Filter *script* -Artifacts C:\arena\tmp\BenchmarkDotNet.Artifacts\current\

## Run benchmarks targeting the 7.1.3 version of PS package
PS:2> Start-Benchmarking -Filter *script* -Artifacts C:\arena\tmp\BenchmarkDotNet.Artifacts\7.1.3 -TargetPSVersion 7.1.3

## Compare the results using 5% threshold
PS:3> Compare-BenchmarkResult -BaseResultPath C:\arena\tmp\BenchmarkDotNet.Artifacts\7.1.3\ -DiffResultPath C:\arena\tmp\BenchmarkDotNet.Artifacts\current\ -Threshold 1%
summary:
better: 4, geomean: 1.057
total diff: 4

No Slower results for the provided threshold = 1% and noise filter = 0.3ns.

| Faster | base/diff | Base Median (ns) | Diff Median (ns) | Modality|
| -------------------------------------------------------------------------------- | ---------:| ----------------:| ----------------:| --------:|
| Engine.Scripting.InvokeMethod(Script: "$fs=New-Object -ComObject scripting.files | 1.07 | 50635.77 | 47116.42 | |
| Engine.Scripting.InvokeMethod(Script: "$sh=New-Object -ComObject Shell.Applicati | 1.07 | 1063085.23 | 991602.08 | |
| Engine.Scripting.InvokeMethod(Script: "'String'.GetType()") | 1.06 | 1329.93 | 1252.51 | |
| Engine.Scripting.InvokeMethod(Script: "[System.IO.Path]::HasExtension('')") | 1.02 | 1322.04 | 1297.72 | |

No file given
```

## References

- [Getting started with BenchmarkDotNet](https://benchmarkdotnet.org/articles/guides/getting-started.html)
- [Micro-benchmark Design Guidelines](https://github.com/dotnet/performance/blob/main/docs/microbenchmark-design-guidelines.md)
- [Adam SITNIK: Powerful benchmarking in .NET](https://www.youtube.com/watch?v=pdcrSG4tOLI&t=351s)
Loading