Status: Fixed After a lot of work, I have now re-written the entire SharpRSync library. My tests indicate that generating a diff file for an 8gb file took 10 minutes before, and now takes 8 minutes. A delta for the same (unchanged) 8GB file took 58 minutes, and now takes 7 minutes. The implementation seems to run faster under 64 bit, but I'm unsure if it is related to the actual instructions issued, or the address simulation/conversion done by the OS. I have also greatly optimized the amount of memory required by the program. On windows, a delta for an 8GB file can be now be created with a total of 125 mb required memory. The base app takes 65mb, so the used memory is around 60mb, and the raw diff file is approximately 44mb. I compared it a bit to the rdiff program on linux. For a 150mb file, rdiff takes about 1 second to generate a diff, and the same for a delta. SharpRSync runs the same in about 7 seconds, although some of that is the mono startup and library loading. For a 8gb file rdiff can generate a diff in about 500 seconds, where SharpRSync takes about 590 seconds. When generating a delta, rdiff uses 530 seconds, and SharpRSync uses 770 seconds. The last measurement is inaccurate as the memory requirement under mono is greater, and the test machine had low memory, so there was a lot of swapping going on. An interesting side effect is that the rdiff generated delta file is about 380kb, where the SharpRSync delta is 15 bytes. This is because rdiff does not do the sequence matching optimization mentioned on the librsync page (last paragraph): http://rsync.samba.org/tech_report/node4.html Further optimization should probably focus on getting the MD4 algorithm to run faster. git-svn-id: https://duplicati.googlecode.com/svn/trunk@480 59da171f-624f-0410-aa54-27559c288bec
332 lines
14 KiB
C#
332 lines
14 KiB
C#
#region Disclaimer / License
|
|
// Copyright (C) 2010, Kenneth Skovhede
|
|
// http://www.hexad.dk, opensource@hexad.dk
|
|
//
|
|
// This library is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU Lesser General Public
|
|
// License as published by the Free Software Foundation; either
|
|
// version 2.1 of the License, or (at your option) any later version.
|
|
//
|
|
// This library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
//
|
|
#endregion
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
|
|
namespace Duplicati.Library.SharpRSync
|
|
{
|
|
/// <summary>
|
|
/// This class contains various values and methods used for reading and writing
|
|
/// RDiff compatible binary files
|
|
/// </summary>
|
|
public class RDiffBinary
|
|
{
|
|
/// <summary>
|
|
/// A small helper to determine the endianness of the machine
|
|
/// </summary>
|
|
private static readonly byte[] ENDIAN = BitConverter.GetBytes((short)1);
|
|
/// <summary>
|
|
/// The magic header value in an RDiff signature file
|
|
/// </summary>
|
|
public static readonly byte[] SIGNATURE_MAGIC = { (byte)'r', (byte)'s', 0x1, (byte)'6' };
|
|
/// <summary>
|
|
/// The magic header value in an RDiff delta file
|
|
/// </summary>
|
|
public static readonly byte[] DELTA_MAGIC = { (byte)'r', (byte)'s', 0x2, (byte)'6' };
|
|
|
|
/// <summary>
|
|
/// Denotes the end of a delta stream.
|
|
/// </summary>
|
|
public const byte EndCommand = 0x0;
|
|
/// <summary>
|
|
/// This is the max number of literal bytes that can be encoded without having the size present.
|
|
/// The delta files created from this library does not contain these, but they can be read.
|
|
/// </summary>
|
|
public static byte LiteralLimit = 0x40;
|
|
|
|
/// <summary>
|
|
/// This is a lookup table to find a copy command.
|
|
/// The first index is size of the offset in the original file,
|
|
/// the second index is the size of the length of the block to copy.
|
|
/// </summary>
|
|
public static readonly CopyDeltaCommand[][] CopyCommand =
|
|
{
|
|
new CopyDeltaCommand[] {CopyDeltaCommand.Copy_Byte_Byte, CopyDeltaCommand.Copy_Short_Byte, CopyDeltaCommand.Copy_Int_Byte, CopyDeltaCommand.Copy_Long_Byte },
|
|
new CopyDeltaCommand[] {CopyDeltaCommand.Copy_Byte_Short, CopyDeltaCommand.Copy_Short_Short, CopyDeltaCommand.Copy_Int_Short, CopyDeltaCommand.Copy_Long_Short},
|
|
new CopyDeltaCommand[] {CopyDeltaCommand.Copy_Byte_Int, CopyDeltaCommand.Copy_Short_Int, CopyDeltaCommand.Copy_Int_Int, CopyDeltaCommand.Copy_Long_Int },
|
|
new CopyDeltaCommand[] {CopyDeltaCommand.Copy_Byte_Long, CopyDeltaCommand.Copy_Short_Long, CopyDeltaCommand.Copy_Int_Long, CopyDeltaCommand.Copy_Long_Long },
|
|
};
|
|
|
|
/// <summary>
|
|
/// Determines the number of bytes required to encode the given size
|
|
/// </summary>
|
|
/// <param name="size">The length to encode</param>
|
|
/// <returns>The number of bytes required</returns>
|
|
public static int FindLength(long size)
|
|
{
|
|
int count = 1;
|
|
if (size > byte.MaxValue)
|
|
count++;
|
|
if (size > ushort.MaxValue)
|
|
count += 2;
|
|
if (size > uint.MaxValue)
|
|
count += 4;
|
|
if (size > long.MaxValue)
|
|
throw new Exception(string.Format(Strings.RDiffBinary.ValueTooLargeError, long.MaxValue));
|
|
return count;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decodes the size parameter.
|
|
/// </summary>
|
|
/// <param name="data">The data to decode</param>
|
|
/// <returns>The decoded size value</returns>
|
|
public static long DecodeLength(byte[] data)
|
|
{
|
|
if (data.Length == 1)
|
|
return (long)data[0];
|
|
else if (data.Length == 2)
|
|
return (long)BitConverter.ToUInt16(FixEndian(data), 0);
|
|
else if (data.Length == 4)
|
|
return (long)BitConverter.ToUInt32(FixEndian(data), 0);
|
|
else if (data.Length == 8)
|
|
{
|
|
long tmp = BitConverter.ToInt64(FixEndian(data), 0);
|
|
if (tmp < 0)
|
|
throw new Exception(string.Format(Strings.RDiffBinary.SizeTooLargeError, long.MaxValue));
|
|
return tmp;
|
|
}
|
|
else
|
|
throw new Exception(Strings.RDiffBinary.InvalidDataLengthError);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns an encoded array with the given size value,
|
|
/// using the least possible number of bytes.
|
|
/// The value is fixed with regards to endianness.
|
|
/// </summary>
|
|
/// <param name="size">The value to write</param>
|
|
/// <returns>The written bytes</returns>
|
|
public static byte[] EncodeLength(long size)
|
|
{
|
|
int len = FindLength(size);
|
|
if (len == 1)
|
|
return new byte[] { (byte)size };
|
|
else if (len == 2)
|
|
return FixEndian(BitConverter.GetBytes((short)size));
|
|
else if (len == 4)
|
|
return FixEndian(BitConverter.GetBytes((int)size));
|
|
else if (len == 8)
|
|
return FixEndian(BitConverter.GetBytes((long)size));
|
|
else
|
|
throw new Exception(Strings.RDiffBinary.EncodedLengthError);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the correct delta copy command, given the offset and size.
|
|
/// </summary>
|
|
/// <param name="offset">The offset in the file where the copy begins</param>
|
|
/// <param name="size">The size of the copy</param>
|
|
/// <returns>The delta copy command</returns>
|
|
public static CopyDeltaCommand FindCopyDeltaCommand(long offset, long size)
|
|
{
|
|
int i1 = FindLength(offset);
|
|
int i2 = FindLength(size);
|
|
|
|
if (i1 == 4) i1 = 3;
|
|
if (i1 == 8) i1 = 4;
|
|
if (i2 == 4) i2 = 3;
|
|
if (i2 == 8) i2 = 4;
|
|
|
|
return CopyCommand[i2 - 1][i1 - 1];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the literal delta command for the given size
|
|
/// </summary>
|
|
/// <param name="size">The number that is to be encoded</param>
|
|
/// <returns>The literal delta command</returns>
|
|
public static LiteralDeltaCommand FindLiteralDeltaCommand(long size)
|
|
{
|
|
switch (FindLength(size))
|
|
{
|
|
case 1:
|
|
return LiteralDeltaCommand.LiteralSizeByte;
|
|
case 2:
|
|
return LiteralDeltaCommand.LiteralSizeShort;
|
|
case 4:
|
|
return LiteralDeltaCommand.LiteralSizeInt;
|
|
case 8:
|
|
return LiteralDeltaCommand.LiteralSizeLong;
|
|
default:
|
|
throw new Exception("Invalid size for encoded value!");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the number of bytes the copy commands offset argument occupies.
|
|
/// </summary>
|
|
/// <param name="command">The copy command to evaluate</param>
|
|
/// <returns>The number of bytes the copy commands offset argument occupies.</returns>
|
|
public static int GetCopyOffsetSize(CopyDeltaCommand command)
|
|
{
|
|
switch (command)
|
|
{
|
|
case CopyDeltaCommand.Copy_Byte_Byte:
|
|
case CopyDeltaCommand.Copy_Byte_Short:
|
|
case CopyDeltaCommand.Copy_Byte_Int:
|
|
case CopyDeltaCommand.Copy_Byte_Long:
|
|
return 1;
|
|
case CopyDeltaCommand.Copy_Short_Byte:
|
|
case CopyDeltaCommand.Copy_Short_Short:
|
|
case CopyDeltaCommand.Copy_Short_Int:
|
|
case CopyDeltaCommand.Copy_Short_Long:
|
|
return 2;
|
|
case CopyDeltaCommand.Copy_Int_Byte:
|
|
case CopyDeltaCommand.Copy_Int_Short:
|
|
case CopyDeltaCommand.Copy_Int_Int:
|
|
case CopyDeltaCommand.Copy_Int_Long:
|
|
return 4;
|
|
case CopyDeltaCommand.Copy_Long_Byte:
|
|
case CopyDeltaCommand.Copy_Long_Short:
|
|
case CopyDeltaCommand.Copy_Long_Int:
|
|
case CopyDeltaCommand.Copy_Long_Long:
|
|
return 8;
|
|
default:
|
|
throw new Exception(string.Format(Strings.RDiffBinary.InvalidDeltaCopyCommandError, command));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the number of bytes the copy commands length argument occupies.
|
|
/// </summary>
|
|
/// <param name="command">The copy command to evaluate</param>
|
|
/// <returns>The number of bytes the copy commands length argument occupies.</returns>
|
|
public static int GetCopyLengthSize(CopyDeltaCommand command)
|
|
{
|
|
switch (command)
|
|
{
|
|
case CopyDeltaCommand.Copy_Byte_Byte:
|
|
case CopyDeltaCommand.Copy_Short_Byte:
|
|
case CopyDeltaCommand.Copy_Int_Byte:
|
|
case CopyDeltaCommand.Copy_Long_Byte:
|
|
return 1;
|
|
|
|
case CopyDeltaCommand.Copy_Byte_Short:
|
|
case CopyDeltaCommand.Copy_Short_Short:
|
|
case CopyDeltaCommand.Copy_Int_Short:
|
|
case CopyDeltaCommand.Copy_Long_Short:
|
|
return 2;
|
|
case CopyDeltaCommand.Copy_Byte_Int:
|
|
case CopyDeltaCommand.Copy_Short_Int:
|
|
case CopyDeltaCommand.Copy_Int_Int:
|
|
case CopyDeltaCommand.Copy_Long_Int:
|
|
return 4;
|
|
case CopyDeltaCommand.Copy_Byte_Long:
|
|
case CopyDeltaCommand.Copy_Short_Long:
|
|
case CopyDeltaCommand.Copy_Int_Long:
|
|
case CopyDeltaCommand.Copy_Long_Long:
|
|
return 8;
|
|
default:
|
|
throw new Exception(string.Format(Strings.RDiffBinary.InvalidDeltaCopyCommandError, command));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the number of bytes the literal command's size argument fills
|
|
/// </summary>
|
|
/// <param name="command">The command to find the size for</param>
|
|
/// <returns>The number of bytes the literal command's size argument fills</returns>
|
|
public static int GetLiteralLength(LiteralDeltaCommand command)
|
|
{
|
|
switch (command)
|
|
{
|
|
case LiteralDeltaCommand.LiteralSizeByte:
|
|
return 1;
|
|
case LiteralDeltaCommand.LiteralSizeShort:
|
|
return 2;
|
|
case LiteralDeltaCommand.LiteralSizeInt:
|
|
return 4;
|
|
case LiteralDeltaCommand.LiteralSizeLong:
|
|
return 8;
|
|
default:
|
|
throw new Exception(string.Format(Strings.RDiffBinary.InvalidLiteralCommand, command));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reverses endian order, if required
|
|
/// </summary>
|
|
/// <param name="data">The data to reverse</param>
|
|
/// <returns>The reversed data</returns>
|
|
public static byte[] FixEndian(byte[] data)
|
|
{
|
|
if (ENDIAN[0] != 0)
|
|
Array.Reverse(data);
|
|
return data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This table contains all the delta commands for literal data
|
|
/// </summary>
|
|
public enum LiteralDeltaCommand : byte
|
|
{
|
|
/// <summary>
|
|
/// The next byte contains the number of bytes with literal data that follows
|
|
/// </summary>
|
|
LiteralSizeByte = 0x41,
|
|
/// <summary>
|
|
/// The next short contains the number of bytes with literal data that follows
|
|
/// </summary>
|
|
LiteralSizeShort = 0x42,
|
|
/// <summary>
|
|
/// The next integer contains the number of bytes with literal data that follows
|
|
/// </summary>
|
|
LiteralSizeInt = 0x43,
|
|
/// <summary>
|
|
/// The next long contains the number of bytes with literal data that follows
|
|
/// </summary>
|
|
LiteralSizeLong = 0x44,
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// These are the possible commands to specify a copy.
|
|
/// The first item is the offset in the original file,
|
|
/// the second item is the length of the block to copy.
|
|
/// </summary>
|
|
public enum CopyDeltaCommand : byte
|
|
{
|
|
Copy_Byte_Byte = 0x45,
|
|
Copy_Byte_Short = 0x46,
|
|
Copy_Byte_Int = 0x47,
|
|
Copy_Byte_Long = 0x48,
|
|
|
|
Copy_Short_Byte = 0x49,
|
|
Copy_Short_Short = 0x4a,
|
|
Copy_Short_Int = 0x4b,
|
|
Copy_Short_Long = 0x4c,
|
|
|
|
Copy_Int_Byte = 0x4d,
|
|
Copy_Int_Short = 0x4e,
|
|
Copy_Int_Int = 0x4f,
|
|
Copy_Int_Long = 0x50,
|
|
|
|
Copy_Long_Byte = 0x51,
|
|
Copy_Long_Short = 0x52,
|
|
Copy_Long_Int = 0x53,
|
|
Copy_Long_Long = 0x54,
|
|
}
|
|
|
|
|
|
}
|
|
}
|