Skip to content

Commit 1827e2c

Browse files
committed
Increase lookup performance by ~426x
Added a configurable internal memory cache that makes subsequent lookups basically instantaneous. The lookup benchmarks between 1.4.5 and 1.4.6 dropped from 9801 msec to 28 msec when recalling 7778000 filters. Flattened duplicate constructor code.
1 parent 7113a90 commit 1827e2c

File tree

4 files changed

+69
-37
lines changed

4 files changed

+69
-37
lines changed

DistillNET/DistillNET/DistillNET.csproj

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@
88
<PackageProjectUrl>https://github.com/TechnikEmpire/DistillNET</PackageProjectUrl>
99
<RepositoryUrl>https://github.com/TechnikEmpire/DistillNET</RepositoryUrl>
1010
<Description>DistillNET is a library for matching and filtering HTTP requests URLs using the Adblock Plus Filter format.</Description>
11-
<Copyright>Copyright © 2017 Jesse Nicholson</Copyright>
12-
<Version>1.4.5</Version>
11+
<Copyright>Copyright © 2017 - 2018 Jesse Nicholson</Copyright>
12+
<Version>1.4.6</Version>
1313
<Authors>Jesse Nicholson</Authors>
1414
<Company>Technik Empire</Company>
1515
<PackageTags>DistillNET Adblock AdblockPlus Adblock-Plus URL-Filter URL-Filtering Content-Filter Filter</PackageTags>
16-
<PackageReleaseNotes>Fixes a bug where, when using the FilterDbCollection constructor that takes parameters, if you specified to use an in-memory database, the database was not configured correctly.
17-
18-
Package also now bundles XML documentation.</PackageReleaseNotes>
16+
<PackageReleaseNotes>Improves rule lookup performance by a factor of ~426!</PackageReleaseNotes>
17+
<AssemblyVersion>1.4.6.0</AssemblyVersion>
1918
</PropertyGroup>
2019

2120
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
@@ -27,7 +26,8 @@ Package also now bundles XML documentation.</PackageReleaseNotes>
2726
</PropertyGroup>
2827

2928
<ItemGroup>
30-
<PackageReference Include="Microsoft.Data.SQLite" Version="2.0.0" />
29+
<PackageReference Include="Microsoft.Data.SQLite" Version="2.0.1" />
30+
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.0.1" />
3131
</ItemGroup>
3232

3333
</Project>

DistillNET/DistillNET/DistillNET/FilterDbCollection.cs

Lines changed: 55 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@
77

88
using DistillNET.Extensions;
99
using Microsoft.Data.Sqlite;
10+
using Microsoft.Extensions.Caching.Memory;
11+
using Microsoft.Extensions.Options;
1012
using System;
1113
using System.Collections.Generic;
1214
using System.Data;
1315
using System.IO;
1416
using System.Linq;
1517
using System.Reflection;
18+
using System.Threading;
1619
using System.Threading.Tasks;
1720

1821
namespace DistillNET
@@ -42,33 +45,23 @@ public class FilterDbCollection : IDisposable
4245
private readonly string m_globalKey;
4346

