Skip to content

Commit b3b95ac

Browse files
committed
memory, more memory, even more memory
1 parent 2d84ab1 commit b3b95ac

File tree

8 files changed

+116
-112
lines changed

8 files changed

+116
-112
lines changed

src/ColumnizerLib/Column.cs

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,10 @@ public class Column : IColumnMemory
2727

2828
static Column ()
2929
{
30-
if (Environment.Version >= Version.Parse("6.2"))
31-
{
32-
//Win8 or newer support full UTF8 chars with the preinstalled fonts.
33-
//Replace null char with UTF8 Symbol U+2400 (␀)
34-
_replacementsMemory.Add(input => ReplaceNullChar(input, '␀'));
35-
}
36-
else
37-
{
38-
//Everything below Win8 the installed fonts seems to not to support reliabel
39-
//Replace null char with space
40-
//.net 10 does no longer support windows lower then windows 10
41-
//TODO: remove if with one of the next releases
42-
//https://github.com/dotnet/core/blob/main/release-notes/10.0/supported-os.md
43-
_replacementsMemory.Add(input => ReplaceNullChar(input, ' '));
44-
}
30+
//.net 10 only supports Windows10+ which has full UTF8-font support
31+
// Replace null char with UTF-8 Symbol U+2400 (␀)
32+
//https://github.com/dotnet/core/blob/main/release-notes/10.0/supported-os.md
33+
_replacementsMemory.Add(input => ReplaceNullChar(input, ' '));
4534

4635
EmptyColumn = new Column { FullValue = ReadOnlyMemory<char>.Empty };
4736
}

src/LogExpert.Core/Classes/Filter/Filter.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using LogExpert.Core.Callback;
2-
using LogExpert.Core.Classes;
32

43
using NLog;
54

@@ -69,7 +68,7 @@ private int DoFilter (FilterParams filterParams, int startLine, int maxCount, Li
6968
return count;
7069
}
7170

72-
var line = _callback.GetLogLine(lineNum);
71+
var line = _callback.GetLogLineMemory(lineNum);
7372

