Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minor performance improvements #159

Merged
merged 5 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion MaxMind.Db.Benchmark/MaxMind.Db.Benchmark.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<GenerateAssemblyCopyrightAttribute>false</GenerateAssemblyCopyrightAttribute>
<GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
<LangVersion>10.0</LangVersion>
<LangVersion>12.0</LangVersion>
<Nullable>enable</Nullable>
<AnalysisLevel>latest</AnalysisLevel>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
Expand Down
22 changes: 22 additions & 0 deletions MaxMind.Db.Test/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,25 @@
[assembly: SuppressMessage("Blocker Code Smell", "S2699:Tests should include assertions", Justification = "<Pending>", Scope = "member", Target = "~M:MaxMind.Db.Test.ThreadingTest.TestManyOpens(MaxMind.Db.FileAccessMode)")]
[assembly: SuppressMessage("Blocker Code Smell", "S2699:Tests should include assertions", Justification = "<Pending>", Scope = "member", Target = "~M:MaxMind.Db.Test.ThreadingTest.TestParallelFor(MaxMind.Db.FileAccessMode)")]
[assembly: SuppressMessage("Major Code Smell", "S3881:\"IDisposable\" should be implemented correctly", Justification = "<Pending>", Scope = "type", Target = "~T:MaxMind.Db.Test.Helper.NonSeekableStreamWrapper")]

/* Unmerged change from project 'MaxMind.Db.Test (net6.0)'
Added:
[assembly: SuppressMessage("Performance", "CA1861:Avoid constant arrays as arguments", Justification = "<Pending>", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestIPV4(MaxMind.Db.Reader,System.String)")]
[assembly: SuppressMessage("Performance", "CA1861:Avoid constant arrays as arguments", Justification = "<Pending>", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestIPV6(MaxMind.Db.Reader,System.String)")]
*/

/* Unmerged change from project 'MaxMind.Db.Test (net7.0)'
Added:
[assembly: SuppressMessage("Performance", "CA1861:Avoid constant arrays as arguments", Justification = "<Pending>", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestIPV4(MaxMind.Db.Reader,System.String)")]
[assembly: SuppressMessage("Performance", "CA1861:Avoid constant arrays as arguments", Justification = "<Pending>", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestIPV6(MaxMind.Db.Reader,System.String)")]
*/

