Skip to content

Commit

Permalink
Speed up linker by 10% (#1130)
Browse files Browse the repository at this point in the history
...using one weird trick.

Cecil uses file I/O to read assemblies, but for our use cases this means we're spending a ton of time context switching between kernel mode and user mode.

Memory mapped I/O is more efficient for these access patterns.

The change is bigger than I would want it to be because:
* Loading assemblies from disk is not centralized (and in fact there are more extensibility pointer that encourage everyone to do their own loading)
* Cecil violates OOP principles by giving certain kinds of streams preferential treatment (`ModuleDefinition.FileName` is useless if the assembly wasn't opened with the blessed stream kind and Cecil doesn't have a way to provide it through other means).
  • Loading branch information
MichalStrehovsky authored Apr 23, 2020
1 parent 17b3c01 commit f5a9875
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 7 deletions.
4 changes: 2 additions & 2 deletions src/linker/Linker.Steps/OutputStep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,9 @@ static string GetConfigFile (string assembly)
return assembly + ".config";
}

static FileInfo GetOriginalAssemblyFileInfo (AssemblyDefinition assembly)
FileInfo GetOriginalAssemblyFileInfo (AssemblyDefinition assembly)
{
return new FileInfo (assembly.MainModule.FileName);
return new FileInfo (Context.Resolver.GetAssemblyFileName (assembly));
}

protected virtual void CopyAssembly (AssemblyDefinition assembly, string directory)
Expand Down
23 changes: 22 additions & 1 deletion src/linker/Linker/AssemblyResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,20 @@ AssemblyDefinition GetAssembly (string file, ReaderParameters parameters)
}
#endif

public string GetAssemblyFileName(AssemblyDefinition assembly)
{
#if FEATURE_ILLINK
if (assemblyToPath.TryGetValue(assembly, out string path)) {
return path;
}
else
#endif
{
// Must be an assembly that we didn't open through the resolver
return assembly.MainModule.FileName;
}
}

AssemblyDefinition ResolveFromReferences (AssemblyNameReference name, Collection<string> references, ReaderParameters parameters)
{
var fileName = name.Name + ".dll";
Expand All @@ -99,6 +113,11 @@ AssemblyDefinition ResolveFromReferences (AssemblyNameReference name, Collection
return null;
}

public AssemblyDefinition ResolveFromPath(string path, ReaderParameters parameters)
{
return CacheAssembly (GetAssembly (path, parameters));
}

public override AssemblyDefinition Resolve (AssemblyNameReference name, ReaderParameters parameters)
{
// Validate arguments, similarly to how the base class does it.
Expand Down Expand Up @@ -133,7 +152,7 @@ public override AssemblyDefinition Resolve (AssemblyNameReference name, ReaderPa
public virtual AssemblyDefinition CacheAssembly (AssemblyDefinition assembly)
{
_assemblies [assembly.Name.Name] = assembly;
base.AddSearchDirectory (Path.GetDirectoryName (assembly.MainModule.FileName));
base.AddSearchDirectory (Path.GetDirectoryName (GetAssemblyFileName(assembly)));
return assembly;
}

Expand All @@ -151,6 +170,8 @@ protected override void Dispose (bool disposing)
_assemblies.Clear ();
if (_unresolvedAssemblies != null)
_unresolvedAssemblies.Clear ();

base.Dispose (disposing);
}
}
}
35 changes: 34 additions & 1 deletion src/linker/Linker/DirectoryAssemblyResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.MemoryMappedFiles;
using Mono.Collections.Generic;
using Mono.Cecil;

Expand All @@ -15,6 +16,10 @@ public abstract class DirectoryAssemblyResolver : IAssemblyResolver {

readonly Collection<string> directories;

protected readonly Dictionary<AssemblyDefinition, string> assemblyToPath = new Dictionary<AssemblyDefinition, string> ();

readonly List<MemoryMappedViewStream> viewStreams = new List<MemoryMappedViewStream> ();

public void AddSearchDirectory (string directory)
{
directories.Add (directory);
Expand All @@ -40,7 +45,28 @@ protected AssemblyDefinition GetAssembly (string file, ReaderParameters paramete
if (parameters.AssemblyResolver == null)
parameters.AssemblyResolver = this;

return ModuleDefinition.ReadModule (file, parameters).Assembly;
MemoryMappedViewStream viewStream = null;
try {
// Create stream because CreateFromFile(string, ...) uses FileShare.None which is too strict
using var fileStream = new FileStream (file, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, false);
using var mappedFile = MemoryMappedFile.CreateFromFile (
fileStream, null, fileStream.Length, MemoryMappedFileAccess.Read, HandleInheritability.None, true);
viewStream = mappedFile.CreateViewStream (0, 0, MemoryMappedFileAccess.Read);

AssemblyDefinition result = ModuleDefinition.ReadModule (viewStream, parameters).Assembly;

assemblyToPath.Add (result, file);

viewStreams.Add (viewStream);

// We transferred the ownership of the viewStream to the collection.
viewStream = null;

return result;
} finally {
if (viewStream != null)
viewStream.Dispose ();
}
}

public virtual AssemblyDefinition Resolve (AssemblyNameReference name)
Expand Down Expand Up @@ -89,6 +115,13 @@ public void Dispose ()

protected virtual void Dispose (bool disposing)
{
if (disposing) {
foreach (var viewStream in viewStreams) {
viewStream.Dispose ();
}

viewStreams.Clear ();
}
}
}
}
Expand Down
5 changes: 2 additions & 3 deletions src/linker/Linker/LinkContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -264,8 +264,7 @@ public AssemblyDefinition Resolve (string name)
{
if (File.Exists (name)) {
try {
AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly (name, _readerParameters);
return _resolver.CacheAssembly (assembly);
return _resolver.ResolveFromPath (name, _readerParameters);
} catch (Exception e) {
throw new AssemblyResolutionException (new AssemblyNameReference (name, new Version ()), e);
}
Expand Down Expand Up @@ -314,7 +313,7 @@ public virtual void SafeReadSymbols (AssemblyDefinition assembly)
try {
var symbolReader = _symbolReaderProvider.GetSymbolReader (
assembly.MainModule,
assembly.MainModule.FileName);
_resolver.GetAssemblyFileName(assembly));

if (symbolReader == null)
return;
Expand Down

0 comments on commit f5a9875

Please sign in to comment.