Skip to content

Commit

Permalink
Use Profile Guided Optimization(pgo) in dotnet core to PreJIT methods… (
Browse files Browse the repository at this point in the history
Azure#5338)

* Use Profile Guided Optimization(pgo) in dotnet core to PreJIT methods during placeholder mode.

Addresses Azure#5336

* Remove timestamps and sort the jittrace file

* Updating the .jittrace file

* Add PreJit for Regexes

* Update the jittrace file with more methods and remove regex optimization as it's not working.

* PR feedback to add new EventId when logging
  • Loading branch information
safihamid authored Dec 12, 2019
1 parent 0b21727 commit 1c4212d
Show file tree
Hide file tree
Showing 5 changed files with 3,784 additions and 1 deletion.
30 changes: 29 additions & 1 deletion src/WebJobs.Script.WebHost/Middleware/HostWarmupMiddleware.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs.Script.Extensions;
using Microsoft.Diagnostics.JitTrace;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;

namespace Microsoft.Azure.WebJobs.Script.WebHost.Middleware
Expand All @@ -15,25 +22,46 @@ public class HostWarmupMiddleware
private readonly IScriptWebHostEnvironment _webHostEnvironment;
private readonly IEnvironment _environment;
private readonly IScriptHostManager _hostManager;
private readonly ILogger _logger;

public HostWarmupMiddleware(RequestDelegate next, IScriptWebHostEnvironment webHostEnvironment, IEnvironment environment, IScriptHostManager hostManager)
public HostWarmupMiddleware(RequestDelegate next, IScriptWebHostEnvironment webHostEnvironment, IEnvironment environment, IScriptHostManager hostManager, ILogger<HostWarmupMiddleware> logger)
{
_next = next;
_webHostEnvironment = webHostEnvironment;
_environment = environment;
_hostManager = hostManager;
_logger = logger;
}

public async Task Invoke(HttpContext httpContext)
{
if (IsWarmUpRequest(httpContext.Request, _webHostEnvironment, _environment))
{
PreJitPrepare();

await WarmUp(httpContext.Request);
}

await _next.Invoke(httpContext);
}

private void PreJitPrepare()
{
// This is to PreJIT all methods captured in coldstart.jittrace file to improve cold start time
var path = Path.Combine(Path.GetDirectoryName(new Uri(typeof(HostWarmupMiddleware).Assembly.CodeBase).LocalPath), WarmUpConstants.PreJitFolderName, WarmUpConstants.JitTraceFileName);

var file = new FileInfo(path);

if (file.Exists)
{
JitTraceRuntime.Prepare(file, out int successfulPrepares, out int failedPrepares);

// We will need to monitor failed vs success prepares and if the failures increase, it means code paths have diverged or there have been updates on dotnet core side.
// When this happens, we will need to regenerate the coldstart.jittrace file.
_logger.LogInformation(new EventId(100, "PreJit"), $"PreJIT Successful prepares: {successfulPrepares}, Failed prepares: {failedPrepares}");
}
}

public async Task WarmUp(HttpRequest request)
{
if (request.Query.TryGetValue("restart", out StringValues value) && string.Compare("1", value) == 0)
Expand Down
273 changes: 273 additions & 0 deletions src/WebJobs.Script.WebHost/PreJIT/JitTraceRuntime.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace Microsoft.Diagnostics.JitTrace
{
public static class JitTraceRuntime
{
/// <summary>
/// When a jittrace entry caused a failure, it will call this event with the
/// line in the jittrace file that triggered the failure. "" will be passed for stream reading failures.
/// </summary>
public static event Action<string> LogFailure;

private static void LogOnFailure(string failure)
{
var log = LogFailure;
if (log != null)
{
log(failure);
}
}

/// <summary>
/// Prepare all the methods specified in a .jittrace file for execution
/// </summary>
/// <param name="fileName">Filename of .jittrace file</param>
/// <param name="successfulPrepares">count of successful prepare operations. May exceed the could of lines in the jittrace file due to fuzzy matching</param>
/// <param name="failedPrepares">count of failed prepare operations. May exceed the could of lines in the jittrace file due to fuzzy matching</param>
public static void Prepare(FileInfo fileName, out int successfulPrepares, out int failedPrepares)
{
using (StreamReader sr = new StreamReader(fileName.FullName))
{
Prepare(sr, out successfulPrepares, out failedPrepares);
}
}

private static string UnescapeStr(string input, string separator)
{
return input.Replace("\\s", separator).Replace("\\\\", "\\");
}

private static string[] SplitAndUnescape(string input, string separator, char[] seperatorCharArray)
{
string[] returnValue = input.Split(seperatorCharArray);
for (int i = 0; i < returnValue.Length; i++)
{
returnValue[i] = UnescapeStr(returnValue[i], separator);
}
return returnValue;
}

/// <summary>
/// Prepare all the methods specified string that matches the .jittrace file format
/// for execution. Useful for embedding via data via resource.
/// </summary>
/// <param name="jittraceString">string with .jittrace data</param>
/// <param name="successfulPrepares">count of successful prepare operations. May exceed the could of lines in the jittrace file due to fuzzy matching</param>
/// <param name="failedPrepares">count of failed prepare operations. May exceed the could of lines in the jittrace file due to fuzzy matching</param>
public static void Prepare(string jittraceString, out int successfulPrepares, out int failedPrepares)
{
MemoryStream strStream = new MemoryStream();
using (var writer = new StreamWriter(strStream, encoding: null, bufferSize: -1, leaveOpen: true))
{
writer.Write(jittraceString);
}

strStream.Position = 0;
Prepare(new StreamReader(strStream), out successfulPrepares, out failedPrepares);
}

/// <summary>
/// Prepare all the methods specified Stream that matches the .jittrace file format
/// for execution. Handles general purpose stream data.
/// </summary>
/// <param name="jittraceStream">Stream with .jittrace data</param>
/// <param name="successfulPrepares">count of successful prepare operations. May exceed the could of lines in the jittrace file due to fuzzy matching</param>
/// <param name="failedPrepares">count of failed prepare operations. May exceed the could of lines in the jittrace file due to fuzzy matching</param>
public static void Prepare(StreamReader jittraceStream, out int successfulPrepares, out int failedPrepares)
{
const string outerCsvEscapeChar = "~";
const string innerCsvEscapeChar = ":";
char[] outerCsvEscapeCharArray = new char[] { '~' };
char[] innerCsvEscapeCharArray = new char[] { ':' };
successfulPrepares = 0;
failedPrepares = 0;

while (true)
{
string methodString = string.Empty;
try
{
methodString = jittraceStream.ReadLine();
if (methodString == null)
{
break;
}
if (methodString.Trim() == string.Empty)
{
break;
}

string[] methodStrComponents = SplitAndUnescape(methodString, outerCsvEscapeChar, outerCsvEscapeCharArray);

Type owningType = Type.GetType(methodStrComponents[1], false);

// owningType failed to load above. Skip rest of method discovery
if (owningType == null)
{
failedPrepares++;
LogOnFailure(methodString);
continue;
}

int signatureLen = int.Parse(methodStrComponents[2]);
string[] methodInstantiationArgComponents = SplitAndUnescape(methodStrComponents[3], innerCsvEscapeChar, innerCsvEscapeCharArray);
int genericMethodArgCount = int.Parse(methodInstantiationArgComponents[0]);
Type[] methodArgs = genericMethodArgCount != 0 ? new Type[genericMethodArgCount] : Array.Empty<Type>();
bool abortMethodDiscovery = false;
for (int iMethodArg = 0; iMethodArg < genericMethodArgCount; iMethodArg++)
{
Type methodArg = Type.GetType(methodInstantiationArgComponents[1 + iMethodArg], false);
methodArgs[iMethodArg] = methodArg;

// methodArg failed to load above. Skip rest of method discovery
if (methodArg == null)
{
abortMethodDiscovery = true;
break;
}
}

if (abortMethodDiscovery)
{
failedPrepares++;
LogOnFailure(methodString);
continue;
}

string methodName = methodStrComponents[4];

// Now all data is parsed
// Find method
IEnumerable<RuntimeMethodHandle> membersFound;

BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly;
if (methodName == ".ctor")
{
if (genericMethodArgCount != 0)
{
// Ctors with generic args don't make sense
failedPrepares++;
LogOnFailure(methodString);
continue;
}
membersFound = CtorMethodsThatMatch();
IEnumerable<RuntimeMethodHandle> CtorMethodsThatMatch()
{
ConstructorInfo[] constructors = owningType.GetConstructors(bindingFlags);
foreach (ConstructorInfo ci in constructors)
{
ConstructorInfo returnConstructorInfo = null;

try
{
if (ci.GetParameters().Length == signatureLen)
{
returnConstructorInfo = ci;
}
}
catch
{
}
if (returnConstructorInfo != null)
{
yield return returnConstructorInfo.MethodHandle;
}
}
}
}
else if (methodName == ".cctor")
{
MemberInfo mi = owningType.TypeInitializer;
if (mi == null)
{
// This type no longer has a type initializer
failedPrepares++;
LogOnFailure(methodString);
continue;
}
membersFound = new RuntimeMethodHandle[] { owningType.TypeInitializer.MethodHandle };
}
else
{
membersFound = MethodsThatMatch();
IEnumerable<RuntimeMethodHandle> MethodsThatMatch()
{
MethodInfo[] methods = owningType.GetMethods(bindingFlags);
foreach (MethodInfo mi in methods)
{
MethodInfo returnMethodInfo = null;
try
{
if (mi.Name != methodName)
{
continue;
}

if (mi.GetParameters().Length != signatureLen)
{
continue;
}
if (mi.GetGenericArguments().Length != genericMethodArgCount)
{
continue;
}
if (genericMethodArgCount != 0)
{
returnMethodInfo = mi.MakeGenericMethod(methodArgs);
}
else
{
returnMethodInfo = mi;
}
}
catch
{
}

if (returnMethodInfo != null)
{
yield return returnMethodInfo.MethodHandle;
}
}
}
}

bool foundAtLeastOneEntry = false;
foreach (RuntimeMethodHandle memberHandle in membersFound)
{
foundAtLeastOneEntry = true;
try
{
System.Runtime.CompilerServices.RuntimeHelpers.PrepareMethod(memberHandle);
successfulPrepares++;
}
catch
{
failedPrepares++;
LogOnFailure(methodString);
}
}
if (!foundAtLeastOneEntry)
{
failedPrepares++;
LogOnFailure(methodString);
}
}
catch
{
failedPrepares++;
LogOnFailure(methodString);
}
}
}
}
}
Loading

0 comments on commit 1c4212d

Please sign in to comment.