/* Unmerged change from project 'MaxMind.Db.Test (net8.0)'
Added:
[assembly: SuppressMessage("Performance", "CA1861:Avoid constant arrays as arguments", Justification = "<Pending>", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestIPV4(MaxMind.Db.Reader,System.String)")]
[assembly: SuppressMessage("Performance", "CA1861:Avoid constant arrays as arguments", Justification = "<Pending>", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestIPV6(MaxMind.Db.Reader,System.String)")]
*/
[assembly: SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "<Pending>", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.NullStreamThrowsArgumentNullExceptionAsync")]
[assembly: SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "<Pending>", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestEmptyStreamAsync")]
[assembly: SuppressMessage("Performance", "CA1861:Avoid constant arrays as arguments", Justification = "<Pending>", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestIPV4(MaxMind.Db.Reader,System.String)")]
[assembly: SuppressMessage("Performance", "CA1861:Avoid constant arrays as arguments", Justification = "<Pending>", Scope = "member", Target = "~M:MaxMind.Db.Test.ReaderTest.TestIPV6(MaxMind.Db.Reader,System.String)")]
2 changes: 1 addition & 1 deletion MaxMind.Db.Test/MaxMind.Db.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<GenerateAssemblyCopyrightAttribute>false</GenerateAssemblyCopyrightAttribute>
<GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
<LangVersion>10.0</LangVersion>
<LangVersion>12.0</LangVersion>
<Nullable>enable</Nullable>
<AnalysisLevel>latest</AnalysisLevel>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
Expand Down
2 changes: 1 addition & 1 deletion MaxMind.Db.Test/ReaderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@ private static void TestAddresses(Reader reader, string file, IEnumerable<string
foreach (var address in singleAddresses)
{
reader.Find<Dictionary<string, object>>(IPAddress.Parse(address))["ip"].Should().Be(
new string(address.ToArray()),
new string([.. address]),
$"Did not find expected data record for {address} in {file}");
}

Expand Down
10 changes: 5 additions & 5 deletions MaxMind.Db/Decoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ internal sealed class Decoder
private readonly Buffer _database;
private readonly long _pointerBase;
private readonly bool _followPointers;
private readonly int[] _pointerValueOffset = { 0, 0, 1 << 11, (1 << 19) + (1 << 11), 0 };
private readonly int[] _pointerValueOffset = [0, 0, 1 << 11, (1 << 19) + (1 << 11), 0];

private readonly DictionaryActivatorCreator _dictionaryActivatorCreator;
private readonly ListActivatorCreator _listActivatorCreator;
Expand Down Expand Up @@ -352,9 +352,9 @@ private object DecodeMapToType(
for (var i = 0; i < size; i++)
{
var key = DecodeKey(offset, out offset);
if (constructor.DeserializationParameters.ContainsKey(key))
if (constructor.DeserializationParameters.TryGetValue(key, out var v))
{
var param = constructor.DeserializationParameters[key];
var param = v;
var paramType = param.ParameterType;
var value = Decode(paramType, offset, out offset, injectables, network);
parameters[param.Position] = value;
Expand Down Expand Up @@ -397,10 +397,10 @@ private static void SetInjectables(TypeActivator constructor, object[] parameter
{
foreach (var item in constructor.InjectableParameters)
{
if (injectables == null || !injectables.Values.ContainsKey(item.Key))
if (injectables == null || !injectables.Values.TryGetValue(item.Key, out var value))
throw new DeserializationException($"No injectable value found for {item.Key}");

parameters[item.Value.Position] = injectables.Values[item.Key];
parameters[item.Value.Position] = value;
}
}

Expand Down
4 changes: 2 additions & 2 deletions MaxMind.Db/Key.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace MaxMind.Db
{
internal struct Key
internal readonly struct Key
{
private readonly Buffer buffer;
private readonly long offset;
Expand All @@ -16,7 +16,7 @@ public Key(Buffer buffer, long offset, int size)
var code = 17;
for (var i = 0; i < size; i++)
{
code = 31 * code + buffer.ReadOne(offset + i);
code = (31 * code) + buffer.ReadOne(offset + i);
}
hashCode = code;
}
Expand Down
2 changes: 1 addition & 1 deletion MaxMind.Db/MaxMind.Db.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<GenerateAssemblyCopyrightAttribute>false</GenerateAssemblyCopyrightAttribute>
<GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
<LangVersion>10.0</LangVersion>
<LangVersion>12.0</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
<AnalysisLevel>latest</AnalysisLevel>
Expand Down
64 changes: 34 additions & 30 deletions MaxMind.Db/Reader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ private struct NetNode
private const int DataSectionSeparatorSize = 16;
private readonly Buffer _database;
private readonly string? _fileName;
private readonly long _dataPointerOffset;
private readonly int _dbIPVersion;
private readonly long _nodeByteSize;
private readonly long _nodeCount;
private readonly int _recordSize;
private readonly long _searchTreeSize;

// The property getter was a hotspot during profiling.

Expand Down Expand Up @@ -133,7 +139,23 @@ private Reader(Buffer buffer, string? file)
var start = FindMetadataStart();
var metaDecode = new Decoder(_database, start);
Metadata = metaDecode.Decode<Metadata>(start, out _);
_dataPointerOffset = Metadata.SearchTreeSize - Metadata.NodeCount;
_dbIPVersion = Metadata.IPVersion;
_nodeByteSize = Metadata.NodeByteSize;
_nodeCount = Metadata.NodeCount;
_recordSize = Metadata.RecordSize;
_searchTreeSize = Metadata.SearchTreeSize;
Decoder = new Decoder(_database, Metadata.SearchTreeSize + DataSectionSeparatorSize);

if (_dbIPVersion ==6)
{
var node = 0;
for (var i = 0; i < 96 && node < _nodeCount; i++)
{
node = ReadNode(node, 0);
}
_ipV4Start = node;
}
}

/// <summary>
Expand Down Expand Up @@ -174,24 +196,6 @@ private static Buffer BufferForMode(string file, FileAccessMode mode)
/// </value>
public Metadata Metadata { get; }

private int IPv4Start
{
get
{
if (_ipV4Start != 0 || Metadata.IPVersion == 4)
{
return _ipV4Start;
}
var node = 0;
for (var i = 0; i < 96 && node < Metadata.NodeCount; i++)
{
node = ReadNode(node, 0);
}
_ipV4Start = node;
return node;
}
}

private Decoder Decoder { get; }

/// <summary>
Expand Down Expand Up @@ -254,7 +258,7 @@ private void Dispose(bool disposing)
/// <returns>Enumerator for all data nodes</returns>
public IEnumerable<ReaderIteratorNode<T>> FindAll<T>(InjectableValues? injectables = null, int cacheSize = 16384) where T : class
{
var byteCount = Metadata.IPVersion == 6 ? 16 : 4;
var byteCount = _dbIPVersion == 6 ? 16 : 4;
var nodes = new List<NetNode>();
var root = new NetNode { IPBytes = new byte[byteCount] };
nodes.Add(root);
Expand All @@ -265,7 +269,7 @@ public IEnumerable<ReaderIteratorNode<T>> FindAll<T>(InjectableValues? injectabl
nodes.RemoveAt(nodes.Count - 1);
while (true)
{
if (node.Pointer < Metadata.NodeCount)
if (node.Pointer < _nodeCount)
{
var ipRight = new byte[byteCount];
Array.Copy(node.IPBytes, ipRight, ipRight.Length);
Expand All @@ -281,7 +285,7 @@ public IEnumerable<ReaderIteratorNode<T>> FindAll<T>(InjectableValues? injectabl
}
else
{
if (node.Pointer > Metadata.NodeCount)
if (node.Pointer > _nodeCount)
{
// data node, we are done with this branch
if (!dataCache.TryGetValue(node.Pointer, out var data))
Expand Down Expand Up @@ -315,7 +319,7 @@ public IEnumerable<ReaderIteratorNode<T>> FindAll<T>(InjectableValues? injectabl

private T ResolveDataPointer<T>(int pointer, InjectableValues? injectables, Network? network) where T : class
{
var resolved = pointer - Metadata.NodeCount + Metadata.SearchTreeSize;
var resolved = pointer + _dataPointerOffset;

if (resolved >= _database.Length)
{
Expand All @@ -333,7 +337,7 @@ private int FindAddressInTree(IPAddress address, out int prefixLength)

var bitLength = rawAddress.Length * 8;
var record = StartNode(bitLength);
var nodeCount = Metadata.NodeCount;
var nodeCount = _nodeCount;

var i = 0;
for (; i < bitLength && record < nodeCount; i++)
Expand All @@ -342,12 +346,12 @@ private int FindAddressInTree(IPAddress address, out int prefixLength)
record = ReadNode(record, bit);
}
prefixLength = i;
if (record == Metadata.NodeCount)
if (record == nodeCount)
{
// record is empty
return 0;
}
if (record > Metadata.NodeCount)
if (record > nodeCount)
{
// record is a data pointer
return record;
Expand All @@ -359,9 +363,9 @@ private int StartNode(int bitLength)
{
// Check if we are looking up an IPv4 address in an IPv6 tree. If this
// is the case, we can skip over the first 96 nodes.
if (Metadata.IPVersion == 6 && bitLength == 32)
if (_dbIPVersion == 6 && bitLength == 32)
{
return IPv4Start;
return _ipV4Start;
}
// The first node of the tree is always node 0, at the beginning of the
// value
Expand Down Expand Up @@ -395,9 +399,9 @@ private long FindMetadataStart()

private int ReadNode(int nodeNumber, int index)
{
var baseOffset = nodeNumber * Metadata.NodeByteSize;
var baseOffset = nodeNumber * _nodeByteSize;

var size = Metadata.RecordSize;
var size = _recordSize;

switch (size)
{
Expand Down Expand Up @@ -425,4 +429,4 @@ private int ReadNode(int nodeNumber, int index)
throw new InvalidDatabaseException($"Unknown record size: {size}");
}
}
}
}
1 change: 1 addition & 0 deletions releasenotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
However, if you are using .NET 5.0, the .NET Standard 2.1 target should
continue working for you.
* .NET 7.0 and .NET 8.0 have been added as a target.
* Minor performance improvements.

## 4.0.0 (2022-02-03) ##

Expand Down
Loading