Improve HierarchicalRomFileTable performance

This commit is contained in:
Alex Barney 2019-02-03 11:55:30 -06:00
parent 5457a81068
commit 0520c25c37
4 changed files with 231 additions and 144 deletions

View file

@ -52,6 +52,23 @@ namespace LibHac
return (candidate == 2); return (candidate == 2);
} }
public static int GetHashTableEntryCount(int entries)
{
uint count = (uint) entries;
if (entries < 3)
count = 3;
else if (count < 19)
count |= 1;
else
{
while (count % 2 == 0 || count % 3 == 0 || count % 5 == 0 || count % 7 == 0 || count % 11 == 0 || count % 13 == 0 || count % 17 == 0)
{
count++;
}
}
return (int) count;
}
public static int GetPrime(int min) public static int GetPrime(int min)
{ {
if (min < 0) if (min < 0)
@ -68,6 +85,7 @@ namespace LibHac
//outside of our predefined table. //outside of our predefined table.
//compute the hard way. //compute the hard way.
for (int i = (min | 1); i < int.MaxValue; i += 2) for (int i = (min | 1); i < int.MaxValue; i += 2)
{ {
if (IsPrime(i) && ((i - 1) % HashPrime != 0)) if (IsPrime(i) && ((i - 1) % HashPrime != 0))

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace LibHac.IO namespace LibHac.IO
@ -99,14 +100,16 @@ namespace LibHac.IO
public static ReadOnlySpan<byte> GetParentDirectory(ReadOnlySpan<byte> path) public static ReadOnlySpan<byte> GetParentDirectory(ReadOnlySpan<byte> path)
{ {
Debug.Assert(IsNormalized(path));
int i = path.Length - 1; int i = path.Length - 1;
// A trailing separator should be ignored // A trailing separator should be ignored
if (path.Length > 0 && path[i] == '/') i--; if (path[i] == '/') i--;
while (i >= 0 && path[i] != '/') i--; while (i >= 1 && path[i] != '/') i--;
if (i < 1) return new ReadOnlySpan<byte>(new[] { (byte)'/' }); i = Math.Max(i, 1);
return path.Slice(0, i); return path.Slice(0, i);
} }

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
@ -56,7 +57,7 @@ namespace LibHac.IO.RomFs
public bool OpenFile(string path, out RomFileInfo fileInfo) public bool OpenFile(string path, out RomFileInfo fileInfo)
{ {
FindFileRecursive(GetUtf8Bytes(path), out RomEntryKey key, out _); FindFileRecursive(GetUtf8Bytes(path), out RomEntryKey key);
if (FileTable.TryGetValue(ref key, out RomKeyValuePair<FileRomEntry> keyValuePair)) if (FileTable.TryGetValue(ref key, out RomKeyValuePair<FileRomEntry> keyValuePair))
{ {
@ -82,7 +83,7 @@ namespace LibHac.IO.RomFs
public bool OpenDirectory(string path, out FindPosition position) public bool OpenDirectory(string path, out FindPosition position)
{ {
FindDirectoryRecursive(GetUtf8Bytes(path), out RomEntryKey key, out _); FindDirectoryRecursive(GetUtf8Bytes(path), out RomEntryKey key);
if (DirectoryTable.TryGetValue(ref key, out RomKeyValuePair<DirectoryRomEntry> keyValuePair)) if (DirectoryTable.TryGetValue(ref key, out RomKeyValuePair<DirectoryRomEntry> keyValuePair))
{ {
@ -113,33 +114,48 @@ namespace LibHac.IO.RomFs
public bool FindNextFile(ref FindPosition position, out RomFileInfo info, out string name) public bool FindNextFile(ref FindPosition position, out RomFileInfo info, out string name)
{ {
if (FileTable.TryGetValue(position.NextFile, out RomKeyValuePair<FileRomEntry> keyValuePair)) if (position.NextFile == -1)
{ {
position.NextFile = keyValuePair.Value.NextSibling;
info = keyValuePair.Value.Info;
name = Encoding.UTF8.GetString(keyValuePair.Key.Name.ToArray());
return true;
}
info = default; info = default;
name = default; name = default;
return false; return false;
} }
public bool FindNextDirectory(ref FindPosition position, out string name) ref FileRomEntry entry = ref FileTable.GetValueReference(position.NextFile, out Span<byte> nameBytes);
{ position.NextFile = entry.NextSibling;
if (DirectoryTable.TryGetValue(position.NextDirectory, out RomKeyValuePair<DirectoryRomEntry> keyValuePair)) info = entry.Info;
{
position.NextDirectory = keyValuePair.Value.NextSibling; name = GetUtf8String(nameBytes);
name = Encoding.UTF8.GetString(keyValuePair.Key.Name.ToArray());
return true; return true;
} }
public bool FindNextDirectory(ref FindPosition position, out string name)
{
if (position.NextDirectory == -1)
{
name = default; name = default;
return false; return false;
} }
public void CreateRootDirectory() ref DirectoryRomEntry entry = ref DirectoryTable.GetValueReference(position.NextDirectory, out Span<byte> nameBytes);
position.NextDirectory = entry.NextSibling;
name = GetUtf8String(nameBytes);
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string GetUtf8String(ReadOnlySpan<byte> value)
{
#if NETFRAMEWORK
return Encoding.UTF8.GetString(value.ToArray());
#else
return Encoding.UTF8.GetString(value);
#endif
}
private void CreateRootDirectory()
{ {
var key = new RomEntryKey(ReadOnlySpan<byte>.Empty, 0); var key = new RomEntryKey(ReadOnlySpan<byte>.Empty, 0);
var entry = new DirectoryRomEntry(); var entry = new DirectoryRomEntry();
@ -155,158 +171,144 @@ namespace LibHac.IO.RomFs
path = PathTools.Normalize(path); path = PathTools.Normalize(path);
ReadOnlySpan<byte> pathBytes = GetUtf8Bytes(path); ReadOnlySpan<byte> pathBytes = GetUtf8Bytes(path);
ReadOnlySpan<byte> parentPath = PathTools.GetParentDirectory(pathBytes); CreateFileRecursiveInternal(pathBytes, ref fileInfo);
CreateDirectoryRecursiveInternal(parentPath);
FindFileRecursive(pathBytes, out RomEntryKey key, out RomKeyValuePair<DirectoryRomEntry> parentEntry);
if (EntryExists(ref key))
{
throw new ArgumentException("Path already exists.");
}
var entry = new FileRomEntry();
entry.NextSibling = -1;
entry.Info = fileInfo;
int offset = FileTable.Insert(ref key, ref entry);
if (parentEntry.Value.Pos.NextFile == -1)
{
parentEntry.Value.Pos.NextFile = offset;
DirectoryTable.TrySetValue(ref parentEntry.Key, ref parentEntry.Value);
return;
}
int nextOffset = parentEntry.Value.Pos.NextFile;
while (FileTable.TryGetValue(nextOffset, out RomKeyValuePair<FileRomEntry> chainEntry))
{
if (chainEntry.Value.NextSibling == -1)
{
chainEntry.Value.NextSibling = offset;
FileTable.TrySetValue(ref chainEntry.Key, ref chainEntry.Value);
return;
}
nextOffset = chainEntry.Value.NextSibling;
}
}
public void CreateDirectoryRecursive(string path)
{
path = PathTools.Normalize(path);
CreateDirectoryRecursiveInternal(GetUtf8Bytes(path));
}
private void CreateDirectoryRecursiveInternal(ReadOnlySpan<byte> path)
{
for (int i = 1; i < path.Length; i++)
{
if (path[i] == '/')
{
ReadOnlySpan<byte> subPath = path.Slice(0, i);
CreateDirectoryInternal(subPath);
}
}
CreateDirectoryInternal(path);
} }
public void CreateDirectory(string path) public void CreateDirectory(string path)
{ {
path = PathTools.Normalize(path); path = PathTools.Normalize(path);
CreateDirectoryInternal(GetUtf8Bytes(path)); CreateDirectoryRecursive(GetUtf8Bytes(path));
} }
private void CreateDirectoryInternal(ReadOnlySpan<byte> path) private void CreateDirectoryRecursive(ReadOnlySpan<byte> path)
{ {
FindDirectoryRecursive(path, out RomEntryKey key, out RomKeyValuePair<DirectoryRomEntry> parentEntry); var parser = new PathParser(path);
var key = new RomEntryKey();
if (EntryExists(ref key)) int prevOffset = 0;
while (parser.TryGetNext(out key.Name))
{ {
return; int offset = DirectoryTable.GetOffsetFromKey(ref key);
// throw new ArgumentException("Path already exists."); if (offset < 0)
} {
ref DirectoryRomEntry entry = ref DirectoryTable.Insert(ref key, out offset, out _);
var entry = new DirectoryRomEntry();
entry.NextSibling = -1; entry.NextSibling = -1;
entry.Pos.NextDirectory = -1; entry.Pos.NextDirectory = -1;
entry.Pos.NextFile = -1; entry.Pos.NextFile = -1;
int offset = DirectoryTable.Insert(ref key, ref entry); ref DirectoryRomEntry parent = ref DirectoryTable.GetValueReference(prevOffset);
if (parentEntry.Value.Pos.NextDirectory == -1) if (parent.Pos.NextDirectory == -1)
{ {
parentEntry.Value.Pos.NextDirectory = offset; parent.Pos.NextDirectory = offset;
DirectoryTable.TrySetValue(ref parentEntry.Key, ref parentEntry.Value);
return;
} }
else
int nextOffset = parentEntry.Value.Pos.NextDirectory;
while (nextOffset != -1)
{ {
DirectoryTable.TryGetValue(nextOffset, out RomKeyValuePair<DirectoryRomEntry> chainEntry); ref DirectoryRomEntry chain = ref DirectoryTable.GetValueReference(parent.Pos.NextDirectory);
if (chainEntry.Value.NextSibling == -1)
while (chain.NextSibling != -1)
{ {
chainEntry.Value.NextSibling = offset; chain = ref DirectoryTable.GetValueReference(chain.NextSibling);
DirectoryTable.TrySetValue(ref chainEntry.Key, ref chainEntry.Value);
return;
} }
nextOffset = chainEntry.Value.NextSibling; chain.NextSibling = offset;
} }
} }
private void FindFileRecursive(ReadOnlySpan<byte> path, out RomEntryKey key, out RomKeyValuePair<DirectoryRomEntry> parentEntry) prevOffset = offset;
key.Parent = offset;
}
}
private void CreateFileRecursiveInternal(ReadOnlySpan<byte> path, ref RomFileInfo fileInfo)
{ {
var parser = new PathParser(path); var parser = new PathParser(path);
FindParentDirectoryRecursive(ref parser, out parentEntry); var key = new RomEntryKey();
key = new RomEntryKey(parser.GetCurrent(), parentEntry.Offset); parser.TryGetNext(out key.Name);
int prevOffset = 0;
while (!parser.IsFinished())
{
int offset = DirectoryTable.GetOffsetFromKey(ref key);
if (offset < 0)
{
ref DirectoryRomEntry entry = ref DirectoryTable.Insert(ref key, out offset, out _);
entry.NextSibling = -1;
entry.Pos.NextDirectory = -1;
entry.Pos.NextFile = -1;
ref DirectoryRomEntry parent = ref DirectoryTable.GetValueReference(prevOffset);
if (parent.Pos.NextDirectory == -1)
{
parent.Pos.NextDirectory = offset;
}
else
{
ref DirectoryRomEntry chain = ref DirectoryTable.GetValueReference(parent.Pos.NextDirectory);
while (chain.NextSibling != -1)
{
chain = ref DirectoryTable.GetValueReference(chain.NextSibling);
} }
private void FindDirectoryRecursive(ReadOnlySpan<byte> path, out RomEntryKey key, out RomKeyValuePair<DirectoryRomEntry> parentEntry) chain.NextSibling = offset;
}
}
prevOffset = offset;
key.Parent = offset;
parser.TryGetNext(out key.Name);
}
{
ref FileRomEntry entry = ref FileTable.Insert(ref key, out int offset, out _);
entry.NextSibling = -1;
entry.Info = fileInfo;
ref DirectoryRomEntry parent = ref DirectoryTable.GetValueReference(prevOffset);
if (parent.Pos.NextFile == -1)
{
parent.Pos.NextFile = offset;
}
else
{
ref FileRomEntry chain = ref FileTable.GetValueReference(parent.Pos.NextFile);
while (chain.NextSibling != -1)
{
chain = ref FileTable.GetValueReference(chain.NextSibling);
}
chain.NextSibling = offset;
}
}
}
private void FindFileRecursive(ReadOnlySpan<byte> path, out RomEntryKey key)
{ {
var parser = new PathParser(path); var parser = new PathParser(path);
FindParentDirectoryRecursive(ref parser, out parentEntry); key = default;
ReadOnlySpan<byte> name = parser.GetCurrent();
int parentOffset = name.Length == 0 ? 0 : parentEntry.Offset;
key = new RomEntryKey(name, parentOffset);
}
private void FindParentDirectoryRecursive(ref PathParser parser, out RomKeyValuePair<DirectoryRomEntry> keyValuePair)
{
keyValuePair = default;
RomEntryKey key = default;
while (parser.TryGetNext(out key.Name) && !parser.IsFinished()) while (parser.TryGetNext(out key.Name) && !parser.IsFinished())
{ {
DirectoryTable.TryGetValue(ref key, out keyValuePair); key.Parent = DirectoryTable.GetOffsetFromKey(ref key);
key.Parent = keyValuePair.Offset;
}
// The above loop won't run for top-level directories, so
// manually return the root directory for them
if (key.Parent == 0)
{
DirectoryTable.TryGetValue(0, out keyValuePair);
} }
} }
private bool EntryExists(ref RomEntryKey key) private void FindDirectoryRecursive(ReadOnlySpan<byte> path, out RomEntryKey key)
{ {
return DirectoryTable.ContainsKey(ref key) || var parser = new PathParser(path);
FileTable.ContainsKey(ref key); key = default;
while (parser.TryGetNext(out key.Name) && !parser.IsFinished())
{
key.Parent = DirectoryTable.GetOffsetFromKey(ref key);
}
} }
[StructLayout(LayoutKind.Sequential, Pack = 4)] [StructLayout(LayoutKind.Sequential, Pack = 4)]

View file

@ -29,7 +29,7 @@ namespace LibHac.IO.RomFs
public RomFsDictionary(int capacity) public RomFsDictionary(int capacity)
{ {
int size = HashHelpers.GetPrime(capacity); int size = HashHelpers.GetHashTableEntryCount(capacity);
Buckets = new int[size]; Buckets = new int[size];
Buckets.AsSpan().Fill(-1); Buckets.AsSpan().Fill(-1);
@ -85,6 +85,13 @@ namespace LibHac.IO.RomFs
return true; return true;
} }
public ref T GetValue(int offset, out Span<byte> name)
{
ref RomFsEntry entry = ref GetEntryReference(offset, out name);
return ref entry.Value;
}
public bool ContainsKey(ref RomEntryKey key) => FindEntry(ref key) >= 0; public bool ContainsKey(ref RomEntryKey key) => FindEntry(ref key) >= 0;
public int Insert(ref RomEntryKey key, ref T value) public int Insert(ref RomEntryKey key, ref T value)
@ -97,7 +104,7 @@ namespace LibHac.IO.RomFs
uint hashCode = key.GetRomHashCode(); uint hashCode = key.GetRomHashCode();
int bucket = (int)(hashCode % Buckets.Length); int bucket = (int)(hashCode % Buckets.Length);
int newOffset = FindOffsetForInsert(ref key); int newOffset = FindOffsetForInsert(key.Name.Length);
ref RomFsEntry entry = ref GetEntryReference(newOffset, out Span<byte> name, key.Name.Length); ref RomFsEntry entry = ref GetEntryReference(newOffset, out Span<byte> name, key.Name.Length);
@ -113,9 +120,31 @@ namespace LibHac.IO.RomFs
return newOffset; return newOffset;
} }
private int FindOffsetForInsert(ref RomEntryKey key) public ref T Insert(ref RomEntryKey key, out int offset, out Span<byte> name)
{ {
int bytesNeeded = Util.AlignUp(_sizeOfEntry + key.Name.Length, 4); uint hashCode = key.GetRomHashCode();
int bucket = (int)(hashCode % Buckets.Length);
int newOffset = FindOffsetForInsert(key.Name.Length);
ref RomFsEntry entry = ref GetEntryReference(newOffset, out name, key.Name.Length);
entry.KeyLength = key.Name.Length;
entry.Next = Buckets[bucket];
entry.Parent = key.Parent;
key.Name.CopyTo(name);
Buckets[bucket] = newOffset;
_count++;
offset = newOffset;
return ref entry.Value;
}
private int FindOffsetForInsert(int nameLength)
{
int bytesNeeded = Util.AlignUp(_sizeOfEntry + nameLength, 4);
if (_length + bytesNeeded > _capacity) if (_length + bytesNeeded > _capacity)
{ {
@ -149,6 +178,27 @@ namespace LibHac.IO.RomFs
return i; return i;
} }
public int GetOffsetFromKey(ref RomEntryKey key)
{
uint hashCode = key.GetRomHashCode();
int index = (int)(hashCode % Buckets.Length);
int i = Buckets[index];
while (i != -1)
{
ref RomFsEntry entry = ref GetEntryReference(i, out Span<byte> name);
if (key.Parent == entry.Parent && key.Name.SequenceEqual(name))
{
break;
}
i = entry.Next;
}
return i;
}
private void EnsureCapacityBytes(int value) private void EnsureCapacityBytes(int value)
{ {
if (value < 0) throw new ArgumentOutOfRangeException(nameof(value)); if (value < 0) throw new ArgumentOutOfRangeException(nameof(value));
@ -216,6 +266,20 @@ namespace LibHac.IO.RomFs
Buckets = newBuckets; Buckets = newBuckets;
} }
public ref T GetValueReference(int offset)
{
ref RomFsEntry entry = ref MemoryMarshal.Cast<byte, RomFsEntry>(Entries.AsSpan(offset))[0];
return ref entry.Value;
}
public ref T GetValueReference(int offset, out Span<byte> name)
{
ref RomFsEntry entry = ref MemoryMarshal.Cast<byte, RomFsEntry>(Entries.AsSpan(offset))[0];
name = Entries.AsSpan(offset + _sizeOfEntry, entry.KeyLength);
return ref entry.Value;
}
private ref RomFsEntry GetEntryReference(int offset) private ref RomFsEntry GetEntryReference(int offset)
{ {
return ref MemoryMarshal.Cast<byte, RomFsEntry>(Entries.AsSpan(offset))[0]; return ref MemoryMarshal.Cast<byte, RomFsEntry>(Entries.AsSpan(offset))[0];