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

@@ -12,6 +12,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using OpenRA.Mods.Cnc.FileFormats;
@@ -57,40 +58,47 @@ namespace OpenRA.Mods.Cnc.FileSystem
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)
{
case PackageHashType.Classic:
{
name = name.ToUpperInvariant();
if (name.Length % 4 != 0)
name = name.PadRight(name.Length + (4 - name.Length % 4), '\0');
for (var p = 0; p < padding; p++)
upperPaddedName[paddedLength - 1 - p] = '\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 data = Encoding.ASCII.GetBytes(name);
var i = 0;
while (i < data.Length)
{
var next = (uint)(data[i++] | data[i++] << 8 | data[i++] << 16 | data[i++] << 24);
foreach (var next in data)
result = ((result << 1) | (result >> 31)) + next;
}
return result;
}
case PackageHashType.CRC32:
{
name = name.ToUpperInvariant();
var l = name.Length;
var a = l >> 2;
if ((l & 3) != 0)
var length = name.Length;
var lengthRoundedDownToFour = length / 4 * 4;
if (length != lengthRoundedDownToFour)
{
name += (char)(l - (a << 2));
var i = 3 - (l & 3);
while (i-- != 0)
name += name[a << 2];
upperPaddedName[length] = (char)(length - lengthRoundedDownToFour);
for (var p = 1; p < padding; p++)
upperPaddedName[length + p] = upperPaddedName[lengthRoundedDownToFour];
}
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}`");