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" );
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|