Avoid allocation in PackageEntry.HashFilename
This is a very frequently called method during loading, and taking some care to avoid allocations improves performance noticeably.
This commit is contained in:
@@ -9,6 +9,8 @@
|
|||||||
*/
|
*/
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace OpenRA.Mods.Cnc.FileFormats
|
namespace OpenRA.Mods.Cnc.FileFormats
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -20,7 +22,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The CRC32 lookup table
|
/// The CRC32 lookup table
|
||||||
/// </summary>
|
/// </summary>
|
||||||
static uint[] lookUp = new uint[256]
|
static readonly uint[] LookUp = new uint[256]
|
||||||
{
|
{
|
||||||
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
|
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
|
||||||
0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
|
0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
|
||||||
@@ -89,47 +91,32 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A fast (native) CRC32 implementation that can be used on a regular byte arrays.
|
/// A CRC32 implementation that can be used on spans of bytes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="data">The data from which to calculate the checksum.</param>
|
/// <param name="data">The data from which to calculate the checksum.</param>
|
||||||
/// <param name="polynomial">The polynomial.</param>
|
/// <param name="polynomial">The polynomial to XOR with.</param>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// The calculated checksum.
|
/// The calculated checksum.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public static uint Calculate(byte[] data, uint polynomial)
|
public static uint Calculate(ReadOnlySpan<byte> data, uint polynomial)
|
||||||
{
|
{
|
||||||
var crc = polynomial;
|
var crc = polynomial;
|
||||||
for (var i = 0; i < data.Length; i++)
|
for (var i = 0; i < data.Length; i++)
|
||||||
crc = (crc >> 8) ^ lookUp[(crc & 0xFF) ^ data[i]];
|
crc = (crc >> 8) ^ LookUp[(crc & 0xFF) ^ data[i]];
|
||||||
crc ^= polynomial;
|
crc ^= polynomial;
|
||||||
return crc;
|
return crc;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static uint Calculate(byte[] data)
|
|
||||||
{
|
|
||||||
return Calculate(data, 0xFFFFFFFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A fast (native) CRC32 implementation that can be used on a pinned byte array using
|
/// A CRC32 implementation that can be used on spans of bytes, with default polynomial.
|
||||||
/// default polynomial.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="data"> [in,out] If non-null, the.</param>
|
/// <param name="data">The data from which to calculate the checksum.</param>
|
||||||
/// <param name="len"> The length of the data.</param>
|
/// <returns>
|
||||||
/// <param name="polynomial">The polynomial to XOR with.</param>
|
/// The calculated checksum.
|
||||||
/// <returns>The calculated checksum.</returns>
|
/// </returns>
|
||||||
public static unsafe uint Calculate(byte* data, uint len, uint polynomial)
|
public static uint Calculate(ReadOnlySpan<byte> data)
|
||||||
{
|
{
|
||||||
var crc = polynomial;
|
return Calculate(data, 0xFFFFFFFF);
|
||||||
for (var i = 0; i < len; i++)
|
|
||||||
crc = (crc >> 8) ^ lookUp[(crc & 0xFF) ^ *data++];
|
|
||||||
crc ^= polynomial;
|
|
||||||
return crc;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static unsafe uint Calculate(byte* data, uint len)
|
|
||||||
{
|
|
||||||
return Calculate(data, len, 0xFFFFFFFF);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using OpenRA.Mods.Cnc.FileFormats;
|
using OpenRA.Mods.Cnc.FileFormats;
|
||||||
|
|
||||||
@@ -57,40 +58,47 @@ namespace OpenRA.Mods.Cnc.FileSystem
|
|||||||
|
|
||||||
public static uint HashFilename(string name, PackageHashType type)
|
public static uint HashFilename(string name, PackageHashType type)
|
||||||
{
|
{
|
||||||
|
var padding = name.Length % 4 != 0 ? 4 - name.Length % 4 : 0;
|
||||||
|
var paddedLength = name.Length + padding;
|
||||||
|
|
||||||
|
// Avoid stack overflows by only allocating small buffers on the stack, and larger ones on the heap.
|
||||||
|
// 64 chars covers most real filenames.
|
||||||
|
var upperPaddedName = paddedLength < 64 ? stackalloc char[paddedLength] : new char[paddedLength];
|
||||||
|
name.AsSpan().ToUpperInvariant(upperPaddedName);
|
||||||
|
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case PackageHashType.Classic:
|
case PackageHashType.Classic:
|
||||||
{
|
{
|
||||||
name = name.ToUpperInvariant();
|
for (var p = 0; p < padding; p++)
|
||||||
if (name.Length % 4 != 0)
|
upperPaddedName[paddedLength - 1 - p] = '\0';
|
||||||
name = name.PadRight(name.Length + (4 - name.Length % 4), '\0');
|
|
||||||
|
|
||||||
|
var asciiBytes = paddedLength < 64 ? stackalloc byte[paddedLength] : new byte[paddedLength];
|
||||||
|
Encoding.ASCII.GetBytes(upperPaddedName, asciiBytes);
|
||||||
|
|
||||||
|
var data = MemoryMarshal.Cast<byte, uint>(asciiBytes);
|
||||||
var result = 0u;
|
var result = 0u;
|
||||||
var data = Encoding.ASCII.GetBytes(name);
|
foreach (var next in data)
|
||||||
var i = 0;
|
|
||||||
while (i < data.Length)
|
|
||||||
{
|
|
||||||
var next = (uint)(data[i++] | data[i++] << 8 | data[i++] << 16 | data[i++] << 24);
|
|
||||||
result = ((result << 1) | (result >> 31)) + next;
|
result = ((result << 1) | (result >> 31)) + next;
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
case PackageHashType.CRC32:
|
case PackageHashType.CRC32:
|
||||||
{
|
{
|
||||||
name = name.ToUpperInvariant();
|
var length = name.Length;
|
||||||
var l = name.Length;
|
var lengthRoundedDownToFour = length / 4 * 4;
|
||||||
var a = l >> 2;
|
if (length != lengthRoundedDownToFour)
|
||||||
if ((l & 3) != 0)
|
|
||||||
{
|
{
|
||||||
name += (char)(l - (a << 2));
|
upperPaddedName[length] = (char)(length - lengthRoundedDownToFour);
|
||||||
var i = 3 - (l & 3);
|
for (var p = 1; p < padding; p++)
|
||||||
while (i-- != 0)
|
upperPaddedName[length + p] = upperPaddedName[lengthRoundedDownToFour];
|
||||||
name += name[a << 2];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return CRC32.Calculate(Encoding.ASCII.GetBytes(name));
|
var asciiBytes = paddedLength < 64 ? stackalloc byte[paddedLength] : new byte[paddedLength];
|
||||||
|
Encoding.ASCII.GetBytes(upperPaddedName, asciiBytes);
|
||||||
|
|
||||||
|
return CRC32.Calculate(asciiBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
default: throw new NotImplementedException($"Unknown hash type `{type}`");
|
default: throw new NotImplementedException($"Unknown hash type `{type}`");
|
||||||
|
|||||||
Reference in New Issue
Block a user