305 lines
9.0 KiB
C#
305 lines
9.0 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Threading;
|
|
using System.Collections.Generic;
|
|
|
|
namespace CRC32
|
|
{
|
|
public class ParallelCRC
|
|
{
|
|
private const uint kCrcPoly = 0xEDB88320;
|
|
private const uint kInitial = 0xFFFFFFFF;
|
|
private static readonly uint[] Table;
|
|
private const int CRC_NUM_TABLES = 8;
|
|
|
|
private const int ThreadCost = 256 << 10;
|
|
private static int ThreadCount = Environment.ProcessorCount;
|
|
|
|
|
|
static ParallelCRC()
|
|
{
|
|
unchecked
|
|
{
|
|
Table = new uint[256 * CRC_NUM_TABLES];
|
|
int i;
|
|
for (i = 0; i < 256; i++)
|
|
{
|
|
uint r = (uint)i;
|
|
for (int j = 0; j < 8; j++)
|
|
r = (r >> 1) ^ (kCrcPoly & ~((r & 1) - 1));
|
|
Table[i] = r;
|
|
}
|
|
for (; i < 256 * CRC_NUM_TABLES; i++)
|
|
{
|
|
uint r = Table[i - 256];
|
|
Table[i] = Table[r & 0xFF] ^ (r >> 8);
|
|
}
|
|
}
|
|
}
|
|
|
|
private uint value;
|
|
|
|
public ParallelCRC()
|
|
{
|
|
Init();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reset CRC
|
|
/// </summary>
|
|
public void Init()
|
|
{
|
|
value = kInitial;
|
|
}
|
|
|
|
public int Value
|
|
{
|
|
get { return (int)~value; }
|
|
}
|
|
|
|
public void UpdateByte(byte b)
|
|
{
|
|
value = (value >> 8) ^ Table[(byte)value ^ b];
|
|
}
|
|
|
|
public void Update(byte[] data, int offset, int count)
|
|
{
|
|
new ArraySegment<byte>(data, offset, count); // check arguments
|
|
|
|
if (count <= ThreadCost || ThreadCount <= 1)
|
|
{
|
|
value = ProcessBlock(value, data, offset, count);
|
|
return;
|
|
}
|
|
|
|
// choose optimal number of threads to use
|
|
|
|
int threadCount = ThreadCount;
|
|
L0:
|
|
int bytesPerThread = (count + threadCount - 1) / threadCount;
|
|
if (bytesPerThread < ThreadCost >> 1)
|
|
{
|
|
threadCount--;
|
|
goto L0;
|
|
}
|
|
|
|
// threadCount >= 2
|
|
|
|
Job lastJob = null;
|
|
while (count > bytesPerThread)
|
|
{
|
|
var job = new Job(new ArraySegment<byte>(data, offset, bytesPerThread), this, lastJob);
|
|
ThreadPool.QueueUserWorkItem(job.Do);
|
|
offset += bytesPerThread;
|
|
count -= bytesPerThread;
|
|
lastJob = job;
|
|
}
|
|
|
|
// lastJob != null
|
|
var lastBlockCRC = ProcessBlock(kInitial, data, offset, count);
|
|
lastJob.WaitAndDispose();
|
|
value = Combine(value, lastBlockCRC, count);
|
|
}
|
|
|
|
private static uint ProcessBlock(uint crc, byte[] data, int offset, int count)
|
|
{
|
|
/*
|
|
* A copy of Optimized implementation.
|
|
*/
|
|
|
|
if (count < 0) throw new ArgumentOutOfRangeException("count");
|
|
if (count == 0) return crc;
|
|
|
|
var table = ParallelCRC.Table;
|
|
|
|
for (; (offset & 7) != 0 && count != 0; count--)
|
|
crc = (crc >> 8) ^ table[(byte)crc ^ data[offset++]];
|
|
|
|
if (count >= 8)
|
|
{
|
|
int to = (count - 8) & ~7;
|
|
count -= to;
|
|
to += offset;
|
|
|
|
while (offset != to)
|
|
{
|
|
crc ^= (uint)(data[offset] + (data[offset + 1] << 8) + (data[offset + 2] << 16) + (data[offset + 3] << 24));
|
|
uint high = (uint)(data[offset + 4] + (data[offset + 5] << 8) + (data[offset + 6] << 16) + (data[offset + 7] << 24));
|
|
offset += 8;
|
|
|
|
crc = table[(byte)crc + 0x700]
|
|
^ table[(byte)(crc >>= 8) + 0x600]
|
|
^ table[(byte)(crc >>= 8) + 0x500]
|
|
^ table[/*(byte)*/(crc >> 8) + 0x400]
|
|
^ table[(byte)(high) + 0x300]
|
|
^ table[(byte)(high >>= 8) + 0x200]
|
|
^ table[(byte)(high >>= 8) + 0x100]
|
|
^ table[/*(byte)*/(high >> 8) + 0x000];
|
|
}
|
|
}
|
|
|
|
while (count-- != 0)
|
|
crc = (crc >> 8) ^ table[(byte)crc ^ data[offset++]];
|
|
|
|
return crc;
|
|
}
|
|
|
|
static public int Compute(byte[] data, int offset, int count)
|
|
{
|
|
var crc = new ParallelCRC();
|
|
crc.Update(data, offset, count);
|
|
return crc.Value;
|
|
}
|
|
|
|
static public int Compute(byte[] data)
|
|
{
|
|
return Compute(data, 0, data.Length);
|
|
}
|
|
|
|
static public int Compute(ArraySegment<byte> block)
|
|
{
|
|
return Compute(block.Array, block.Offset, block.Count);
|
|
}
|
|
|
|
#region Combining
|
|
|
|
/*
|
|
* CRC values combining algorithm.
|
|
* Taken from DotNetZip project sources (http://dotnetzip.codeplex.com/)
|
|
*/
|
|
|
|
/// <summary>
|
|
/// This function is thread-safe!
|
|
/// </summary>
|
|
private static uint Combine(uint crc1, uint crc2, int length2)
|
|
{
|
|
if (length2 <= 0) return crc1;
|
|
if (crc1 == kInitial) return crc2;
|
|
|
|
if (even_cache == null)
|
|
{
|
|
Prepare_even_odd_Cache();
|
|
}
|
|
|
|
uint[] even = CopyArray(even_cache);
|
|
uint[] odd = CopyArray(odd_cache);
|
|
|
|
crc1 = ~crc1;
|
|
crc2 = ~crc2;
|
|
|
|
uint len2 = (uint)length2;
|
|
|
|
// apply len2 zeros to crc1 (first square will put the operator for one
|
|
// zero byte, eight zero bits, in even)
|
|
do
|
|
{
|
|
// apply zeros operator for this bit of len2
|
|
gf2_matrix_square(even, odd);
|
|
|
|
if ((len2 & 1) != 0) crc1 = gf2_matrix_times(even, crc1);
|
|
len2 >>= 1;
|
|
|
|
if (len2 == 0) break;
|
|
|
|
// another iteration of the loop with odd and even swapped
|
|
gf2_matrix_square(odd, even);
|
|
if ((len2 & 1) != 0) crc1 = gf2_matrix_times(odd, crc1);
|
|
len2 >>= 1;
|
|
} while (len2 != 0);
|
|
|
|
crc1 ^= crc2;
|
|
return ~crc1;
|
|
}
|
|
|
|
private static uint[] even_cache = null;
|
|
private static uint[] odd_cache;
|
|
|
|
private static void Prepare_even_odd_Cache()
|
|
{
|
|
var even = new uint[32]; // even-power-of-two zeros operator
|
|
var odd = new uint[32]; // odd-power-of-two zeros operator
|
|
|
|
// put operator for one zero bit in odd
|
|
odd[0] = kCrcPoly; // the CRC-32 polynomial
|
|
for (int i = 1; i < 32; i++) odd[i] = 1U << (i - 1);
|
|
|
|
// put operator for two zero bits in even
|
|
gf2_matrix_square(even, odd);
|
|
|
|
// put operator for four zero bits in odd
|
|
gf2_matrix_square(odd, even);
|
|
|
|
odd_cache = odd;
|
|
even_cache = even;
|
|
}
|
|
|
|
/// <param name="matrix">will not be modified</param>
|
|
private static uint gf2_matrix_times(uint[] matrix, uint vec)
|
|
{
|
|
uint sum = 0;
|
|
int i = 0;
|
|
while (vec != 0)
|
|
{
|
|
if ((vec & 1) != 0) sum ^= matrix[i];
|
|
vec >>= 1;
|
|
i++;
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
/// <param name="square">this array will be modified!</param>
|
|
/// <param name="mat">will not be modified</param>
|
|
private static void gf2_matrix_square(uint[] square, uint[] mat)
|
|
{
|
|
for (int i = 0; i < 32; i++)
|
|
square[i] = gf2_matrix_times(mat, mat[i]);
|
|
}
|
|
|
|
private static uint[] CopyArray(uint[] a)
|
|
{
|
|
var b = new uint[a.Length];
|
|
Buffer.BlockCopy(a, 0, b, 0, a.Length * sizeof(uint));
|
|
return b;
|
|
}
|
|
|
|
#endregion Combining
|
|
|
|
class Job
|
|
{
|
|
private ArraySegment<byte> data;
|
|
private Job waitForJob;
|
|
private ParallelCRC accumulator;
|
|
|
|
private ManualResetEventSlim finished;
|
|
|
|
public Job(ArraySegment<byte> data, ParallelCRC accumulator, Job waitForJob)
|
|
{
|
|
this.data = data;
|
|
this.accumulator = accumulator;
|
|
this.waitForJob = waitForJob;
|
|
this.finished = new ManualResetEventSlim(false);
|
|
}
|
|
|
|
public void Do(object arg)
|
|
{
|
|
var crc = ProcessBlock(kInitial, data.Array, data.Offset, data.Count);
|
|
if (waitForJob != null) waitForJob.WaitAndDispose();
|
|
accumulator.value = Combine(accumulator.value, crc, data.Count);
|
|
finished.Set();
|
|
}
|
|
|
|
public void WaitAndDispose()
|
|
{
|
|
finished.Wait();
|
|
Dispose();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (finished != null) finished.Dispose();
|
|
finished = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
} |