Don't use IStorage for reading the romfs file table.

It's at least 2-3x faster reading it all from a byte array
This commit is contained in:
Alex Barney 2019-01-30 15:31:53 -06:00
parent 7004b22958
commit 34e926f2a4
6 changed files with 182 additions and 153 deletions

View file

@ -5,12 +5,12 @@ namespace LibHac.IO
{ {
public ref struct PathParser public ref struct PathParser
{ {
private ReadOnlySpan<char> _path; private ReadOnlySpan<byte> _path;
private int _offset; private int _offset;
private int _length; private int _length;
private bool _finished; private bool _finished;
public PathParser(ReadOnlySpan<char> path) public PathParser(ReadOnlySpan<byte> path)
{ {
Debug.Assert(PathTools.IsNormalized(path)); Debug.Assert(PathTools.IsNormalized(path));
@ -25,7 +25,7 @@ namespace LibHac.IO
_finished = false; _finished = false;
} }
public bool TryGetNext(out ReadOnlySpan<char> name) public bool TryGetNext(out ReadOnlySpan<byte> name)
{ {
bool success = MoveNext(); bool success = MoveNext();
name = GetCurrent(); name = GetCurrent();
@ -50,7 +50,7 @@ namespace LibHac.IO
return true; return true;
} }
public ReadOnlySpan<char> GetCurrent() public ReadOnlySpan<byte> GetCurrent()
{ {
return _path.Slice(_offset, _length); return _path.Slice(_offset, _length);
} }

View file

@ -7,10 +7,16 @@ namespace LibHac.IO
{ {
public static readonly char DirectorySeparator = '/'; public static readonly char DirectorySeparator = '/';
public static string Normalize(string inPath)
{
if (IsNormalized(inPath.AsSpan())) return inPath;
return NormalizeInternal(inPath);
}
// Licensed to the .NET Foundation under one or more agreements. // Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
public static string Normalize(string inPath) public static string NormalizeInternal(string inPath)
{ {
// Relative paths aren't a thing for IFileSystem, so assume all paths are absolute // Relative paths aren't a thing for IFileSystem, so assume all paths are absolute
// and add a '/' to the beginning of the path if it doesn't already begin with one // and add a '/' to the beginning of the path if it doesn't already begin with one
@ -111,7 +117,36 @@ namespace LibHac.IO
case NormalizeState.Dot when c == '/': return false; case NormalizeState.Dot when c == '/': return false;
case NormalizeState.Dot when c == '.': state = NormalizeState.DoubleDot; break; case NormalizeState.Dot when c == '.': state = NormalizeState.DoubleDot; break;
case NormalizeState.Dot: state = NormalizeState.Normal; break; case NormalizeState.Dot: state = NormalizeState.Normal; break;
case NormalizeState.DoubleDot when c == '/': return false;
case NormalizeState.DoubleDot: state = NormalizeState.Normal; break;
}
}
return state == NormalizeState.Normal || state == NormalizeState.Delimiter;
}
public static bool IsNormalized(ReadOnlySpan<byte> path)
{
var state = NormalizeState.Initial;
foreach (byte c in path)
{
switch (state)
{
case NormalizeState.Initial when c == '/': state = NormalizeState.Delimiter; break;
case NormalizeState.Initial: return false;
case NormalizeState.Normal when c == '/': state = NormalizeState.Delimiter; break;
case NormalizeState.Delimiter when c == '/': return false;
case NormalizeState.Delimiter when c == '.': state = NormalizeState.Dot; break;
case NormalizeState.Delimiter: state = NormalizeState.Normal; break;
case NormalizeState.Dot when c == '/': return false;
case NormalizeState.Dot when c == '.': state = NormalizeState.DoubleDot; break;
case NormalizeState.Dot: state = NormalizeState.Normal; break;
case NormalizeState.DoubleDot when c == '/': return false; case NormalizeState.DoubleDot when c == '/': return false;
case NormalizeState.DoubleDot: state = NormalizeState.Normal; break; case NormalizeState.DoubleDot: state = NormalizeState.Normal; break;
} }

View file

@ -1,5 +1,5 @@
using System; using System;
using System.Runtime.InteropServices; using System.Text;
namespace LibHac.IO.RomFs namespace LibHac.IO.RomFs
{ {
@ -27,11 +27,11 @@ namespace LibHac.IO.RomFs
public bool OpenFile(string path, out RomFileInfo fileInfo) public bool OpenFile(string path, out RomFileInfo fileInfo)
{ {
FindFileRecursive(path.AsSpan(), out RomEntryKey key); FindFileRecursive(GetUtf8Bytes(path), out RomEntryKey key);
if (FileTable.TryGetValue(ref key, out FileRomEntry entry, out int _)) if (FileTable.TryGetValue(ref key, out RomKeyValuePair<FileRomEntry> keyValuePair))
{ {
fileInfo = entry.Info; fileInfo = keyValuePair.Value.Info;
return true; return true;
} }
@ -41,9 +41,9 @@ namespace LibHac.IO.RomFs
public bool OpenFile(int offset, out RomFileInfo fileInfo) public bool OpenFile(int offset, out RomFileInfo fileInfo)
{ {
if (FileTable.TryGetValue(offset, out FileRomEntry entry)) if (FileTable.TryGetValue(offset, out RomKeyValuePair<FileRomEntry> keyValuePair))
{ {
fileInfo = entry.Info; fileInfo = keyValuePair.Value.Info;
return true; return true;
} }
@ -53,11 +53,11 @@ namespace LibHac.IO.RomFs
public bool OpenDirectory(string path, out FindPosition position) public bool OpenDirectory(string path, out FindPosition position)
{ {
FindDirectoryRecursive(path.AsSpan(), out RomEntryKey key); FindDirectoryRecursive(GetUtf8Bytes(path), out RomEntryKey key);
if (DirectoryTable.TryGetValue(ref key, out DirectoryRomEntry entry, out int _)) if (DirectoryTable.TryGetValue(ref key, out RomKeyValuePair<DirectoryRomEntry> keyValuePair))
{ {
position = entry.Pos; position = keyValuePair.Value.Pos;
return true; return true;
} }
@ -67,9 +67,9 @@ namespace LibHac.IO.RomFs
public bool OpenDirectory(int offset, out FindPosition position) public bool OpenDirectory(int offset, out FindPosition position)
{ {
if (DirectoryTable.TryGetValue(offset, out DirectoryRomEntry entry)) if (DirectoryTable.TryGetValue(offset, out RomKeyValuePair<DirectoryRomEntry> keyValuePair))
{ {
position = entry.Pos; position = keyValuePair.Value.Pos;
return true; return true;
} }
@ -77,121 +77,68 @@ namespace LibHac.IO.RomFs
return false; return false;
} }
private static ReadOnlySpan<byte> GetUtf8Bytes(string value)
{
return Encoding.UTF8.GetBytes(value).AsSpan();
}
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 FileRomEntry entry, out name)) if (FileTable.TryGetValue(position.NextFile, out RomKeyValuePair<FileRomEntry> keyValuePair))
{ {
position.NextFile = entry.NextSibling; position.NextFile = keyValuePair.Value.NextSibling;
info = entry.Info; info = keyValuePair.Value.Info;
name = Encoding.UTF8.GetString(keyValuePair.Key.Name.ToArray());
return true; return true;
} }
info = default; info = default;
name = default;
return false; return false;
} }
public bool FindNextDirectory(ref FindPosition position, out string name) public bool FindNextDirectory(ref FindPosition position, out string name)
{ {
if (DirectoryTable.TryGetValue(position.NextDirectory, out DirectoryRomEntry entry, out name)) if (DirectoryTable.TryGetValue(position.NextDirectory, out RomKeyValuePair<DirectoryRomEntry> keyValuePair))
{ {
position.NextDirectory = entry.NextSibling; position.NextDirectory = keyValuePair.Value.NextSibling;
name = Encoding.UTF8.GetString(keyValuePair.Key.Name.ToArray());
return true; return true;
} }
name = default;
return false; return false;
} }
private void FindFileRecursive(ReadOnlySpan<char> path, out RomEntryKey key) private void FindFileRecursive(ReadOnlySpan<byte> path, out RomEntryKey key)
{ {
var parser = new PathParser(path); var parser = new PathParser(path);
FindParentDirectoryRecursive(ref parser, out DirectoryRomEntry _, out int parentOffset); FindParentDirectoryRecursive(ref parser, out RomKeyValuePair<DirectoryRomEntry> keyValuePair);
key = new RomEntryKey(parser.GetCurrent(), parentOffset); key = keyValuePair.Key;
} }
private void FindDirectoryRecursive(ReadOnlySpan<char> path, out RomEntryKey key) private void FindDirectoryRecursive(ReadOnlySpan<byte> path, out RomEntryKey key)
{ {
var parser = new PathParser(path); var parser = new PathParser(path);
FindParentDirectoryRecursive(ref parser, out DirectoryRomEntry _, out int parentOffset); FindParentDirectoryRecursive(ref parser, out RomKeyValuePair<DirectoryRomEntry> keyValuePair);
ReadOnlySpan<char> name = parser.GetCurrent(); ReadOnlySpan<byte> name = parser.GetCurrent();
if (name.Length == 0) parentOffset = 0; int parentOffset = name.Length == 0 ? 0 : keyValuePair.Offset;
key = new RomEntryKey(name, parentOffset); key = new RomEntryKey(name, parentOffset);
} }
private void FindParentDirectoryRecursive(ref PathParser parser, out DirectoryRomEntry parentEntry, out int parentOffset) private void FindParentDirectoryRecursive(ref PathParser parser, out RomKeyValuePair<DirectoryRomEntry> keyValuePair)
{ {
parentEntry = default; keyValuePair = default;
parentOffset = default;
RomEntryKey key = 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 parentEntry, out parentOffset); DirectoryTable.TryGetValue(ref key, out keyValuePair);
key.Parent = parentOffset; key.Parent = keyValuePair.Offset;
} }
} }
} }
internal ref struct RomEntryKey
{
public ReadOnlySpan<char> Name;
public int Parent;
public RomEntryKey(ReadOnlySpan<char> name, int parent)
{
Name = name;
Parent = parent;
}
public uint GetRomHashCode()
{
uint hash = 123456789 ^ (uint)Parent;
foreach (char c in Name)
{
hash = c ^ ((hash << 27) | (hash >> 5));
}
return hash;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct RomFsEntry<T> where T : unmanaged
{
public int Parent;
public T Value;
public int Next;
public int KeyLength;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct FileRomEntry
{
public int NextSibling;
public RomFileInfo Info;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct RomFileInfo
{
public long Offset;
public long Length;
}
[StructLayout(LayoutKind.Sequential)]
public struct DirectoryRomEntry
{
public int NextSibling;
public FindPosition Pos;
}
[StructLayout(LayoutKind.Sequential)]
public struct FindPosition
{
public int NextDirectory;
public int NextFile;
}
} }

View file

@ -1,36 +1,32 @@
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
namespace LibHac.IO.RomFs namespace LibHac.IO.RomFs
{ {
internal class RomFsDictionary<T> where T : unmanaged internal class RomFsDictionary<T> where T : unmanaged
{ {
private int HashBucketCount { get; } private int[] BucketTable { get; }
private IStorage BucketStorage { get; } private byte[] EntryTable { get; }
private IStorage EntryStorage { get; }
// Hack around not being able to get the size of generic structures // Hack around not being able to get the size of generic structures
private readonly int _sizeOfEntry = 12 + Marshal.SizeOf<T>(); private readonly int _sizeOfEntry = 12 + Marshal.SizeOf<T>();
public RomFsDictionary(IStorage bucketStorage, IStorage entryStorage) public RomFsDictionary(IStorage bucketStorage, IStorage entryStorage)
{ {
BucketStorage = bucketStorage; BucketTable = bucketStorage.ToArray<int>();
EntryStorage = entryStorage; EntryTable = entryStorage.ToArray();
HashBucketCount = (int)(bucketStorage.Length / 4);
} }
public bool TryGetValue(ref RomEntryKey key, out T value, out int offset) public bool TryGetValue(ref RomEntryKey key, out RomKeyValuePair<T> value)
{ {
int i = FindEntry(ref key); int i = FindEntry(ref key);
offset = i;
if (i >= 0) if (i >= 0)
{ {
GetEntryInternal(i, out RomFsEntry<T> entry); GetEntryInternal(i, out RomFsEntry<T> entry);
value = entry.Value;
value = new RomKeyValuePair<T> { Key = key, Value = entry.Value, Offset = i };
return true; return true;
} }
@ -38,43 +34,32 @@ namespace LibHac.IO.RomFs
return false; return false;
} }
public bool TryGetValue(int offset, out T value, out string entryName) public bool TryGetValue(int offset, out RomKeyValuePair<T> value)
{ {
if (offset < 0 || offset + _sizeOfEntry >= EntryStorage.Length) if (offset < 0 || offset + _sizeOfEntry >= EntryTable.Length)
{
value = default;
entryName = default;
return false;
}
GetEntryInternal(offset, out RomFsEntry<T> entry, out entryName);
value = entry.Value;
return true;
}
public bool TryGetValue(int offset, out T value)
{
if (offset < 0 || offset + _sizeOfEntry >= EntryStorage.Length)
{ {
value = default; value = default;
return false; return false;
} }
GetEntryInternal(offset, out RomFsEntry<T> entry); value = new RomKeyValuePair<T>();
value = entry.Value;
GetEntryInternal(offset, out RomFsEntry<T> entry, out value.Key.Name);
value.Value = entry.Value;
return true; return true;
} }
private int FindEntry(ref RomEntryKey key) private int FindEntry(ref RomEntryKey key)
{ {
uint hashCode = key.GetRomHashCode(); uint hashCode = key.GetRomHashCode();
int i = GetBucket((int)(hashCode % HashBucketCount)); int index = (int)(hashCode % BucketTable.Length);
int i = BucketTable[index];
while (i != -1) while (i != -1)
{ {
GetEntryInternal(i, out RomFsEntry<T> entry); GetEntryInternal(i, out RomFsEntry<T> entry, out ReadOnlySpan<byte> name);
if (IsEqual(ref key, ref entry, i)) if (key.Parent == entry.Parent && key.Name.SequenceEqual(name))
{ {
break; break;
} }
@ -85,24 +70,12 @@ namespace LibHac.IO.RomFs
return i; return i;
} }
private bool IsEqual(ref RomEntryKey key, ref RomFsEntry<T> entry, int entryOffset)
{
if (key.Parent != entry.Parent) return false;
if (key.Name.Length != entry.KeyLength) return false;
GetEntryInternal(entryOffset, out RomFsEntry<T> _, out string name);
return key.Name.Equals(name.AsSpan(), StringComparison.Ordinal);
}
private void GetEntryInternal(int offset, out RomFsEntry<T> outEntry) private void GetEntryInternal(int offset, out RomFsEntry<T> outEntry)
{ {
Span<byte> b = stackalloc byte[_sizeOfEntry]; outEntry = MemoryMarshal.Read<RomFsEntry<T>>(EntryTable.AsSpan(offset));
EntryStorage.Read(b, offset);
outEntry = MemoryMarshal.Read<RomFsEntry<T>>(b);
} }
private void GetEntryInternal(int offset, out RomFsEntry<T> outEntry, out string entryName) private void GetEntryInternal(int offset, out RomFsEntry<T> outEntry, out ReadOnlySpan<byte> entryName)
{ {
GetEntryInternal(offset, out outEntry); GetEntryInternal(offset, out outEntry);
@ -111,18 +84,7 @@ namespace LibHac.IO.RomFs
throw new InvalidDataException("Rom entry name is too long."); throw new InvalidDataException("Rom entry name is too long.");
} }
var buf = new byte[outEntry.KeyLength]; entryName = EntryTable.AsSpan(offset + _sizeOfEntry, outEntry.KeyLength);
EntryStorage.Read(buf, offset + _sizeOfEntry);
entryName = Encoding.ASCII.GetString(buf);
}
private int GetBucket(int index)
{
Debug.Assert(index < HashBucketCount);
Span<byte> buf = stackalloc byte[4];
BucketStorage.Read(buf, index * 4);
return MemoryMarshal.Read<int>(buf);
} }
} }
} }

View file

@ -0,0 +1,73 @@
using System;
using System.Runtime.InteropServices;
namespace LibHac.IO.RomFs
{
internal ref struct RomEntryKey
{
public ReadOnlySpan<byte> Name;
public int Parent;
public RomEntryKey(ReadOnlySpan<byte> name, int parent)
{
Name = name;
Parent = parent;
}
public uint GetRomHashCode()
{
uint hash = 123456789 ^ (uint)Parent;
foreach (byte c in Name)
{
hash = c ^ ((hash << 27) | (hash >> 5));
}
return hash;
}
}
internal ref struct RomKeyValuePair<T> where T : unmanaged
{
public RomEntryKey Key;
public int Offset;
public T Value;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct RomFsEntry<T> where T : unmanaged
{
public int Parent;
public T Value;
public int Next;
public int KeyLength;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct FileRomEntry
{
public int NextSibling;
public RomFileInfo Info;
}
[StructLayout(LayoutKind.Sequential)]
public struct RomFileInfo
{
public long Offset;
public long Length;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct DirectoryRomEntry
{
public int NextSibling;
public FindPosition Pos;
}
[StructLayout(LayoutKind.Sequential)]
public struct FindPosition
{
public int NextDirectory;
public int NextFile;
}
}

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.IO; using System.IO;
using System.Runtime.InteropServices;
namespace LibHac.IO namespace LibHac.IO
{ {
@ -111,6 +112,17 @@ namespace LibHac.IO
return arr; return arr;
} }
public static T[] ToArray<T>(this IStorage storage) where T : unmanaged
{
if (storage == null) return new T[0];
var arr = new T[storage.Length / Marshal.SizeOf<T>()];
Span<byte> dest = MemoryMarshal.Cast<T, byte>(arr.AsSpan());
storage.Read(dest, 0);
return arr;
}
public static void CopyToStream(this IStorage input, Stream output, long length, IProgressReport progress = null) public static void CopyToStream(this IStorage input, Stream output, long length, IProgressReport progress = null)
{ {
const int bufferSize = 0x8000; const int bufferSize = 0x8000;