Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GenericEnumeration JIT benchmark #1

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
7 changes: 6 additions & 1 deletion src/jit/morph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5660,10 +5660,15 @@ GenTreePtr Compiler::fgMorphField(GenTreePtr tree, MorphAddrContext* ma
/// This is left here to point out how to implement it.
#define CONSERVATIVE_NULL_CHECK_BYREF_CREATION 1

CORINFO_CLASS_HANDLE fieldClass;
bool fieldTypeIsValueClassWithOnlyZeroOffsetFields =
info.compCompHnd->getFieldType(symHnd, &fieldClass) == CORINFO_TYPE_VALUECLASS &&
info.compCompHnd->getClassSize(fieldClass) == 1;

// If the objRef is a GT_ADDR node, it, itself, never requires null checking. The expression
// whose address is being taken is either a local or static variable, whose address is necessarily
// non-null, or else it is a field dereference, which will do its own bounds checking if necessary.
if (objRef->gtOper != GT_ADDR
if (objRef->gtOper != GT_ADDR && !fieldTypeIsValueClassWithOnlyZeroOffsetFields
&& ((mac->m_kind == MACK_Addr || mac->m_kind == MACK_Ind)
&& (!mac->m_allConstantOffsets
|| fgIsBigOffset(mac->m_totalOffset + fldOffset)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Xunit.Performance;
using System.Runtime.CompilerServices;
using Xunit;
using System.Collections;
using System.Diagnostics;
using System.Threading;

[assembly: OptimizeForBenchmarks]
[assembly: MeasureInstructionsRetired]

public static class Template {

#if DEBUG
private const int Iterations = 1;
#else
private const int Iterations = 10000;
#endif
private const int CollectionCount = 50000;

private interface IGenericEnumerator<TCollection, TPosition, T> {
int GetVersion(TCollection collection);
TPosition StepForward(TPosition position);
T GetValue(TCollection collection, TPosition position);
bool Equals(TPosition lhs, TPosition rhs);
}

private struct GenericEnumerator<TOperations, TCollection, TPosition, T> : IEnumerator<T>
where TOperations : struct, IGenericEnumerator<TCollection, TPosition, T> {

private TOperations m_operations;
private TCollection m_collection;
private int m_version;
private T m_current;
private TPosition m_position;
private TPosition m_begin;
private TPosition m_end;

internal GenericEnumerator(TCollection collection, TPosition begin, TPosition end) : this() {
m_operations = default(TOperations);
m_collection = collection;
m_begin = begin;
m_end = end;
Reset();
}

private bool MoveLast() {
if (m_operations.GetVersion(m_collection) != m_version)
throw new InvalidOperationException();

m_current = default(T);
return false;
}

// jit dump: https://gist.github.com/kingces95/b0281b3d54f1c361fad63b3ab622c568
public bool MoveNext() {
var position = m_position;

// For a given path through a method I expect at most one null check of the `this` pointer
// however RyuJIT is actually generating subsequent checks so, in this benchmark, the gains
// of inlining calls on m_operations are (somewhat) offset by those extra null this checks.

if (m_operations.GetVersion(m_collection) == m_version && !m_operations.Equals(position, m_end)) {
//cmp dword ptr [rsi], esi <-- expected null check
//mov rcx, gword ptr [rsi]
//cmp dword ptr [rsi+8], 0
//jne SHORT G_M61072_IG04
//cmp dword ptr [rsi], esi <-- unexpected null check
//mov ecx, dword ptr [rsi+24]
//cmp eax, ecx
//je SHORT G_M61072_IG04

m_current = m_operations.GetValue(m_collection, position);
//cmp dword ptr [rsi], esi <-- unexpected null check
//mov rcx, gword ptr [rsi]
//cmp eax, dword ptr [rcx+8]
//jae SHORT G_M61072_IG06
//movsxd rdx, eax
//mov ecx, dword ptr [rcx+4*rdx+16]
//mov dword ptr [rsi+12], ecx

m_position = m_operations.StepForward(position);
//cmp dword ptr [rsi], esi <-- unexpected null check
//inc eax
//mov dword ptr [rsi+16], eax

return true;
}

return MoveLast();
}

public T Current => m_current;
object IEnumerator.Current => m_current;

public void Reset() {
m_current = default(T);
m_version = m_operations.GetVersion(m_collection);
m_position = m_begin;
}
public void Dispose() {
Reset();
m_version = -1;
}
}

private struct ArrayEnumerator<T> : IGenericEnumerator<T[], int, T> {
public bool Equals(int lhs, int rhs) => lhs == rhs;
public T GetValue(T[] collection, int position) => collection[position];
public int GetVersion(T[] collection) => 0;
public int StepForward(int iterator) => iterator + 1;
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static void Bench<TOperations, TCollection, TPosition, T>(
GenericEnumerator<TOperations, TCollection, TPosition, T> enumerator)
where TOperations : struct, IGenericEnumerator<TCollection, TPosition, T> {

while (enumerator.MoveNext())
continue;
}

private static void Bench() {
var collection = new int[CollectionCount];
var enumerator = new GenericEnumerator<ArrayEnumerator<int>, int[], int, int>(
collection: collection,
begin: 0,
end: CollectionCount
);

Bench(enumerator);
}

[Benchmark]
public static void Test() {
foreach (var iteration in Benchmark.Iterations) {
using (iteration.StartMeasurement()) {
for (int i = 0; i < Iterations; i++)
Bench();
}
}
}

public static int Main() {

for (int i = 0; i < Iterations; i++)
Bench();

return 100;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{225C3ED1-5309-4F25-A04F-3E62A44B975E}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\11.0\UITestExtensionPackages</ReferencePath>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
<NuGetPackageImportStamp>7a9bfb7d</NuGetPackageImportStamp>
</PropertyGroup>
<!-- Default configurations to help VS understand the configurations -->
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
</PropertyGroup>
<ItemGroup>
<CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
<Visible>False</Visible>
</CodeAnalysisDependentAssemblyPaths>
</ItemGroup>
<ItemGroup>
<None Include="$(JitPackagesConfigFileDirectory)benchmark\project.json" />
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<ItemGroup>
<Compile Include="Benchmark.cs" />
</ItemGroup>
<PropertyGroup>
<ProjectJson>$(JitPackagesConfigFileDirectory)benchmark\project.json</ProjectJson>
<ProjectLockJson>$(JitPackagesConfigFileDirectory)benchmark\project.lock.json</ProjectLockJson>
</PropertyGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
<PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' ">
</PropertyGroup>
</Project>