Last active
December 27, 2015 00:39
-
-
Save leidegre/7239070 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.IO; | |
using System.Linq; | |
using System.Runtime.CompilerServices; | |
using System.Security.Cryptography; | |
using System.Text; | |
using System.Threading; | |
using System.Threading.Tasks; | |
namespace AsyncCopyTest | |
{ | |
class TestCase | |
{ | |
public string Name; | |
public Action<Stream, Stream, int, CancellationToken> Func; | |
public FileOptions ReadOptions; | |
public FileOptions WriteOptions; | |
} | |
class Program | |
{ | |
static Task CompletedTask = ((Task)Task.FromResult(0)); | |
static async Task CopyTransformAsync(Stream inputStream, Stream outputStream, int bufferSize, CancellationToken cancellationToken, Action<byte, int> transform = null) | |
{ | |
var temp = new byte[bufferSize]; | |
var temp2 = new byte[bufferSize]; | |
int i = 0; | |
var readTask = inputStream.ReadAsync(temp, 0, bufferSize, cancellationToken).ConfigureAwait(false); | |
var writeTask = CompletedTask.ConfigureAwait(false); | |
for (; ; ) | |
{ | |
int read = await readTask; | |
if (read == 0) | |
{ | |
break; | |
} | |
if (i > 0) | |
{ | |
await writeTask; | |
} | |
writeTask = outputStream.WriteAsync(temp, 0, read, cancellationToken).ConfigureAwait(false); | |
i++; | |
readTask = inputStream.ReadAsync(temp2, 0, bufferSize, cancellationToken).ConfigureAwait(false); | |
var temp3 = temp; | |
temp = temp2; | |
temp2 = temp3; | |
} | |
await writeTask; // complete any lingering write task | |
} | |
static void Main(string[] args) | |
{ | |
if (!System.IO.File.Exists("test.bin")) | |
{ | |
var rng = new RNGCryptoServiceProvider(); | |
var tmp = new byte[1024]; | |
using (var outputStream = new FileStream("test.bin", FileMode.Create, FileAccess.Write, FileShare.None, 1024)) | |
{ | |
for (int i = 0; i < 256 * 1024; i++) | |
{ | |
rng.GetBytes(tmp); | |
outputStream.Write(tmp, 0, 1024); | |
} | |
} | |
} | |
// findings: | |
// - CopyToAsync bufferSize is sensitive (a large buffer is required) | |
// - FileOptions.Asynchronous -> horrendously slow, not sure exactly why that is | |
// - The bufferSize of the FileStream objects can be smaller without losing much performance (however, the serial test benefits from large buffers) | |
var bufferSizeList = new[] { | |
4 * 1024, | |
8 * 1024, | |
40 * 1024, | |
80 * 1024, // this is the framework default for CopyToAsync (it's big but not big enough for the large object heap) | |
160 * 1024, | |
}; | |
var testList = new[] { | |
new TestCase { | |
Name = "Serial", | |
Func = (inputStream, outputStream, bufferSize, cancellationToken) => { | |
var temp = new byte[bufferSize]; | |
int read; | |
while ((read = inputStream.Read(temp, 0, temp.Length)) > 0) | |
{ | |
cancellationToken.ThrowIfCancellationRequested(); | |
outputStream.Write(temp, 0, read); | |
cancellationToken.ThrowIfCancellationRequested(); | |
} | |
}, | |
ReadOptions = FileOptions.SequentialScan, | |
}, | |
new TestCase { | |
Name = "CopyToAsync", | |
Func = (inputStream, outputStream, bufferSize, cancellationToken) => { | |
inputStream.CopyToAsync(outputStream, bufferSize, cancellationToken).Wait(); | |
}, | |
ReadOptions = FileOptions.SequentialScan, | |
}, | |
new TestCase { | |
Name = "CopyToAsync (Asynchronous)", | |
Func = (inputStream, outputStream, bufferSize, cancellationToken) => { | |
inputStream.CopyToAsync(outputStream, bufferSize, cancellationToken).Wait(); | |
}, | |
ReadOptions = FileOptions.SequentialScan | FileOptions.Asynchronous, | |
WriteOptions = FileOptions.Asynchronous, | |
}, | |
new TestCase { | |
Name = "CopyTransformAsync", | |
Func = (inputStream, outputStream, bufferSize, cancellationToken) => { | |
CopyTransformAsync(inputStream, outputStream, bufferSize, cancellationToken).Wait(); | |
}, | |
ReadOptions = FileOptions.SequentialScan, | |
}, | |
new TestCase { | |
Name = "CopyTransformAsync (Asynchronous)", | |
Func = (inputStream, outputStream, bufferSize, cancellationToken) => { | |
CopyTransformAsync(inputStream, outputStream, bufferSize, cancellationToken).Wait(); | |
}, | |
ReadOptions = FileOptions.SequentialScan | FileOptions.Asynchronous, | |
WriteOptions = FileOptions.Asynchronous, | |
} | |
}; | |
Console.WriteLine("Press ENTER to start..."); | |
Console.ReadLine(); | |
var sw = new Stopwatch(); | |
foreach (var bufferSize in bufferSizeList) | |
{ | |
Console.WriteLine(); | |
Console.WriteLine("{0}K buffer", bufferSize / 1024); | |
Console.WriteLine(); | |
foreach (var test in testList) | |
{ | |
sw.Reset(); | |
Console.Write("{0,-40}", test.Name + "..."); | |
var cts = new CancellationTokenSource(); | |
cts.CancelAfter(15 * 1000); | |
try | |
{ | |
for (int i = 0; i < 10; i++) | |
{ | |
sw.Start(); | |
using (var inputStream = new FileStream("test.bin", FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, test.ReadOptions)) | |
{ | |
using (var outputStream = new FileStream("test.copy", FileMode.Create, FileAccess.Write, FileShare.None, bufferSize, test.WriteOptions)) | |
{ | |
test.Func(inputStream, outputStream, bufferSize, cts.Token); | |
} | |
} | |
sw.Stop(); | |
} | |
Console.WriteLine(" in {0:0.000}s", sw.Elapsed.TotalSeconds / 10); | |
} | |
catch (AggregateException ex) | |
{ | |
if (!(ex.InnerException is OperationCanceledException)) | |
{ | |
throw; | |
} | |
Console.WriteLine(" timed out"); | |
} | |
cts.Dispose(); | |
} | |
} | |
Console.WriteLine("Done"); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment