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:
RoosterDragon
2021-10-09 13:47:19 +01:00
committed by abcdefg30
parent 2ed4cb8aff
commit 3bee524c6b
2 changed files with 40 additions and 45 deletions

View File

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

View File

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