Skip to content

Commit 65fa6af

Browse files
authored
Merge pull request #134 from petli/performance
Performance improvements
2 parents 74fa3a2 + 1f4da61 commit 65fa6af

File tree

11 files changed

+5082
-95
lines changed

11 files changed

+5082
-95
lines changed

coverlet.sln

+33-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
Microsoft Visual Studio Solution File, Format Version 12.00
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
23
# Visual Studio 15
34
VisualStudioVersion = 15.0.26124.0
45
MinimumVisualStudioVersion = 15.0.26124.0
@@ -14,7 +15,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.core.tests", "test
1415
EndProject
1516
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.console", "src\coverlet.console\coverlet.console.csproj", "{F3DBE7C3-ABBB-4B8B-A6CB-A1D3D607163E}"
1617
EndProject
17-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "coverlet.tracker", "src\coverlet.tracker\coverlet.tracker.csproj", "{F4273009-536D-4999-A126-B0A2E3AA3E70}"
18+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.tracker", "src\coverlet.tracker\coverlet.tracker.csproj", "{F4273009-536D-4999-A126-B0A2E3AA3E70}"
19+
EndProject
20+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "coverlet.testsubject", "test\coverlet.testsubject\coverlet.testsubject.csproj", "{AE117FAA-C21D-4F23-917E-0C8050614750}"
21+
EndProject
22+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "coverlet.core.performancetest", "test\coverlet.core.performancetest\coverlet.core.performancetest.csproj", "{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}"
1823
EndProject
1924
Global
2025
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -86,6 +91,30 @@ Global
8691
{F4273009-536D-4999-A126-B0A2E3AA3E70}.Release|x64.Build.0 = Release|Any CPU
8792
{F4273009-536D-4999-A126-B0A2E3AA3E70}.Release|x86.ActiveCfg = Release|Any CPU
8893
{F4273009-536D-4999-A126-B0A2E3AA3E70}.Release|x86.Build.0 = Release|Any CPU
94+
{AE117FAA-C21D-4F23-917E-0C8050614750}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
95+
{AE117FAA-C21D-4F23-917E-0C8050614750}.Debug|Any CPU.Build.0 = Debug|Any CPU
96+
{AE117FAA-C21D-4F23-917E-0C8050614750}.Debug|x64.ActiveCfg = Debug|Any CPU
97+
{AE117FAA-C21D-4F23-917E-0C8050614750}.Debug|x64.Build.0 = Debug|Any CPU
98+
{AE117FAA-C21D-4F23-917E-0C8050614750}.Debug|x86.ActiveCfg = Debug|Any CPU
99+
{AE117FAA-C21D-4F23-917E-0C8050614750}.Debug|x86.Build.0 = Debug|Any CPU
100+
{AE117FAA-C21D-4F23-917E-0C8050614750}.Release|Any CPU.ActiveCfg = Release|Any CPU
101+
{AE117FAA-C21D-4F23-917E-0C8050614750}.Release|Any CPU.Build.0 = Release|Any CPU
102+
{AE117FAA-C21D-4F23-917E-0C8050614750}.Release|x64.ActiveCfg = Release|Any CPU
103+
{AE117FAA-C21D-4F23-917E-0C8050614750}.Release|x64.Build.0 = Release|Any CPU
104+
{AE117FAA-C21D-4F23-917E-0C8050614750}.Release|x86.ActiveCfg = Release|Any CPU
105+
{AE117FAA-C21D-4F23-917E-0C8050614750}.Release|x86.Build.0 = Release|Any CPU
106+
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
107+
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
108+
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Debug|x64.ActiveCfg = Debug|Any CPU
109+
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Debug|x64.Build.0 = Debug|Any CPU
110+
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Debug|x86.ActiveCfg = Debug|Any CPU
111+
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Debug|x86.Build.0 = Debug|Any CPU
112+
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
113+
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Release|Any CPU.Build.0 = Release|Any CPU
114+
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Release|x64.ActiveCfg = Release|Any CPU
115+
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Release|x64.Build.0 = Release|Any CPU
116+
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Release|x86.ActiveCfg = Release|Any CPU
117+
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9}.Release|x86.Build.0 = Release|Any CPU
89118
EndGlobalSection
90119
GlobalSection(SolutionProperties) = preSolution
91120
HideSolutionNode = FALSE
@@ -96,6 +125,8 @@ Global
96125
{E7637CC6-43F7-461A-A0BF-3C14562419BD} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
97126
{F3DBE7C3-ABBB-4B8B-A6CB-A1D3D607163E} = {E877EBA4-E78B-4F7D-A2D3-1E070FED04CD}
98127
{F4273009-536D-4999-A126-B0A2E3AA3E70} = {E877EBA4-E78B-4F7D-A2D3-1E070FED04CD}
128+
{AE117FAA-C21D-4F23-917E-0C8050614750} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
129+
{C68CF6DE-F86C-4BCF-BAB9-7A60C320E1F9} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
99130
EndGlobalSection
100131
GlobalSection(ExtensibilityGlobals) = postSolution
101132
SolutionGuid = {9CA57C02-97B0-4C38-A027-EA61E8741F10}

