-
Notifications
You must be signed in to change notification settings - Fork 1
/
PackageExtraction.cs
170 lines (145 loc) · 4.82 KB
/
PackageExtraction.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
using System.IO.Compression;
using System.IO.MemoryMappedFiles;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Engines;
[SimpleJob(RunStrategy.Monitoring, warmupCount: 1, iterationCount: 3)]
public class PackageExtraction
{
IReadOnlyList<string>? packages;
readonly string extractDirectory;
[Params(1, 2, 4, 8, 16, 32)]
public int MaxParallel { get; set; }
public PackageExtraction()
{
extractDirectory = Path.Combine("bin", "extract");
}
[GlobalSetup]
public async Task GlobalSetup()
{
var setup = new OrchardCodePackagesSetup();
packages = await setup.GetPackages();
}
[IterationSetup]
public void IterationSetup()
{
if (Directory.Exists(extractDirectory))
{
Directory.Delete(extractDirectory, true);
}
}
[IterationCleanup]
public void IterationCleanup()
{
Directory.Delete(extractDirectory, true);
}
[Benchmark(Baseline = true)]
public void CopyTo()
{
if (packages == null)
{
throw new Exception("Instance hasn't been setup");
}
var options = new ParallelOptions()
{
MaxDegreeOfParallelism = MaxParallel
};
Parallel.For(0, packages.Count, options, index =>
{
ExtractZip(packages[index], CopyTo).GetAwaiter().GetResult();
});
}
private Task CopyTo(Stream input, FileStream output, long length)
{
input.CopyTo(output);
return Task.CompletedTask;
}
[Benchmark]
public async Task CopyToAsync()
{
if (packages == null)
{
throw new Exception("Instance hasn't been setup");
}
var tasks = new Task[MaxParallel];
var index = 0;
while (index < tasks.Length)
{
var packageIndex = index;
tasks[index] = Task.Run(() => ExtractZip(packages[packageIndex], CopyToAsync));
index++;
}
while (index < packages.Count)
{
await Task.WhenAny(tasks);
for (int i = 0; i < tasks.Length; i++)
{
if (tasks[i].IsCompleted)
{
var packageIndex = index;
tasks[i] = Task.Run(() => ExtractZip(packages[packageIndex], CopyToAsync, useAsync: true));
index++;
}
}
}
await Task.WhenAll(tasks);
}
private async Task CopyToAsync(Stream input, FileStream output, long length)
{
await input.CopyToAsync(output);
await output.FlushAsync();
}
[Benchmark]
public void CopyToMmap()
{
if (packages == null)
{
throw new Exception("Instance hasn't been setup");
}
var options = new ParallelOptions()
{
MaxDegreeOfParallelism = MaxParallel
};
Parallel.For(0, packages.Count, options, index =>
{
ExtractZip(packages[index], CopyToMmap).GetAwaiter().GetResult();
});
}
private Task CopyToMmap(Stream input, FileStream output, long length)
{
if (length > 0)
{
using var mmf = MemoryMappedFile.CreateFromFile(output, null, length, MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, false);
using var mmStream = mmf.CreateViewStream();
input.CopyTo(mmStream);
}
return Task.CompletedTask;
}
private async Task ExtractZip(string package, Func<Stream, FileStream, long, Task> copy, bool useAsync = false)
{
var destination = Path.Combine(extractDirectory, Path.GetFileNameWithoutExtension(package)) + Path.DirectorySeparatorChar;
Directory.CreateDirectory(destination);
FileOptions fileOptions = useAsync ? FileOptions.Asynchronous : FileOptions.None;
using var zipFile = new FileStream(package, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete, 4096, useAsync);
using var zip = new ZipArchive(zipFile);
foreach (var entry in zip.Entries)
{
if (entry.Name.Equals(string.Empty))
{
continue;
}
var destinationFile = Path.GetFullPath(Path.Combine(destination, entry.FullName));
if (!destinationFile.StartsWith(destination))
{
throw new Exception("Zip slip");
}
var destinationDirectory = Path.GetDirectoryName(destinationFile);
if (!Directory.Exists(destinationDirectory))
{
Directory.CreateDirectory(destinationDirectory!);
}
using var writeStream = File.Create(destinationFile, 4096, fileOptions);
using var zipStream = entry.Open();
await copy(zipStream, writeStream, entry.Length);
}
}
}