4447
/// <summary>
45-
/// Constructs a new FilterDbCollection using an in-memory database.
48+
/// Memory cache.
4649
/// </summary>
47-
public FilterDbCollection()
48-
{
49-
50-
var version = typeof(FilterDbCollection).Assembly.GetCustomAttribute<AssemblyFileVersionAttribute>().Version;
51-
var rnd = new Random();
52-
var rndNum = rnd.Next();
53-
var generatedDbName = string.Format("{0} {1} - {2}", nameof(FilterDbCollection), version, rndNum);
54-
55-
// "Data Source = :memory:; Cache = shared;"
56-
var cb = new SqliteConnectionStringBuilder();
57-
cb.DataSource = generatedDbName;
58-
cb.Mode = SqliteOpenMode.Memory;
59-
cb.Cache = SqliteCacheMode.Shared;
60-
m_connection = new SqliteConnection(cb.ToString());
61-
62-
//m_connection. = SQLiteConnectionFlags.UseConnectionPool | SQLiteConnectionFlags.NoConvertSettings | SQLiteConnectionFlags.NoVerifyTypeAffinity;
63-
m_connection.Open();
64-
65-
ConfigureDatabase();
66-
67-
CreateTables();
50+
private MemoryCache m_cache;
6851

69-
m_globalKey = "global";
52+
/// <summary>
53+
/// Mem cache options.
54+
/// </summary>
55+
private MemoryCacheOptions m_cacheOptions;
7056

71-
m_ruleParser = new AbpFormatRuleParser();
57+
/// <summary>
58+
/// Constructs a new FilterDbCollection using an in-memory database.
59+
/// </summary>
60+
/// <param name="cacheOptions">
61+
/// User defined query caching options.
62+
/// </param>
63+
public FilterDbCollection(MemoryCacheOptions cacheOptions = null) : this(null, true, true, cacheOptions)
64+
{
7265
}
7366

7467
/// <summary>
@@ -84,13 +77,24 @@ public FilterDbCollection()
8477
/// <param name="useMemory">
8578
/// If true, the database will be created as a purely in-memory database.
8679
/// </param>
87-
public FilterDbCollection(string dbAbsolutePath, bool overwrite = true, bool useMemory = false)
80+
/// <param name="cacheOptions">
81+
/// User defined query caching options.
82+
/// </param>
83+
public FilterDbCollection(string dbAbsolutePath, bool overwrite = true, bool useMemory = false, MemoryCacheOptions cacheOptions = null)
8884
{
8985
if(!useMemory && overwrite && File.Exists(dbAbsolutePath))
9086
{
9187
File.Delete(dbAbsolutePath);
9288
}
9389

90+
if(cacheOptions == null)
91+
{
92+
cacheOptions = new MemoryCacheOptions();
93+
cacheOptions.ExpirationScanFrequency = TimeSpan.FromMinutes(10);
94+
}
95+
96+
m_cacheOptions = cacheOptions;
97+
9498
bool isNew = !File.Exists(dbAbsolutePath);
9599

96100
m_ruleParser = new AbpFormatRuleParser();
@@ -213,6 +217,16 @@ public void FinalizeForRead()
213217
CreatedIndexes();
214218
}
215219

220+
private void RecreateCache()
221+
{
222+
if (m_cache != null)
223+
{
224+
m_cache.Dispose();
225+
}
226+
227+
m_cache = new MemoryCache(m_cacheOptions);
228+
}
229+
216230
/// <summary>
217231
/// Parses the supplied list of rules and stores them in the assigned database for retrieval,
218232
/// indexed by the rule's domain names.
@@ -230,6 +244,8 @@ public void FinalizeForRead()
230244
/// </returns>
231245
public async Task<Tuple<int, int>> ParseStoreRules(string[] rawRuleStrings, short categoryId)
232246
{
247+
RecreateCache();
248+
233249
int loaded = 0, failed = 0;
234250

235251
using(var transaction = m_connection.BeginTransaction())
@@ -305,6 +321,8 @@ public async Task<Tuple<int, int>> ParseStoreRules(string[] rawRuleStrings, shor
305321
/// </returns>
306322
public async Task<Tuple<int, int>> ParseStoreRulesFromStream(Stream rawRulesStream, short categoryId)
307323
{
324+
RecreateCache();
325+
308326
int loaded = 0, failed = 0;
309327

310328
using(var transaction = m_connection.BeginTransaction())
@@ -408,7 +426,16 @@ public async Task<List<UrlFilter>> GetWhitelistFiltersForDomain(string domain =
408426
/// </returns>
409427
private async Task<List<UrlFilter>> GetFiltersForDomain(string domain, bool isWhitelist)
410428
{
411-
var retVal = new List<UrlFilter>();
429+
var cacheKey = new Tuple<string, bool>(domain, isWhitelist);
430+
431+
List<UrlFilter> retVal;
432+
433+
if(m_cache.TryGetValue(cacheKey, out retVal))
434+
{
435+
return retVal;
436+
}
437+
438+
retVal = new List<UrlFilter>();
412439

413440
var allPossibleVariations = GetAllPossibleSubdomains(domain);
414441

@@ -454,6 +481,8 @@ private async Task<List<UrlFilter>> GetFiltersForDomain(string domain, bool isWh
454481
}
455482
}
456483

484+
m_cache.Set(cacheKey, retVal);
485+
457486
return retVal;
458487
}
459488

DistillNET/Tests/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ private static void TestDomainWideException()
5555
}
5656

5757
private static void Main(string[] args)
58-
{
58+
{
5959
var parser = new AbpFormatRuleParser();
6060

6161
string easylistPath = AppDomain.CurrentDomain.BaseDirectory + "easylist.txt";

DistillNET/Tests/Tests.csproj

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,16 @@
6565
</Content>
6666
</ItemGroup>
6767
<ItemGroup>
68-
<PackageReference Include="DistillNET">
69-
<Version>1.4.5</Version>
70-
</PackageReference>
7168
<PackageReference Include="Microsoft.Data.SQLite">
72-
<Version>2.0.0</Version>
69+
<Version>2.0.1</Version>
7370
</PackageReference>
7471
</ItemGroup>
72+
<ItemGroup>
73+
<ProjectReference Include="..\DistillNET\DistillNET.csproj">
74+
<Project>{693840ca-eaf1-4718-bb1f-e2f9d6646e10}</Project>
75+
<Name>DistillNET</Name>
76+
</ProjectReference>
77+
</ItemGroup>
7578
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
7679
<PropertyGroup>
7780
<PostBuildEvent>

0 commit comments

Comments
 (0)