Skip to content

Instantly share code, notes, and snippets.

@leidegre
Last active December 27, 2015 00:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leidegre/7239070 to your computer and use it in GitHub Desktop.
Save leidegre/7239070 to your computer and use it in GitHub Desktop.
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