286 lines
10 KiB
C#
286 lines
10 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Diagnostics;
|
||
using System.Text;
|
||
|
||
namespace fec
|
||
{
|
||
public class ReedSolomonBenchmark
|
||
{
|
||
public static readonly CodingLoop[] ALL_CODING_LOOPS =
|
||
new CodingLoop[] {
|
||
new InputOutputByteTableCodingLoop(),
|
||
new OutputInputByteTableCodingLoop(),
|
||
};
|
||
|
||
|
||
private const int DATA_COUNT = 17;
|
||
|
||
private const int PARITY_COUNT = 3;
|
||
|
||
private const int TOTAL_COUNT = DATA_COUNT + PARITY_COUNT;
|
||
|
||
private const int BUFFER_SIZE = 200 * 1000;
|
||
|
||
private const int PROCESSOR_CACHE_SIZE = 10 * 1024 * 1024;
|
||
|
||
private const int TWICE_PROCESSOR_CACHE_SIZE = 2 * PROCESSOR_CACHE_SIZE;
|
||
|
||
private const int NUMBER_OF_BUFFER_SETS = TWICE_PROCESSOR_CACHE_SIZE / DATA_COUNT / BUFFER_SIZE + 1;
|
||
|
||
|
||
|
||
private const long MEASUREMENT_DURATION = 2 * 1000;
|
||
|
||
|
||
|
||
private static readonly Random random = new Random();
|
||
|
||
|
||
|
||
private int nextBuffer = 0;
|
||
|
||
|
||
public void run()
|
||
{
|
||
Console.WriteLine("preparing...");
|
||
BufferSet[] bufferSets = new BufferSet [NUMBER_OF_BUFFER_SETS];
|
||
for (int iBufferSet = 0; iBufferSet < NUMBER_OF_BUFFER_SETS; iBufferSet++)
|
||
{
|
||
bufferSets[iBufferSet] = new BufferSet();
|
||
}
|
||
|
||
byte[] tempBuffer = new byte [BUFFER_SIZE];
|
||
|
||
List<String> summaryLines = new List<String>();
|
||
StringBuilder csv = new StringBuilder();
|
||
csv.Append("Outer,Middle,Inner,Multiply,Encode,Check\n");
|
||
foreach (var codingLoop in ALL_CODING_LOOPS)
|
||
{
|
||
Measurement encodeAverage = new Measurement();
|
||
{
|
||
String testName = codingLoop.GetType().Name + " encodeParity";
|
||
|
||
|
||
Console.WriteLine("\nTEST: " + testName);
|
||
ReedSolomon codec = new ReedSolomon(DATA_COUNT, PARITY_COUNT, codingLoop);
|
||
Console.WriteLine(" warm up...");
|
||
doOneEncodeMeasurement(codec, bufferSets);
|
||
doOneEncodeMeasurement(codec, bufferSets);
|
||
Console.WriteLine(" testing...");
|
||
for (int iMeasurement = 0; iMeasurement < 10; iMeasurement++)
|
||
{
|
||
encodeAverage.add(doOneEncodeMeasurement(codec, bufferSets));
|
||
}
|
||
|
||
Console.WriteLine("AVERAGE: {0}", encodeAverage);
|
||
summaryLines.Add(testName+" "+encodeAverage);
|
||
}
|
||
// The encoding test should have filled all of the buffers with
|
||
// correct parity, so we can benchmark parity checking.
|
||
Measurement checkAverage = new Measurement();
|
||
{
|
||
String testName = codingLoop.GetType().Name + " isParityCorrect";
|
||
Console.WriteLine("\nTEST: " + testName);
|
||
ReedSolomon codec = new ReedSolomon(DATA_COUNT, PARITY_COUNT, codingLoop);
|
||
Console.WriteLine(" warm up...");
|
||
doOneEncodeMeasurement(codec, bufferSets);
|
||
doOneEncodeMeasurement(codec, bufferSets);
|
||
Console.WriteLine(" testing...");
|
||
for (int iMeasurement = 0; iMeasurement < 10; iMeasurement++)
|
||
{
|
||
checkAverage.add(doOneCheckMeasurement(codec, bufferSets, tempBuffer));
|
||
}
|
||
|
||
Console.WriteLine("AVERAGE: {0}", checkAverage);
|
||
summaryLines.Add(testName+" "+checkAverage);
|
||
}
|
||
csv.Append(codingLoopNameToCsvPrefix(codingLoop.GetType().Name));
|
||
csv.Append((int)encodeAverage.getRate());
|
||
csv.Append(",");
|
||
csv.Append((int)checkAverage.getRate());
|
||
csv.Append("\n");
|
||
}
|
||
|
||
Console.WriteLine("\n");
|
||
Console.WriteLine(csv.ToString());
|
||
|
||
Console.WriteLine("\nSummary:\n");
|
||
foreach (var line in summaryLines)
|
||
{ Console.WriteLine(line);
|
||
}
|
||
}
|
||
|
||
private Measurement doOneEncodeMeasurement(ReedSolomon codec, BufferSet[] bufferSets)
|
||
{
|
||
long passesCompleted = 0;
|
||
long bytesEncoded = 0;
|
||
long encodingTime = 0;
|
||
while (encodingTime < MEASUREMENT_DURATION)
|
||
{
|
||
BufferSet bufferSet = bufferSets[nextBuffer];
|
||
nextBuffer = (nextBuffer + 1) % bufferSets.Length;
|
||
byte[][] shards = bufferSet.buffers;
|
||
Stopwatch stopwatch = new Stopwatch();
|
||
stopwatch.Start();
|
||
|
||
codec.encodeParity(shards, 0, BUFFER_SIZE);
|
||
stopwatch.Stop();
|
||
long stop = stopwatch.ElapsedMilliseconds;
|
||
TimeSpan timespan = stopwatch.Elapsed;
|
||
encodingTime += (long)timespan.TotalMilliseconds;
|
||
bytesEncoded += BUFFER_SIZE * DATA_COUNT;
|
||
passesCompleted += 1;
|
||
}
|
||
|
||
double seconds = ((double) encodingTime) / 1000.0;
|
||
double megabytes = ((double) bytesEncoded) / 1000000.0;
|
||
Measurement result = new Measurement(megabytes, seconds);
|
||
Console.WriteLine(" {0} passes, {1}", passesCompleted, result.ToString());
|
||
return result;
|
||
}
|
||
|
||
private Measurement doOneCheckMeasurement(ReedSolomon codec, BufferSet[] bufferSets, byte[] tempBuffer)
|
||
{
|
||
long passesCompleted = 0;
|
||
long bytesChecked = 0;
|
||
long checkingTime = 0;
|
||
while (checkingTime < MEASUREMENT_DURATION)
|
||
{
|
||
BufferSet bufferSet = bufferSets[nextBuffer];
|
||
nextBuffer = (nextBuffer + 1) % bufferSets.Length;
|
||
byte[][] shards = bufferSet.buffers;
|
||
|
||
Stopwatch stopwatch = new Stopwatch();
|
||
stopwatch.Start();
|
||
|
||
if (!codec.isParityCorrect(shards, 0, BUFFER_SIZE, tempBuffer))
|
||
{
|
||
// if the parity is not correct, it will throw off the
|
||
// benchmarking because it may return early.
|
||
throw new Exception("parity not correct");
|
||
}
|
||
|
||
stopwatch.Stop(); // 停止监视
|
||
TimeSpan timespan = stopwatch.Elapsed;
|
||
checkingTime += (long)timespan.TotalMilliseconds;
|
||
bytesChecked += BUFFER_SIZE * DATA_COUNT;
|
||
passesCompleted += 1;
|
||
}
|
||
|
||
double seconds = ((double) checkingTime) / 1000.0;
|
||
double megabytes = ((double) bytesChecked) / 1000000.0;
|
||
Measurement result = new Measurement(megabytes, seconds);
|
||
Console.WriteLine(" {0} passes, {1}", passesCompleted, result);
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* Converts a name like "OutputByteInputTableCodingLoop" to
|
||
* "output,byte,input,table,".
|
||
*/
|
||
private static string codingLoopNameToCsvPrefix(string className)
|
||
{
|
||
List<string> names = splitCamelCase(className);
|
||
return
|
||
names[0] + "," +
|
||
names[1] + "," +
|
||
names[2] + "," +
|
||
names[3] + ",";
|
||
}
|
||
|
||
/**
|
||
* Converts a name like "OutputByteInputTableCodingLoop" to a List of
|
||
* words: { "output", "byte", "input", "table", "coding", "loop" }
|
||
*/
|
||
private static List<string> splitCamelCase(string className)
|
||
{
|
||
string remaining = className;
|
||
List<string> result = new List<string>();
|
||
while (remaining.Length!=0)
|
||
{
|
||
bool found = false;
|
||
for (int i = 1; i < remaining.Length; i++)
|
||
{
|
||
if (remaining[i] >= 'A' && remaining[i] <= 'Z')
|
||
{
|
||
result.Add(remaining.Substring(0, i));
|
||
remaining = remaining.Substring(i);
|
||
found = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!found)
|
||
{
|
||
result.Add(remaining);
|
||
remaining = "";
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
|
||
private class BufferSet
|
||
{
|
||
public readonly byte[][] buffers;
|
||
|
||
public readonly byte[] bigBuffer;
|
||
|
||
public BufferSet()
|
||
{
|
||
buffers = new byte [TOTAL_COUNT][];
|
||
for (int iBuffer = 0; iBuffer < TOTAL_COUNT; iBuffer++)
|
||
{
|
||
byte[] buffer = new byte[BUFFER_SIZE];
|
||
buffers[iBuffer] = buffer;
|
||
for (int iByte = 0; iByte < BUFFER_SIZE; iByte++)
|
||
{
|
||
buffer[iByte] = (byte) random.Next(256);
|
||
}
|
||
}
|
||
|
||
bigBuffer = new byte [TOTAL_COUNT * BUFFER_SIZE];
|
||
for (int i = 0; i < TOTAL_COUNT * BUFFER_SIZE; i++)
|
||
{
|
||
bigBuffer[i] = (byte) random.Next(256);
|
||
}
|
||
}
|
||
}
|
||
|
||
private class Measurement
|
||
{
|
||
private double megabytes;
|
||
private double seconds;
|
||
|
||
public Measurement()
|
||
{
|
||
this.megabytes = 0.0;
|
||
this.seconds = 0.0;
|
||
}
|
||
|
||
public Measurement(double megabytes, double seconds)
|
||
{
|
||
this.megabytes = megabytes;
|
||
this.seconds = seconds;
|
||
}
|
||
|
||
public void add(Measurement other)
|
||
{
|
||
megabytes += other.megabytes;
|
||
seconds += other.seconds;
|
||
}
|
||
|
||
public double getRate()
|
||
{
|
||
return megabytes / seconds;
|
||
}
|
||
|
||
public override string ToString()
|
||
{
|
||
return string.Format((int)getRate()+"MB/s" );
|
||
}
|
||
}
|
||
}
|
||
} |