7473
if (line == null)
7574
{

src/LogExpert.Core/Classes/Filter/FilterParams.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Collections;
21
using System.Collections.ObjectModel;
32
using System.Drawing;
43
using System.Text.RegularExpressions;
@@ -12,6 +11,10 @@
1211

1312
namespace LogExpert.Core.Classes.Filter;
1413

14+
// TODO: Convert LastLine to ReadOnlyMemory<char> as part of memory optimization effort
15+
// This will eliminate string allocations in TestFilterCondition and improve performance.
16+
// Will require updating all callers that currently expect string type.
17+
// Related to: ReadOnlyMemory migration in columnizers
1518
[Serializable]
1619
public class FilterParams : ICloneable
1720
{
@@ -75,7 +78,7 @@ public class FilterParams : ICloneable
7578

7679
[JsonIgnore]
7780
[field: NonSerialized]
78-
public Hashtable LastNonEmptyCols { get; set; } = [];
81+
public Dictionary<int, ReadOnlyMemory<char>> LastNonEmptyCols { get; set; } = [];
7982

8083
[JsonIgnore]
8184
[field: NonSerialized]

src/LogExpert.Core/Classes/Util.cs

Lines changed: 93 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -53,29 +53,31 @@ public static string GetExtension (string fileName)
5353
: fileName[(i + 1)..];
5454
}
5555

56-
5756
public static string GetFileSizeAsText (long size)
5857
{
5958
return size < 1024
60-
? string.Empty + size + " bytes"
59+
? $"{size} bytes"
6160
: size < 1024 * 1024
62-
? string.Empty + (size / 1024) + " KB"
63-
: string.Empty + $"{size / 1048576.0:0.00}" + " MB";
61+
? $"{size / 1024} KB"
62+
: $"{size / 1048576.0:0.00} MB";
6463
}
6564

66-
//TOOD: check if the callers are checking for null before calling
67-
public static bool TestFilterCondition (FilterParams filterParams, ILogLine line, ILogLineColumnizerCallback columnizerCallback)
65+
public static bool TestFilterCondition (FilterParams filterParams, ILogLineMemory logLine, ILogLineMemoryColumnizerCallback columnizerCallback)
6866
{
6967
ArgumentNullException.ThrowIfNull(filterParams, nameof(filterParams));
70-
ArgumentNullException.ThrowIfNull(line, nameof(line));
68+
ArgumentNullException.ThrowIfNull(logLine, nameof(logLine));
7169

72-
if (filterParams.LastLine.Equals(line.FullLine, StringComparison.OrdinalIgnoreCase))
70+
// TODO: Once FilterParams.LastLine is converted to ReadOnlyMemory<char>, this can be simplified to:
71+
// if (MemoryExtensions.Equals(filterParams.LastLine.Span, logLine.FullLine.Span, StringComparison.OrdinalIgnoreCase))
72+
if (MemoryExtensions.Equals(filterParams.LastLine.AsSpan(), logLine.FullLine.Span, StringComparison.OrdinalIgnoreCase))
7373
{
7474
return filterParams.LastResult;
7575
}
7676

77-
var match = TestFilterMatch(filterParams, line, columnizerCallback);
78-
filterParams.LastLine = line.FullLine;
77+
var match = TestFilterMatch(filterParams, logLine, columnizerCallback);
78+
79+
// TODO: This ToString() allocation will be eliminated when LastLine becomes ReadOnlyMemory<char>
80+
filterParams.LastLine = logLine.FullLine.ToString();
7981

8082
if (filterParams.IsRangeSearch)
8183
{
@@ -108,48 +110,68 @@ public static bool TestFilterCondition (FilterParams filterParams, ILogLine line
108110
return match;
109111
}
110112

111-
//TODO Add Null Checks (https://github.com/LogExperts/LogExpert/issues/403)
112-
public static int DamerauLevenshteinDistance (string src, string dest)
113+
public static int DamerauLevenshteinDistance (ReadOnlySpan<char> source, ReadOnlySpan<char> destination, bool ignoreCase = false)
113114
{
114-
var d = new int[src.Length + 1, dest.Length + 1];
115-
int i, j, cost;
116-
var str1 = src.ToCharArray();
117-
var str2 = dest.ToCharArray();
115+
var d = new int[source.Length + 1, destination.Length + 1];
118116

119-
for (i = 0; i <= str1.Length; i++)
117+
for (var i = 0; i <= source.Length; i++)
120118
{
121119
d[i, 0] = i;
122120
}
123121

124-
for (j = 0; j <= str2.Length; j++)
122+
for (var j = 0; j <= destination.Length; j++)
125123
{
126124
d[0, j] = j;
127125
}
128126

129-
for (i = 1; i <= str1.Length; i++)
127+
for (var i = 1; i <= source.Length; i++)
130128
{
131-
for (j = 1; j <= str2.Length; j++)
129+
for (var j = 1; j <= destination.Length; j++)
132130
{
133-
cost = str1[i - 1] == str2[j - 1]
131+
var char1 = ignoreCase
132+
? char.ToUpperInvariant(source[i - 1])
133+
: source[i - 1];
134+
135+
var char2 = ignoreCase
136+
? char.ToUpperInvariant(destination[j - 1])
137+
: destination[j - 1];
138+
139+
var cost = char1 == char2
134140
? 0
135141
: 1;
136142

137-
d[i, j] =
138-
Math.Min(d[i - 1, j] + 1, // Deletion
139-
Math.Min(d[i, j - 1] + 1, // Insertion
140-
d[i - 1, j - 1] + cost)); // Substitution
141-
142-
if (i > 1 && j > 1 && str1[i - 1] == str2[j - 2] && str1[i - 2] == str2[j - 1])
143+
d[i, j] = Math.Min
144+
(
145+
d[i - 1, j] + 1, // Deletion
146+
Math.Min
147+
(
148+
d[i, j - 1] + 1, // Insertion
149+
d[i - 1, j - 1] + cost // Substitution
150+
)
151+
);
152+
153+
// Transposition
154+
if (i > 1 && j > 1)
143155
{
144-
d[i, j] = Math.Min(d[i, j], d[i - 2, j - 2] + cost);
156+
var prevChar1 = ignoreCase
157+
? char.ToUpperInvariant(source[i - 2])
158+
: source[i - 2];
159+
160+
var prevChar2 = ignoreCase
161+
? char.ToUpperInvariant(destination[j - 2])
162+
: destination[j - 2];
163+
164+
if (char1 == prevChar2 && prevChar1 == char2)
165+
{
166+
d[i, j] = Math.Min(d[i, j], d[i - 2, j - 2] + cost);
167+
}
145168
}
146169
}
147170
}
148171

149-
return d[str1.Length, str2.Length];
172+
return d[source.Length, destination.Length];
150173
}
151174

152-
//TODO Add Null Checks (https://github.com/LogExperts/LogExpert/issues/403)
153175
public static unsafe int YetiLevenshtein (string s1, string s2)
154176
{
155177
fixed (char* p1 = s1)
@@ -159,13 +181,13 @@ public static unsafe int YetiLevenshtein (string s1, string s2)
159181
}
160182
}
161183

162-
public static unsafe int YetiLevenshtein (string s1, string s2, int substitionCost)
184+
public static unsafe int YetiLevenshtein (string s1, string s2, int substitutionCost)
163185
{
164-
var xc = substitionCost - 1;
186+
var xc = substitutionCost - 1;
165187

166188
if (xc is < 0 or > 1)
167189
{
168-
throw new ArgumentException("", nameof(substitionCost));
190+
throw new ArgumentException("", nameof(substitutionCost));
169191
}
170192

171193
fixed (char* p1 = s1)
@@ -385,26 +407,6 @@ public static unsafe int YetiLevenshtein (char* s1, int l1, char* s2, int l2, in
385407
return i;
386408
}
387409

388-
/// <summary>
389-
/// Returns true, if the given string is null or empty
390-
/// </summary>
391-
/// <param name="toTest"></param>
392-
/// <returns></returns>
393-
public static bool IsNull (string toTest)
394-
{
395-
return toTest == null || toTest.Length == 0;
396-
}
397-
398-
/// <summary>
399-
/// Returns true, if the given string is null or empty or contains only spaces
400-
/// </summary>
401-
/// <param name="toTest"></param>
402-
/// <returns></returns>
403-
public static bool IsNullOrSpaces (string toTest)
404-
{
405-
return toTest == null || toTest.Trim().Length == 0;
406-
}
407-
408410
[Conditional("DEBUG")]
409411
public static void AssertTrue (bool condition, string msg)
410412
{
@@ -418,7 +420,7 @@ public static void AssertTrue (bool condition, string msg)
418420

419421
//TODO Add Null Check (https://github.com/LogExperts/LogExpert/issues/403)
420422
[SupportedOSPlatform("windows")]
421-
public string? GetWordFromPos (int xPos, string text, Graphics g, Font font)
423+
public static string? GetWordFromPos (int xPos, string text, Graphics g, Font font)
422424
{
423425
var words = text.Split([' ', '.', ':', ';']);
424426

@@ -469,7 +471,7 @@ public static void AssertTrue (bool condition, string msg)
469471

470472
#region Private Methods
471473

472-
private static bool TestFilterMatch (FilterParams filterParams, ILogLine line, ILogLineColumnizerCallback columnizerCallback)
474+
private static bool TestFilterMatch (FilterParams filterParams, ILogLineMemory logLine, ILogLineMemoryColumnizerCallback columnizerCallback)
473475
{
474476
string normalizedSearchText;
475477
string searchText;
@@ -495,22 +497,19 @@ private static bool TestFilterMatch (FilterParams filterParams, ILogLine line, I
495497

496498
if (filterParams.ColumnRestrict)
497499
{
498-
var columns = filterParams.CurrentColumnizer.SplitLine(columnizerCallback, line);
500+
var columns = filterParams.CurrentColumnizer.SplitLine(columnizerCallback, logLine);
499501
var found = false;
500502
foreach (var colIndex in filterParams.ColumnList)
501503
{
502-
if (colIndex < columns.ColumnValues.Length
503-
) // just to be sure, maybe the columnizer has changed anyhow
504+
if (colIndex < columns.ColumnValues.Length) // just to be sure, maybe the columnizer has changed anyhow
504505
{
505506
if (columns.ColumnValues[colIndex].FullValue.Trim().Length == 0)
506507
{
507508
if (filterParams.EmptyColumnUsePrev)
508509
{
509-
var prevValue = (string)filterParams.LastNonEmptyCols[colIndex];
510-
if (prevValue != null)
510+
if (filterParams.LastNonEmptyCols.TryGetValue(colIndex, out var prevValue))
511511
{
512-
if (TestMatchSub(filterParams, prevValue, normalizedSearchText, searchText, rex,
513-
filterParams.ExactColumnMatch))
512+
if (TestMatchSub(filterParams, prevValue, normalizedSearchText.AsSpan(), searchText.AsSpan(), rex, filterParams.ExactColumnMatch))
514513
{
515514
found = true;
516515
}
@@ -524,9 +523,7 @@ private static bool TestFilterMatch (FilterParams filterParams, ILogLine line, I
524523
else
525524
{
526525
filterParams.LastNonEmptyCols[colIndex] = columns.ColumnValues[colIndex].FullValue;
527-
if (TestMatchSub(filterParams, columns.ColumnValues[colIndex].FullValue, normalizedSearchText,
528-
searchText, rex,
529-
filterParams.ExactColumnMatch))
526+
if (TestMatchSub(filterParams, columns.ColumnValues[colIndex].FullValue, normalizedSearchText.AsSpan(), searchText.AsSpan(), rex, filterParams.ExactColumnMatch))
530527
{
531528
found = true;
532529
}
@@ -538,11 +535,17 @@ private static bool TestFilterMatch (FilterParams filterParams, ILogLine line, I
538535
}
539536
else
540537
{
541-
return TestMatchSub(filterParams, line.FullLine, normalizedSearchText, searchText, rex, false);
538+
return TestMatchSub(filterParams, logLine.FullLine, normalizedSearchText.AsSpan(), searchText.AsSpan(), rex, false);
542539
}
543540
}
544541

545-
private static bool TestMatchSub (FilterParams filterParams, string line, string lowerSearchText, string searchText, Regex rex, bool exactMatch)
542+
private static bool TestMatchSub (
543+
FilterParams filterParams,
544+
ReadOnlySpan<char> line,
545+
ReadOnlySpan<char> normalizedSearchText, // Pre-normalized (uppercase) // lowerSearchText
546+
ReadOnlySpan<char> searchText,
547+
Regex rex,
548+
bool exactMatch)
546549
{
547550
if (filterParams.IsRegex)
548551
{
@@ -557,14 +560,15 @@ private static bool TestMatchSub (FilterParams filterParams, string line, string
557560
{
558561
if (exactMatch)
559562
{
560-
if (line.ToUpperInvariant().Trim().Equals(lowerSearchText, StringComparison.Ordinal))
563+
var trimmedLine = line.Trim();
564+
if (MemoryExtensions.Equals(trimmedLine, normalizedSearchText, StringComparison.OrdinalIgnoreCase))
561565
{
562566
return true;
563567
}
564568
}
565569
else
566570
{
567-
if (line.Contains(lowerSearchText, StringComparison.OrdinalIgnoreCase))
571+
if (line.Contains(normalizedSearchText, StringComparison.OrdinalIgnoreCase))
568572
{
569573
return true;
570574
}
@@ -581,7 +585,7 @@ private static bool TestMatchSub (FilterParams filterParams, string line, string
581585
}
582586
else
583587
{
584-
if (line.Contains(searchText, StringComparison.OrdinalIgnoreCase))
588+
if (line.Contains(searchText, StringComparison.Ordinal))
585589
{
586590
return true;
587591
}
@@ -593,16 +597,11 @@ private static bool TestMatchSub (FilterParams filterParams, string line, string
593597
var range = line.Length - searchText.Length;
594598
if (range > 0)
595599
{
596-
for (var i = 0; i < range; ++i)
600+
for (var i = 0; i <= range; ++i)
597601
{
598-
var src = line.Substring(i, searchText.Length);
599-
600-
if (!filterParams.IsCaseSensitive)
601-
{
602-
src = src.ToLowerInvariant();
603-
}
602+
var src = line.Slice(i, searchText.Length);
604603

605-
var dist = DamerauLevenshteinDistance(src, searchText);
604+
var dist = DamerauLevenshteinDistance(src, searchText, !filterParams.IsCaseSensitive);
606605

607606
if ((searchText.Length + 1) / (float)(dist + 1) >= 11F / (float)(filterParams.FuzzyValue + 1F))
608607
{
@@ -618,6 +617,22 @@ private static bool TestMatchSub (FilterParams filterParams, string line, string
618617
return false;
619618
}
620619

620+
private static bool TestMatchSub (
621+
FilterParams filterParams,
622+
ReadOnlyMemory<char> line, // From ILogLineMemory.FullLine
623+
ReadOnlySpan<char> normalizedSearchText,
624+
ReadOnlySpan<char> searchText,
625+
Regex rex,
626+
bool exactMatch)
627+
{
628+
return TestMatchSub(filterParams, line.Span, normalizedSearchText, searchText, rex, exactMatch);
629+
}
630+
631+
private static bool TestMatchSub (FilterParams filterParams, string line, string lowerSearchText, string searchText, Regex rex, bool exactMatch)
632+
{
633+
return TestMatchSub(filterParams, line.AsSpan(), lowerSearchText.AsSpan(), searchText.AsSpan(), rex, exactMatch);
634+
}
635+
621636
private static unsafe int MemchrRPLC (char* buffer, char c, int count)
622637
{
623638
var p = buffer;

0 commit comments

Comments
 (0)