src/coverlet.core/Coverage.cs

+38-41
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ public CoverageResult GetCoverageResult()
5555
foreach (var result in _results)
5656
{
5757
Documents documents = new Documents();
58-
foreach (var doc in result.Documents)
58+
foreach (var doc in result.Documents.Values)
5959
{
6060
// Construct Line Results
61-
foreach (var line in doc.Lines)
61+
foreach (var line in doc.Lines.Values)
6262
{
6363
if (documents.TryGetValue(doc.Path, out Classes classes))
6464
{
@@ -91,7 +91,7 @@ public CoverageResult GetCoverageResult()
9191
}
9292

9393
// Construct Branch Results
94-
foreach (var branch in doc.Branches)
94+
foreach (var branch in doc.Branches.Values)
9595
{
9696
if (documents.TryGetValue(doc.Path, out Classes classes))
9797
{
@@ -147,55 +147,52 @@ private void CalculateCoverage()
147147
{
148148
foreach (var result in _results)
149149
{
150-
var i = 0;
151-
while (true)
150+
if (!File.Exists(result.HitsFilePath))
152151
{
153-
var file = $"{result.HitsFilePath}_compressed_{i}";
154-
if(!File.Exists(file)) break;
155-
156-
using (var fs = new FileStream(file, FileMode.Open))
157-
using (var gz = new GZipStream(fs, CompressionMode.Decompress))
158-
using (var sr = new StreamReader(gz))
152+
// File not instrumented, or nothing in it called. Warn about this?
153+
continue;
154+
}
155+
156+
using (var fs = new FileStream(result.HitsFilePath, FileMode.Open))
157+
using (var sr = new StreamReader(fs))
158+
{
159+
string row;
160+
while ((row = sr.ReadLine()) != null)
159161
{
160-
string row;
161-
while ((row = sr.ReadLine()) != null)
162-
{
163-
var info = row.Split(',');
164-
// Ignore malformed lines
165-
if (info.Length != 4)
166-
continue;
162+
var info = row.Split(',');
163+
// Ignore malformed lines
164+
if (info.Length != 5)
165+
continue;
167166

168-
bool isBranch = info[0] == "B";
167+
bool isBranch = info[0] == "B";
169168

170-
var document = result.Documents.FirstOrDefault(d => d.Path == info[1]);
171-
if (document == null)
172-
continue;
169+
if (!result.Documents.TryGetValue(info[1], out var document))
170+
{
171+
continue;
172+
}
173173

174-
int start = int.Parse(info[2]);
174+
int start = int.Parse(info[2]);
175+
int hits = int.Parse(info[4]);
175176

176-
if (isBranch)
177-
{
178-
uint ordinal = uint.Parse(info[3]);
179-
var branch = document.Branches.First(b => b.Number == start && b.Ordinal == ordinal);
180-
if (branch.Hits != int.MaxValue)
181-
branch.Hits += branch.Hits + 1;
182-
}
183-
else
177+
if (isBranch)
178+
{
179+
int ordinal = int.Parse(info[3]);
180+
var branch = document.Branches[(start, ordinal)];
181+
branch.Hits = hits;
182+
}
183+
else
184+
{
185+
int end = int.Parse(info[3]);
186+
for (int j = start; j <= end; j++)
184187
{
185-
int end = int.Parse(info[3]);
186-
for (int j = start; j <= end; j++)
187-
{
188-
var line = document.Lines.First(l => l.Number == j);
189-
if (line.Hits != int.MaxValue)
190-
line.Hits = line.Hits + 1;
191-
}
188+
var line = document.Lines[j];
189+
line.Hits = hits;
192190
}
193191
}
194192
}
195-
196-
InstrumentationHelper.DeleteHitsFile(file);
197-
i++;
198193
}
194+
195+
InstrumentationHelper.DeleteHitsFile(result.HitsFilePath);
199196
}
200197
}
201198
}

src/coverlet.core/Instrumentation/Instrumenter.cs

+11-12
Original file line numberDiff line numberDiff line change
@@ -166,17 +166,16 @@ private void InstrumentIL(MethodDefinition method)
166166

167167
private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor processor, Instruction instruction, SequencePoint sequencePoint)
168168
{
169-
var document = _result.Documents.FirstOrDefault(d => d.Path == sequencePoint.Document.Url);
170-
if (document == null)
171-
{
169+
if (!_result.Documents.TryGetValue(sequencePoint.Document.Url, out var document))
170+
{
172171
document = new Document { Path = sequencePoint.Document.Url };
173-
_result.Documents.Add(document);
172+
_result.Documents.Add(document.Path, document);
174173
}
175174

176175
for (int i = sequencePoint.StartLine; i <= sequencePoint.EndLine; i++)
177176
{
178-
if (!document.Lines.Exists(l => l.Number == i))
179-
document.Lines.Add(new Line { Number = i, Class = method.DeclaringType.FullName, Method = method.FullName });
177+
if (!document.Lines.ContainsKey(i))
178+
document.Lines.Add(i, new Line { Number = i, Class = method.DeclaringType.FullName, Method = method.FullName });
180179
}
181180

182181
string marker = $"L,{document.Path},{sequencePoint.StartLine},{sequencePoint.EndLine}";
@@ -194,15 +193,15 @@ private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor
194193

195194
private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor processor, Instruction instruction, BranchPoint branchPoint)
196195
{
197-
var document = _result.Documents.FirstOrDefault(d => d.Path == branchPoint.Document);
198-
if (document == null)
199-
{
196+
if (!_result.Documents.TryGetValue(branchPoint.Document, out var document))
197+
{
200198
document = new Document { Path = branchPoint.Document };
201-
_result.Documents.Add(document);
199+
_result.Documents.Add(document.Path, document);
202200
}
203201

204-
if (!document.Branches.Exists(l => l.Number == branchPoint.StartLine && l.Ordinal == branchPoint.Ordinal))
205-
document.Branches.Add(
202+
var key = (branchPoint.StartLine, (int)branchPoint.Ordinal);
203+
if (!document.Branches.ContainsKey(key))
204+
document.Branches.Add(key,
206205
new Branch
207206
{
208207
Number = branchPoint.StartLine,

src/coverlet.core/Instrumentation/InstrumenterResult.cs

+11-6
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,26 @@ internal class Document
2222
{
2323
public Document()
2424
{
25-
Lines = new List<Line>();
26-
Branches = new List<Branch>();
25+
Lines = new Dictionary<int, Line>();
26+
Branches = new Dictionary<(int Line, int Ordinal), Branch>();
2727
}
2828

2929
public string Path;
30-
public List<Line> Lines { get; private set; }
31-
public List<Branch> Branches { get; private set; }
30+
31+
public Dictionary<int, Line> Lines { get; private set; }
32+
public Dictionary<(int Line, int Ordinal), Branch> Branches { get; private set; }
3233
}
3334

3435
internal class InstrumenterResult
3536
{
36-
public InstrumenterResult() => Documents = new List<Document>();
37+
public InstrumenterResult()
38+
{
39+
Documents = new Dictionary<string, Document>();
40+
}
41+
3742
public string Module;
3843
public string HitsFilePath;
3944
public string ModulePath;
40-
public List<Document> Documents { get; private set; }
45+
public Dictionary<string, Document> Documents { get; private set; }
4146
}
4247
}

src/coverlet.msbuild.tasks/CoverageResultTask.cs

+5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Diagnostics;
23
using System.IO;
34
using System.Linq;
45
using System.Text;
@@ -50,8 +51,12 @@ public override bool Execute()
5051
try
5152
{
5253
Console.WriteLine("\nCalculating coverage result...");
54+
var duration = new Stopwatch();
55+
duration.Start();
5356
var coverage = InstrumentationTask.Coverage;
5457
var result = coverage.GetCoverageResult();
58+
duration.Stop();
59+
Console.WriteLine($"Results calculated in {duration.Elapsed.TotalSeconds} seconds");
5560

5661
var directory = Path.GetDirectoryName(_filename);
5762
if (!Directory.Exists(directory))

src/coverlet.tracker/CoverageTracker.cs

+23-32
Original file line numberDiff line numberDiff line change
@@ -2,70 +2,61 @@
22
using System.Collections.Generic;
33
using System.Diagnostics.CodeAnalysis;
44
using System.IO;
5-
using System.IO.Compression;
6-
7-
using Coverlet.Tracker.Extensions;
85

96
namespace Coverlet.Tracker
107
{
118
public static class CoverageTracker
129
{
13-
private static Dictionary<string, List<string>> _markers;
14-
private static Dictionary<string, int> _markerFileCount;
10+
private static Dictionary<string, Dictionary<string, int>> _events;
1511

1612
[ExcludeFromCodeCoverage]
1713
static CoverageTracker()
1814
{
19-
_markers = new Dictionary<string, List<string>>();
20-
_markerFileCount = new Dictionary<string, int>();
15+
_events = new Dictionary<string, Dictionary<string, int>>();
2116
AppDomain.CurrentDomain.ProcessExit += new EventHandler(CurrentDomain_ProcessExit);
2217
AppDomain.CurrentDomain.DomainUnload += new EventHandler(CurrentDomain_ProcessExit);
2318
}
2419

2520
[ExcludeFromCodeCoverage]
26-
public static void MarkExecuted(string path, string marker)
21+
public static void MarkExecuted(string file, string evt)
2722
{
28-
lock (_markers)
23+
lock (_events)
2924
{
30-
_markers.TryAdd(path, new List<string>());
31-
_markers[path].Add(marker);
32-
_markerFileCount.TryAdd(path, 0);
33-
if (_markers[path].Count >= 100000)
25+
if (!_events.TryGetValue(file, out var fileEvents))
3426
{
35-
using (var fs = new FileStream($"{path}_compressed_{_markerFileCount[path]}", FileMode.OpenOrCreate))
36-
using (var gz = new GZipStream(fs, CompressionMode.Compress))
37-
using (var sw = new StreamWriter(gz))
38-
{
39-
foreach (var line in _markers[path])
40-
{
41-
sw.WriteLine(line);
42-
}
43-
}
44-
_markers[path].Clear();
45-
_markerFileCount[path] = _markerFileCount[path] + 1;
27+
fileEvents = new Dictionary<string, int>();
28+
_events.Add(file, fileEvents);
29+
}
30+
31+
if (!fileEvents.TryGetValue(evt, out var count))
32+
{
33+
fileEvents.Add(evt, 1);
34+
}
35+
else if (count < int.MaxValue)
36+
{
37+
fileEvents[evt] = count + 1;
4638
}
4739
}
4840
}
4941

5042
[ExcludeFromCodeCoverage]
5143
public static void CurrentDomain_ProcessExit(object sender, EventArgs e)
5244
{
53-
lock (_markers)
45+
lock (_events)
5446
{
55-
foreach (var kvp in _markers)
47+
foreach (var files in _events)
5648
{
57-
using (var fs = new FileStream($"{kvp.Key}_compressed_{_markerFileCount[kvp.Key]}", FileMode.OpenOrCreate))
58-
using (var gz = new GZipStream(fs, CompressionMode.Compress))
59-
using (var sw = new StreamWriter(gz))
49+
using (var fs = new FileStream(files.Key, FileMode.Create))
50+
using (var sw = new StreamWriter(fs))
6051
{
61-
foreach (var line in kvp.Value)
52+
foreach (var evt in files.Value)
6253
{
63-
sw.WriteLine(line);
54+
sw.WriteLine($"{evt.Key},{evt.Value}");
6455
}
6556
}
6657
}
6758

68-
_markers.Clear();
59+
_events.Clear();
6960
}
7061
}
7162
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using coverlet.testsubject;
2+
using Xunit;
3+
4+
namespace coverlet.core.performancetest
5+
{
6+
/// <summary>
7+
/// Test the performance of coverlet by running a unit test that calls a reasonably big and complex test class.
8+
/// Enable the test, compile, then run the test in the command line:
9+
/// <code>
10+
/// dotnet test -p:CollectCoverage=true -p:CoverletOutputFormat=opencover test/coverlet.core.performa ncetest/
11+
/// </code>
12+
/// </summary>
13+
public class PerformanceTest
14+
{
15+
[Theory(Skip = "Only enabled when explicitly testing performance.")]
16+
[InlineData(150)]
17+
public void TestPerformance(int iterations)
18+
{
19+
var big = new BigClass();
20+
21+
for (var i = 0; i < iterations; i++)
22+
{
23+
big.Do(i);
24+
}
25+
}
26+
}
27+
}

0 commit comments

Comments
 (0)