diff --git a/src/LogExpert.UI/Controls/LogWindow/LogWindow.cs b/src/LogExpert.UI/Controls/LogWindow/LogWindow.cs index 34408a5e..6b76d7d5 100644 --- a/src/LogExpert.UI/Controls/LogWindow/LogWindow.cs +++ b/src/LogExpert.UI/Controls/LogWindow/LogWindow.cs @@ -581,6 +581,7 @@ private void ApplyToolStripMenuItemResources () private void ApplyToolTipsResources () { + helpToolTip.AutoPopDelay = 5000; //this is in ms, 5000ms = 5 seconds helpToolTip.SetToolTip(btnColumn, Resources.LogWindow_UI_Button_ToolTip_Column); helpToolTip.SetToolTip(columnRestrictCheckBox, Resources.LogWindow_UI_CheckBox_ToolTip_ColumnRestrict); helpToolTip.SetToolTip(knobControlFuzzy, Resources.LogWindow_UI_KnobControl_Fuzzy); @@ -1333,7 +1334,7 @@ private void OnSelectionChangedTriggerSignal (object sender, EventArgs e) if (IsMultiFile) { //MethodInvoker invoker = DisplayCurrentFileOnStatusline; - _ = Task.Run(DisplayCurrentFileOnStatusline); + _ = Task.Run(DisplayCurrentFileOnStatusline); //_ = invoker.BeginInvoke(null, null); } else @@ -2982,7 +2983,7 @@ private void LogEventWorker () { return; } - + CheckFilterAndHighlight(e); _timeSpreadCalc.SetLineCount(e.LineCount); } diff --git a/src/LogExpert.UI/Controls/LogWindow/TimeSpreadigControl.cs b/src/LogExpert.UI/Controls/LogWindow/TimeSpreadigControl.cs index 88dda899..84591cf3 100644 --- a/src/LogExpert.UI/Controls/LogWindow/TimeSpreadigControl.cs +++ b/src/LogExpert.UI/Controls/LogWindow/TimeSpreadigControl.cs @@ -37,7 +37,7 @@ public TimeSpreadingControl () Font = new Font("Courier New", 8.25F, FontStyle.Regular, GraphicsUnit.Point, 0); _toolTip.InitialDelay = 0; _toolTip.ReshowDelay = 0; - _toolTip.ShowAlways = true; + _toolTip.AutoPopDelay = 5000; DoubleBuffered = false; ResumeLayout(); diff --git a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs index 834b0ec3..f35c92d6 100644 --- a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs +++ b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs @@ -93,7 +93,7 @@ public LogTabWindow (string[] fileNames, int instanceNumber, bool showInstanceNu _menuToolbarController = new MenuToolbarController(); _menuToolbarController.InitializeMenus(mainMenuStrip, buttonToolStrip, externalToolsToolStrip, dragControlDateTime, checkBoxFollowTail); InitializeMenuToolbarControllerEvents(); - + ApplyTextResources(); ConfigManager = configManager; @@ -184,29 +184,9 @@ private void InitializeTabControllerEvents () #endregion - #region Delegates - - private delegate void AddFileTabsDelegate (string[] fileNames); - - private delegate void ExceptionFx (); - - private delegate void FileNotFoundDelegate (LogWindow.LogWindow logWin); - - private delegate void FileRespawnedDelegate (LogWindow.LogWindow logWin); - - public delegate void HighlightSettingsChangedEventHandler (object sender, EventArgs e); - - private delegate void LoadMultiFilesDelegate (string[] fileName, EncodingOptions encodingOptions); - - private delegate void SetColumnizerFx (ILogLineMemoryColumnizer columnizer); - - private delegate void SetTabIconDelegate (LogWindow.LogWindow logWindow, Icon icon); - - #endregion - #region Events - public event HighlightSettingsChangedEventHandler HighlightSettingsChanged; + public event EventHandler HighlightSettingsChanged; #endregion @@ -598,7 +578,7 @@ public LogWindow.LogWindow AddMultiFileTab (string[] fileNames) multiFileEnabledStripMenuItem.Checked = true; EncodingOptions encodingOptions = new(); FillDefaultEncodingFromSettings(encodingOptions); - _ = BeginInvoke(new LoadMultiFilesDelegate(logWindow.LoadFilesAsMulti), fileNames, encodingOptions); + _ = BeginInvoke(logWindow.LoadFilesAsMulti, fileNames, encodingOptions); AddToFileHistory(fileNames[0]); return logWindow; } @@ -606,7 +586,7 @@ public LogWindow.LogWindow AddMultiFileTab (string[] fileNames) [SupportedOSPlatform("windows")] public void LoadFiles (string[] fileNames) { - _ = Invoke(new AddFileTabsDelegate(AddFileTabs), [fileNames]); + _ = Invoke(AddFileTabs, [fileNames]); } [SupportedOSPlatform("windows")] @@ -703,7 +683,7 @@ private void OnTabControllerWindowActivated (object sender, WindowActivatedEvent // Update the tab icon to reflect cleared dirty state var icon = GetLedIcon(data.LedState.DiffSum, data); - _ = BeginInvoke(new SetTabIconDelegate(SetTabIcon), newWindow, icon); + _ = BeginInvoke(SetTabIcon, newWindow, icon); } // Notify the window it has been activated @@ -876,7 +856,7 @@ public void FollowTailChanged (LogWindow.LogWindow logWindow, bool isEnabled, bo if (Preferences.ShowTailState) { var icon = GetLedIcon(data.LedState.DiffSum, data); - _ = BeginInvoke(new SetTabIconDelegate(SetTabIcon), logWindow, icon); + _ = BeginInvoke(SetTabIcon, logWindow, icon); } } @@ -1487,7 +1467,7 @@ private void StatusLineEventWorker (StatusLineEventArgs e) private void FileNotFound (LogWindow.LogWindow logWin) { var data = logWin.Tag as LogWindowData; - _ = BeginInvoke(new SetTabIconDelegate(SetTabIcon), logWin, _deadIcon); + _ = BeginInvoke(SetTabIcon, logWin, _deadIcon); dragControlDateTime.Visible = false; } @@ -1497,7 +1477,7 @@ private void FileRespawned (LogWindow.LogWindow logWin) var data = logWin.Tag as LogWindowData; data.LedState.DiffSum = 0; var icon = GetLedIcon(0, data); - _ = BeginInvoke(new SetTabIconDelegate(SetTabIcon), logWin, icon); + _ = BeginInvoke(SetTabIcon, logWin, icon); } [SupportedOSPlatform("windows")] @@ -1641,7 +1621,7 @@ private void SetTabIcons (Preferences preferences) { var data = logWindow.Tag as LogWindowData; var icon = GetLedIcon(data.LedState.DiffSum, data); - _ = BeginInvoke(new SetTabIconDelegate(SetTabIcon), logWindow, icon); + _ = BeginInvoke(SetTabIcon, logWindow, icon); } } @@ -2163,8 +2143,7 @@ private void OnSelectFilterToolStripMenuItemClick (object sender, EventArgs e) if (logWindow.CurrentColumnizer.GetType() != form.SelectedColumnizer.GetType()) { //logWindow.SetColumnizer(form.SelectedColumnizer); - SetColumnizerFx fx = logWindow.ForceColumnizer; - _ = logWindow.Invoke(fx, form.SelectedColumnizer); + _ = logWindow.Invoke(logWindow.ForceColumnizer, form.SelectedColumnizer); SetColumnizerHistoryEntry(logWindow.FileName, form.SelectedColumnizer); } else @@ -2180,8 +2159,7 @@ private void OnSelectFilterToolStripMenuItemClick (object sender, EventArgs e) { if (CurrentLogWindow.CurrentColumnizer.GetType() != form.SelectedColumnizer.GetType()) { - SetColumnizerFx fx = CurrentLogWindow.ForceColumnizer; - _ = CurrentLogWindow.Invoke(fx, form.SelectedColumnizer); + _ = CurrentLogWindow.Invoke(CurrentLogWindow.ForceColumnizer, form.SelectedColumnizer); SetColumnizerHistoryEntry(CurrentLogWindow.FileName, form.SelectedColumnizer); } @@ -2434,12 +2412,12 @@ private void OnFileSizeChanged (object sender, LogEventArgs e) private void OnLogWindowFileNotFound (object sender, EventArgs e) { - _ = Invoke(new FileNotFoundDelegate(FileNotFound), sender); + _ = Invoke(FileNotFound, sender); } private void OnLogWindowFileRespawned (object sender, EventArgs e) { - _ = Invoke(new FileRespawnedDelegate(FileRespawned), sender); + _ = Invoke(FileRespawned, sender); } private void OnLogWindowFilterListChanged (object sender, FilterListChangedEventArgs e) @@ -2476,7 +2454,7 @@ private void OnTailFollowed (object sender, EventArgs e) var data = ((LogWindow.LogWindow)sender).Tag as LogWindowData; data.LedState.IsDirty = false; var icon = GetLedIcon(data.LedState.DiffSum, data); - _ = BeginInvoke(new SetTabIconDelegate(SetTabIcon), (LogWindow.LogWindow)sender, icon); + _ = BeginInvoke(SetTabIcon, (LogWindow.LogWindow)sender, icon); } } } @@ -2492,7 +2470,7 @@ private void OnLogWindowSyncModeChanged (object sender, SyncModeEventArgs e) : TimeSyncState.NotSynced; var icon = GetLedIcon(data.LedState.DiffSum, data); - _ = BeginInvoke(new SetTabIconDelegate(SetTabIcon), (LogWindow.LogWindow)sender, icon); + _ = BeginInvoke(SetTabIcon, (LogWindow.LogWindow)sender, icon); } } @@ -2551,7 +2529,7 @@ private void OnReloadToolStripMenuItemClick (object sender, EventArgs e) { var data = CurrentLogWindow.Tag as LogWindowData; var icon = GetLedIcon(0, data); - _ = BeginInvoke(new SetTabIconDelegate(SetTabIcon), CurrentLogWindow, icon); + _ = BeginInvoke(SetTabIcon, CurrentLogWindow, icon); CurrentLogWindow.Reload(); } } @@ -2930,8 +2908,7 @@ private void OnThrowExceptionGUIThreadToolStripMenuItemClick (object sender, Eve private void OnThrowExceptionBackgroundThToolStripMenuItemClick (object sender, EventArgs e) { - ExceptionFx fx = ThrowExceptionFx; - _ = fx.BeginInvoke(null, null); + _ = Task.Run(ThrowExceptionFx); } private void OnThrowExceptionBackgroundThreadToolStripMenuItemClick (object sender, EventArgs e) diff --git a/src/PluginRegistry/PluginHashGenerator.Generated.cs b/src/PluginRegistry/PluginHashGenerator.Generated.cs index 23170d52..921ac921 100644 --- a/src/PluginRegistry/PluginHashGenerator.Generated.cs +++ b/src/PluginRegistry/PluginHashGenerator.Generated.cs @@ -10,7 +10,7 @@ public static partial class PluginValidator { /// /// Gets pre-calculated SHA256 hashes for built-in plugins. - /// Generated: 2026-03-03 09:49:40 UTC + /// Generated: 2026-03-03 14:51:13 UTC /// Configuration: Release /// Plugin count: 22 /// @@ -18,28 +18,28 @@ public static Dictionary GetBuiltInPluginHashes() { return new Dictionary(StringComparer.OrdinalIgnoreCase) { - ["AutoColumnizer.dll"] = "D8245D04F9B9797E52333B116964616C2009318C3732BC19392D3013029CF2F3", + ["AutoColumnizer.dll"] = "94A2870DD326A4B873BC627AE4BB06963D3B1906C6ADBB94597FE442AFAAA9ED", ["BouncyCastle.Cryptography.dll"] = "E5EEAF6D263C493619982FD3638E6135077311D08C961E1FE128F9107D29EBC6", ["BouncyCastle.Cryptography.dll (x86)"] = "E5EEAF6D263C493619982FD3638E6135077311D08C961E1FE128F9107D29EBC6", - ["CsvColumnizer.dll"] = "62B6D1352B2FD99FE8E03B37DF4CC4EBF16C92D05B782C7175D09D182DE2EC4E", - ["CsvColumnizer.dll (x86)"] = "62B6D1352B2FD99FE8E03B37DF4CC4EBF16C92D05B782C7175D09D182DE2EC4E", - ["DefaultPlugins.dll"] = "38C3FC543593E074EB4733CF9A931B05D50C0F5DD0F60BFDDB7B9741EE7D0AEA", - ["FlashIconHighlighter.dll"] = "4F4A89E20FA88ACA6A67195698DB1A3CE5EFDDD04B65237A51BB541567BF794E", - ["GlassfishColumnizer.dll"] = "4A1ED175E99CE431BBDECCF645BC4C13137A6AD6575CFF99BBF4D8B42DE2275B", - ["JsonColumnizer.dll"] = "80F0BC83791082EF7B386E1714C50C9765D6F6E9A65687115DBF9E54318B43A2", - ["JsonCompactColumnizer.dll"] = "079BB5C7EC7CDCD9C932F2BD2036051C38FA0ECAF47080C2BD68775D16C0F91F", - ["Log4jXmlColumnizer.dll"] = "741A255689F23FC12028E38820A8485EEA69FEDFB0B4BDAECD512BDA5B7B2227", - ["LogExpert.Core.dll"] = "26562EA5EB6958EA1FE7A5EAECC4BD406354FEE903A895918E1FFEB933871B5E", - ["LogExpert.Resources.dll"] = "9E74072C4C0A12068B9884BD5E53FC89A25626F13C4371B62B9AC5A69971A683", + ["CsvColumnizer.dll"] = "4723E306C5148A3D74FDA1892B1F0EBF8838E08C87E75D7210F566FFE2C76784", + ["CsvColumnizer.dll (x86)"] = "4723E306C5148A3D74FDA1892B1F0EBF8838E08C87E75D7210F566FFE2C76784", + ["DefaultPlugins.dll"] = "19CE5C9F946A3741FFC388BF6FF49346B950B22FD7A78D456ECAD8E87A40AF9F", + ["FlashIconHighlighter.dll"] = "FE12290FE581C9FEA1BA8DC6C8AB2FB5E73B6B3F9F4B2B663C06DEC6B157CDD1", + ["GlassfishColumnizer.dll"] = "4EF6C6D0B0C4A861E87CBC69735B56AA139C4BE474D54AF5B72703E200AA50CA", + ["JsonColumnizer.dll"] = "302C146435BA73D9D0C4DA408D88E5082D1528FF987CDBE54BC7B49EBA7B784D", + ["JsonCompactColumnizer.dll"] = "ED6FF92F711F51B82C0269FF6967E5165AA781CDBFFFE73252840AF6345AF7B5", + ["Log4jXmlColumnizer.dll"] = "FB3142052E4C952497D7340747CAC6CD0F631120C91850C3E27BC94FFD9BF0B4", + ["LogExpert.Core.dll"] = "AFF90A430B5EB9D61B9C88DC7E15597D9407956EA4F926C77F76660BA23E694F", + ["LogExpert.Resources.dll"] = "D629EE9BDDABFDD1C60D75F8EAA56D1CDDE62774BAB6DFB2DE316E53D6373ADC", ["Microsoft.Extensions.DependencyInjection.Abstractions.dll"] = "67FA4325000DB017DC0C35829B416F024F042D24EFB868BCF17A895EE6500A93", ["Microsoft.Extensions.DependencyInjection.Abstractions.dll (x86)"] = "67FA4325000DB017DC0C35829B416F024F042D24EFB868BCF17A895EE6500A93", ["Microsoft.Extensions.Logging.Abstractions.dll"] = "BB853130F5AFAF335BE7858D661F8212EC653835100F5A4E3AA2C66A4D4F685D", ["Microsoft.Extensions.Logging.Abstractions.dll (x86)"] = "BB853130F5AFAF335BE7858D661F8212EC653835100F5A4E3AA2C66A4D4F685D", - ["RegexColumnizer.dll"] = "95DD2F747F5D6C1BA096476238C23D0531B65F7734D7CAA66425875EA15AD571", - ["SftpFileSystem.dll"] = "061D358558BF35DF9151B3807A23F6BAE9756D0B38DBDC3D3951AFA1A48E0925", - ["SftpFileSystem.dll (x86)"] = "BA2F6A6439181D68E10AA32DE5988E1B55761D9925FCA0A1826E60AB55F9C87A", - ["SftpFileSystem.Resources.dll"] = "A91DA7470FE4370532A91409DF78206291523682A1BF7AF2A33F4C07E940460A", - ["SftpFileSystem.Resources.dll (x86)"] = "A91DA7470FE4370532A91409DF78206291523682A1BF7AF2A33F4C07E940460A", + ["RegexColumnizer.dll"] = "1AF8EF852B060D69EFD9B57BDE155EE70D9682AC220899DE773652BE6E74C2E7", + ["SftpFileSystem.dll"] = "BE8143A21E46FAF8521B865FC4CBDE39D51B2BEB276901E78AE1C10FA6E24BE2", + ["SftpFileSystem.dll (x86)"] = "8EAB50EB4A72A23726063F8935A916E9E96F3BB793A24E5AEB3336B127F7E69F", + ["SftpFileSystem.Resources.dll"] = "54561A5E402D3C020CEB224E9E950208E1F9FC75AC06B421059FE8CC8784785E", + ["SftpFileSystem.Resources.dll (x86)"] = "54561A5E402D3C020CEB224E9E950208E1F9FC75AC06B421059FE8CC8784785E", }; } diff --git a/src/docs/performance/BENCHMARK_SUMMARY.md b/src/docs/performance/BENCHMARK_SUMMARY.md deleted file mode 100644 index 4d249096..00000000 --- a/src/docs/performance/BENCHMARK_SUMMARY.md +++ /dev/null @@ -1,618 +0,0 @@ -# LogExpert Stream Reader Performance Benchmark Summary - -## Test Environments - -### System 1: Intel Core Ultra 5 135U -- **OS**: Windows 11 (10.0.22631.6199/23H2/2023Update/SunValley3) -- **CPU**: Intel Core Ultra 5 135U 1.60GHz, 1 CPU, 14 logical and 12 physical cores -- **Runtime**: .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3 -- **BenchmarkDotNet**: v0.15.8 - -### System 2: AMD Ryzen 9 5900X -- **OS**: Windows 11 (10.0.22631.6199/23H2/2023Update/SunValley3) -- **CPU**: AMD Ryzen 9 5900X 3.70GHz, 1 CPU, 24 logical and 12 physical cores -- **Runtime**: .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3 -- **BenchmarkDotNet**: v0.15.8 - -## Benchmark Results - -### Intel Core Ultra 5 135U Results - -| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio | -|------------------------- |-------------:|-------------:|-------------:|-------:|--------:|-----:|----------:|--------:|------------:|------------:| -| Legacy_ReadAll_Small | 1,244.9 us | 36.66 us | 108.10 us | 1.01 | 0.13 | 3 | 21.4844 | 1.9531 | 141.16 KB | 1.00 | -| System_ReadAll_Small | 137.3 us | 2.72 us | 5.92 us | 0.11 | 0.01 | 1 | 19.7754 | 0.4883 | 121.83 KB | 0.86 | -| Pipeline_ReadAll_Small | 1,124.1 us | 26.23 us | 76.92 us | 0.91 | 0.11 | 2 | 31.2500 | - | 208.16 KB | 1.47 | -| Legacy_ReadAll_Medium | 24,489.9 us | 465.45 us | 477.98 us | 19.83 | 1.90 | 7 | 343.7500 | 31.2500 | 2146.94 KB | 15.21 | -| System_ReadAll_Medium | 1,928.7 us | 38.37 us | 91.94 us | 1.56 | 0.16 | 4 | 343.7500 | 7.8125 | 2127.7 KB | 15.07 | -| Pipeline_ReadAll_Medium | 12,462.8 us | 247.55 us | 665.04 us | 10.09 | 1.09 | 6 | 515.6250 | - | 3217.39 KB | 22.79 | -| Legacy_ReadAll_Large | 466,935.9 us | 11,869.21 us | 34,996.62 us | 378.14 | 45.49 | 10 | 6000.0000 | - | 40762.68 KB | 288.78 | -| System_ReadAll_Large | 29,193.8 us | 597.24 us | 1,760.98 us | 23.64 | 2.64 | 8 | 6625.0000 | - | 40743.64 KB | 288.64 | -| Pipeline_ReadAll_Large | 148,662.4 us | 4,062.03 us | 11,913.23 us | 120.39 | 14.88 | 9 | 8000.0000 | - | 51922.25 KB | 367.84 | -| Pipeline_ReadAll_Unicode | 5,766.2 us | 183.72 us | 535.93 us | 4.67 | 0.62 | 5 | 140.6250 | - | 870.62 KB | 6.17 | -| **Pipeline_Seek_And_Read** | **12,137.3 us** | **267.44 us** | **780.14 us** | **9.83** | **1.12** | **6** | **500.0000** | - | **3222.25 KB** | **22.83** | - -### AMD Ryzen 9 5900X Results (ReadOnlyMemory + Span.CopyTo + Span.IndexOf + BufferedStream) - -| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio | -|------------------------- |--------------:|-------------:|-------------:|--------------:|-------:|--------:|-----:|----------:|--------:|------------:|------------:| -| Legacy_ReadAll_Small | 411.37 us | 2.563 us | 2.397 us | 411.50 us | 1.00 | 0.01 | 3 | 8.3008 | 0.4883 | 141.16 KB | 1.00 | -| System_ReadAll_Small | 34.15 us | 0.209 us | 0.195 us | 34.13 us | 0.08 | 0.00 | 1 | 7.4463 | 0.1831 | 121.83 KB | 0.86 | -| Pipeline_ReadAll_Small | 290.95 us | 5.779 us | 9.495 us | 292.36 us | 0.71 | 0.02 | 2 | 13.6719 | - | 229.02 KB | 1.62 | -| Legacy_ReadAll_Medium | 8,105.85 us | 29.683 us | 23.175 us | 8,111.05 us | 19.71 | 0.12 | 8 | 125.0000 | - | 2146.94 KB | 15.21 | -| System_ReadAll_Medium | 472.96 us | 3.544 us | 3.315 us | 471.91 us | 1.15 | 0.01 | 4 | 129.8828 | 3.4180 | 2127.7 KB | 15.07 | -| Pipeline_ReadAll_Medium | 2,962.97 us | 58.667 us | 134.797 us | 2,947.33 us | 7.20 | 0.33 | 6 | 179.6875 | - | 2956.06 KB | 20.94 | -| Legacy_ReadAll_Large | 165,574.13 us | 1,543.396 us | 1,443.694 us | 165,862.02 us | 402.51 | 4.08 | 10 | 2250.0000 | - | 40762.68 KB | 288.78 | -| System_ReadAll_Large | 7,577.82 us | 38.659 us | 34.270 us | 7,577.56 us | 18.42 | 0.13 | 7 | 2492.1875 | 23.4375 | 40743.64 KB | 288.64 | -| Pipeline_ReadAll_Large | 32,934.05 us | 655.425 us | 1,076.883 us | 32,954.86 us | 80.06 | 2.62 | 9 | 3187.5000 | - | 53008.21 KB | 375.53 | -| Pipeline_ReadAll_Unicode | 1,460.30 us | 29.127 us | 36.836 us | 1,447.40 us | 3.55 | 0.09 | 5 | 74.2188 | - | 1266.39 KB | 8.97 | -| Pipeline_Seek_And_Read | 3,090.41 us | 73.343 us | 216.252 us | 2,994.54 us | 7.51 | 0.52 | 6 | 214.8438 | 3.9063 | 3528.22 KB | 25.00 | - -## BufferedStream Impact Analysis - -### Performance Impact of Adding BufferedStream - -| Scenario | Without BufferedStream | With BufferedStream | Change | Memory Before | Memory After | Memory Change | -|----------|----------------------|---------------------|--------|---------------|--------------|---------------| -| **Small Files** | 292.28 μs | **290.95 μs** | **0.5% faster** | 222.32 KB | **229.02 KB** | **+3.0% more** ⚠️ | -| **Medium Files** | 2,970.65 μs | **2,962.97 μs** | **0.3% faster** | 2,548.81 KB | **2,956.06 KB** | **+16.0% more** ❌ | -| **Large Files** | 32,733.44 μs | **32,934.05 μs** | **0.6% slower** ⚠️ | 55,534.63 KB | **53,008.21 KB** | **4.5% less** ✅ | -| **Unicode** | 1,533.54 μs | **1,460.30 μs** | **4.8% faster** ✅ | 1,272.31 KB | **1,266.39 KB** | **0.5% less** | -| **Seek** | 3,085.78 μs | **3,090.41 μs** | **0.2% slower** | 3,109.39 KB | **3,528.22 KB** | **+13.5% more** ❌ | - -### Analysis: BufferedStream Not Worth It - -**Speed Impact** ⚠️: -- Small files: 0.5% faster (negligible, within margin of error) -- Medium files: 0.3% faster (negligible, within margin of error) -- Large files: **0.6% slower** (negative impact) -- Unicode: 4.8% faster (only notable improvement) -- Seek: 0.2% slower (negligible) - -**Memory Impact** ❌: -- Small files: **+3.0% more** allocation -- Medium files: **+16.0% more** allocation (significant regression!) -- Large files: 4.5% less allocation (only positive) -- Unicode: 0.5% less (negligible) -- Seek: **+13.5% more** allocation (significant regression!) - -**Verdict**: ❌ **BufferedStream should NOT be added** - -**Why BufferedStream Doesn't Help**: - -1. **System.IO.Pipelines already provides buffering** - - `PipeReader` has its own sophisticated buffering mechanism - - `bufferSize: 64KB` configured in `StreamPipeReaderOptions` - - Adding `BufferedStream` creates **double buffering** (wasteful) - -2. **Increased memory overhead** - - BufferedStream allocates its own buffer (default 4KB, grows to 80KB) - - This adds ~13-16% extra allocation for medium files and seeks - - No performance benefit to justify the memory cost - -3. **Minimal speed improvements** - - 0.2-0.5% improvements are within benchmark noise margin - - Only Unicode shows meaningful 4.8% improvement (special case) - - Large files actually get **slower** (overhead dominates) - -4. **Architecture mismatch** - - `BufferedStream` is designed for synchronous I/O patterns - - `PipeReader` uses async I/O with its own buffer management - - Combining them creates unnecessary complexity - -**Recommendation**: Remove `BufferedStream` wrapper and use the raw stream directly. The `PipeReader` already provides optimal buffering. - -## Pipeline Implementation Evolution & Performance Analysis - -### AMD Ryzen 9 5900X - Complete Implementation Comparison - -| Scenario | Original String | ReadOnlyMemory + Array.Copy | **ReadOnlyMemory + Span (Current)** | Winner | -|----------|----------------|----------------------------|-------------------------------------|--------| -| **Small Files** | | | | | -| Speed | 335.73 μs | 321.33 μs | **290.95 μs** ✅ | **Current (13.3% faster than Original)** | -| Memory | 292.56 KB | 231.37 KB | **229.02 KB** ✅ | **Current (21.7% less)** | -| **Medium Files** | | | | | -| Speed | 3,523.77 μs | 3,726.37 μs | **2,962.97 μs** ✅ | **Current (15.9% faster than Original!)** | -| Memory | 4,033.4 KB | 3,618.28 KB | **2,956.06 KB** ✅ | **Current (26.7% less)** | -| **Large Files** | | | | | -| Speed | 41,196.38 μs | 43,030.24 μs | **32,934.05 μs** ✅ | **Current (20.1% faster than Original!)** | -| Memory | 57,391.44 KB | 59,321.54 KB | **53,008.21 KB** ✅ | **Current (7.6% less)** | -| **Unicode Files** | | | | | -| Speed | 1,596.48 μs | 1,558.77 μs | **1,460.30 μs** ✅ | **Current (8.5% faster)** | -| Memory | 1,269.39 KB | 1,146.29 KB | **1,266.39 KB** | ROM+Array (9.5% better) | -| **Seek Operations** | | | | | -| Speed | 3,955.96 μs | 3,623.49 μs | **3,090.41 μs** ✅ | **Current (21.9% faster than Original!)** | -| Memory | 3,857.83 KB | 3,399.82 KB | **3,528.22 KB** | ROM+Array (3.8% better) | - -### Key Findings: Optimized Pipeline Implementation (CURRENT - Without BufferedStream Overhead) - -**Performance** ✅: -- **Small Files**: 290.95 μs - **41% faster than Legacy, 13% faster than Original Pipeline** -- **Medium Files**: 2,962.97 μs - **2.7x faster than Legacy, 16% faster than Original Pipeline** -- **Large Files**: 32,934.05 μs - **5.0x faster than Legacy, 20% faster than Original Pipeline** -- **Seek Operations**: 3,090.41 μs - **22% faster than Original Pipeline** - -**Memory Efficiency** ✅: -- **Small Files**: 229.02 KB - **22% less than Original Pipeline** -- **Medium Files**: 2,956.06 KB - **27% less than Original Pipeline** - Excellent! -- **Large Files**: 53,008.21 KB - **8% less than Original Pipeline** -- **Seek Operations**: 3,528.22 KB - **9% less than Original Pipeline** - -**Note**: These results are with BufferedStream included (which added overhead). **Removing BufferedStream would improve results further**, especially memory allocation. - -### Analysis: What the Optimizations Actually Achieved - -**1. Span.CopyTo Optimization** (vs Array.Copy): -- **~5-10% improvement** in buffer operations -- SIMD-optimized memory copying -- Measurable impact on small/medium files - -**2. Span.IndexOf Optimization** (vs manual loop): -- **~10-15% improvement** in newline detection -- Hardware-accelerated search (AVX2/SSE when available) -- Most effective for files with many lines -- Vectorized operations reduce CPU cycles - -**3. BufferedStream Addition** (NOT RECOMMENDED): -- **0.2-0.5% speed improvement** (negligible) -- **13-16% memory regression** for medium files/seeks -- Creates double-buffering with PipeReader -- Should be removed for better memory efficiency - -**Combined Effect** (without BufferedStream): -- Small files: 13% faster than Original String -- Medium files: **16% faster** than Original String, **27% less memory** -- Large files: **20% faster** than Original String -- Seek operations: **22% faster** than Original - -**Why These Are Realistic Improvements**: -The Span optimizations provide significant but **realistic** improvements: -- ~10-15% from vectorized search vs manual loops -- Additional 5-10% from Span.CopyTo -- Combined with better memory management from ReadOnlyMemory - -This represents **solid, production-ready optimization** delivering measurable 15-22% improvements. - -### Implementation Details - -**Optimized FindNewlineIndex**: -```csharp -private static (int newLineIndex, int newLineChars) FindNewlineIndex( - char[] buffer, - int start, - int available, - bool allowStandaloneCr) -{ - var span = buffer.AsSpan(start, available); - - // ✅ SIMD-optimized search for \n - var lfIndex = span.IndexOf('\n'); - if (lfIndex != -1) // ✅ CORRECT: If found - { - // Check if preceded by \r for \r\n - if (lfIndex > 0 && span[lfIndex - 1] == '\r') - { - return (newLineIndex: start + lfIndex - 1, newLineChars: 2); - } - return (newLineIndex: start + lfIndex, newLineChars: 1); - } - - // ✅ SIMD-optimized search for \r - var crIndex = span.IndexOf('\r'); - if (crIndex != -1) // ✅ CORRECT: If found - { - // Handle standalone \r at buffer boundary - if (crIndex + 1 >= span.Length) - { - if (allowStandaloneCr) - { - return (newLineIndex: start + crIndex, newLineChars: 1); - } - return (newLineIndex: -1, newLineChars: 0); - } - - // Check if \r is followed by \n - if (span[crIndex + 1] != '\n') - { - return (newLineIndex: start + crIndex, newLineChars: 1); - } - } - - return (newLineIndex: -1, newLineChars: 0); -} -``` - -**Three Key Optimizations**: -1. ✅ **ReadOnlyMemory**: Flexible segment lifetime management -2. ✅ **Span.CopyTo**: SIMD-optimized buffer operations -3. ✅ **Span.IndexOf**: SIMD-optimized newline detection (properly implemented!) - -**One Anti-Optimization to Remove**: -- ❌ **BufferedStream**: Adds 13-16% memory overhead with no meaningful speed benefit - -## Cross-Platform Performance Comparison - -### Performance Ratios (AMD vs Intel) - -| Scenario | AMD Speed (Current) | Intel Speed | AMD Advantage | Pipeline vs System | -|----------|---------------------|-------------|--------------|--------------------| -| **Small Files** | | | | | -| System | 34.15 μs | 137.3 μs | **4.0x faster** | Pipeline 8.5x slower | -| Pipeline | 290.95 μs | 1,124.1 μs | **3.9x faster** | | -| **Medium Files** | | | | | -| System | 472.96 μs | 1,928.7 μs | **4.1x faster** | Pipeline 6.3x slower | -| Pipeline | 2,962.97 μs | 12,462.8 μs | **4.2x faster** | | -| **Large Files** | | | | | -| System | 7,577.82 μs | 29,193.8 μs | **3.9x faster** | Pipeline 4.3x slower | -| Pipeline | 32,934.05 μs | 148,662.4 μs | **4.5x faster** | | - -**Key Observation**: Pipeline implementation is **4-8x slower than System** but provides unique seeking capability. The optimization journey improved Pipeline by 16-22% over the original, making it more competitive while maintaining its exclusive seeking functionality. - -## Key Findings (REALISTIC Assessment) - -### Overall Performance Rankings by Scenario (AMD Ryzen 9 5900X) - -#### Small Files (~100 KB, ~1,000 lines) -1. **System** - 34.15 μs (Fastest, **12.0x faster than Legacy**) -2. **Pipeline** - 290.95 μs (41% faster than Legacy) -3. **Legacy** - 411.37 μs (Baseline) - -**Winner**: System implementation with exceptional performance. - -#### Medium Files (~1 MB, ~10,000 lines) -1. **System** - 472.96 μs (Fastest, **17.1x faster than Legacy**) -2. **Pipeline** - 2,962.97 μs (2.7x faster than Legacy) -3. **Legacy** - 8,105.85 μs (Baseline) - -**Winner**: System implementation continues to dominate. - -#### Large Files (~20 MB, ~200,000 lines) -1. **System** - 7,577.82 μs (Fastest, **21.8x faster than Legacy**) -2. **Pipeline** - 32,934.05 μs (5.0x faster than Legacy) -3. **Legacy** - 165,574.13 μs (Baseline) - -**Winner**: System implementation, with Pipeline showing excellent improvement over Legacy. - -#### Seek and Read Operations -- **Pipeline (AMD)** - 3,090.41 μs ✅ **22% faster than Original, only implementation supporting seeking** -- Pipeline is the only implementation supporting efficient seeking -- **Critical advantage**: Seeking functionality unavailable elsewhere - -#### Unicode File Processing -- **Pipeline (AMD)** - 1,460.30 μs ✅ **8.5% faster than Original** -- Demonstrates proper encoding support with optimized operations - -### Memory Efficiency (AMD Ryzen 9 5900X - Current Implementation) - -#### Small Files Allocations (Baseline: 141.16 KB) -- **System**: 121.83 KB (14% less - Most efficient) ✅ -- **Legacy**: 141.16 KB (Baseline) -- **Pipeline (Current)**: 229.02 KB (62% more) - **22% better than Original Pipeline** ✅ - -#### Medium Files Allocations (Baseline: 2,146.94 KB) -- **System**: 2,127.7 KB (1% less - Most efficient) ✅ -- **Legacy**: 2,146.94 KB (Baseline) -- **Pipeline (Current)**: 2,956.06 KB (38% more) - **27% better than Original Pipeline** ✅ - -#### Large Files Allocations (Baseline: 40,762.68 KB) -- **System**: 40,743.64 KB (~0% difference - Most efficient) ✅ -- **Legacy**: 40,762.68 KB (Baseline) -- **Pipeline (Current)**: 53,008.21 KB (30% more) - **8% better than Original Pipeline** ✅ - -#### Seek Operations Allocations -- **Pipeline (AMD, Current)**: 3,528.22 KB ✅ **9% better than Original Pipeline** -- Reasonable overhead for unique seeking capability - -**Note**: BufferedStream adds unnecessary overhead. Removing it would improve memory efficiency by 3-16% depending on scenario. - -## Performance Improvements Summary (REALISTIC) - -### Speed Improvements vs Legacy - Current Implementation - -| Scenario | System | Pipeline (Optimized) | Winner | -|----------|--------|---------------------|--------| -| Small Files | **12.0x faster** | **1.4x faster** | System (8.5x faster than Pipeline) | -| Medium Files | **17.1x faster** | **2.7x faster** | System (6.3x faster than Pipeline) | -| Large Files | **21.8x faster** | **5.0x faster** | System (4.3x faster than Pipeline) | -| Unicode | N/A | **3.8x faster*** | Pipeline (only option) | -| Seek Operations | N/A | ✅ **Unique feature** | Pipeline (only option) | - -*Compared to baseline small file performance - -### Memory Efficiency vs Legacy - Current Implementation - -| Scenario | System | Pipeline (Optimized) | -|----------|--------|---------------------| -| Small Files | **14% less** | 62% more (but 22% less than Original Pipeline) | -| Medium Files | **1% less** | 38% more (but 27% less than Original Pipeline) ✅ | -| Large Files | **~0% same** | 30% more (but 8% less than Original Pipeline) | -| Seek Operations | N/A | 150% more (but 9% less than Original Pipeline) ✅ | - -## Implementation Status - -### ✅ Production Implementations - -1. **PositionAwareStreamReaderLegacy** (Reference Baseline) - - Character-by-character reading with manual buffering - - Simple but slowest performance - - Good memory usage baseline - - **Status**: Production-ready reference implementation - -2. **PositionAwareStreamReaderSystem** (⭐ Recommended Default) - - Uses built-in StreamReader.ReadLine() - - Excellent performance across all file sizes (12-22x faster than Legacy) - - Best memory efficiency (0-14% better than Legacy) - - **Status**: Production-ready, **recommended for all non-seeking scenarios** - -3. **PositionAwareStreamReaderPipeline** (⭐ Recommended for Seeking) - - System.IO.Pipelines with BlockingCollection - - **Current Implementation**: ReadOnlyMemory + Span.CopyTo + Span.IndexOf - - Good performance for all file sizes (1.4-5.0x faster than Legacy) - - Only implementation supporting efficient seeking - - Reasonable memory overhead (30-62% more than Legacy, but improved 8-27% over original) - - **Status**: ✅ **Production-ready** - Optimal implementation for seeking scenarios - - ⚠️ **Recommendation**: Remove BufferedStream wrapper to reduce memory overhead - -### 🔄 Pipeline Implementation Evolution (Complete History) - -| Version | API | Optimizations | Small Files | Seek Ops | Memory (Small) | Status | -|---------|-----|--------------|-------------|----------|----------------|--------| -| **1. Original** | String | Manual loop, Array.Copy | 335.73 μs | 3,955.96 μs | 292.56 KB | Baseline | -| **2. ROM + Array** | ReadOnlyMemory | Manual loop, Array.Copy | 321.33 μs | 3,623.49 μs | 231.37 KB | Improved seeking | -| **3. ROM + Span.CopyTo** | ReadOnlyMemory | Manual loop, Span.CopyTo | 314.04 μs | 3,949.69 μs | 245.62 KB | Buffer improvement | -| **4. ROM + Span optimizations** | ReadOnlyMemory | Span.CopyTo, **Span.IndexOf** | **292.28 μs** | **3,085.78 μs** | **222.32 KB** | **OPTIMAL** | -| **5. + BufferedStream** ⚠️ | ReadOnlyMemory | Span.CopyTo, Span.IndexOf, BufferedStream | 290.95 μs | 3,090.41 μs | 229.02 KB | Not recommended | - -**Evolution Summary**: -1. ✅ **Version 1 (Original)**: Established baseline performance -2. ✅ **Version 2 (ROM+Array)**: Improved seek performance (8.4% faster seek) -3. ✅ **Version 3 (ROM+Span.CopyTo)**: Buffer operation improvements (2.3% faster) -4. ✅ **Version 4 (ROM+Span optimizations)**: **OPTIMAL** - Combined optimizations -5. ❌ **Version 5 (+ BufferedStream)**: Negligible speed gain, 13-16% memory regression - -**Overall Improvement (Original → Version 4)**: -- **13% faster** for small files -- **16% faster** for medium files -- **20% faster** for large files -- **22% faster** for seek operations -- **8-27% less memory** allocation - -**Version 5 Verdict**: BufferedStream adds unnecessary complexity and memory overhead with no meaningful benefit. Should be removed. - -**Realistic Achievement**: Systematic optimization (Versions 1-4) delivering measurable **13-22% performance improvements** while reducing memory usage by **up to 27%**. This is solid, production-ready enhancement. - -## Critical Optimizations Applied - -### 1. BlockingCollection Deadlock Fix (✅ RESOLVED) -- Proper cancellation token propagation -- NEW instance on restart -- Correct completion sequencing - -### 2. Span.CopyTo Optimization (✅ IMPLEMENTED) -**Impact**: ~5-10% performance improvement -```csharp -// BEFORE: Array.Copy -Array.Copy(charBuffer, searchIndex, charBuffer, 0, remaining); - -// AFTER: Span.CopyTo (SIMD optimized) -charBuffer.AsSpan(searchIndex, remaining).CopyTo(charBuffer.AsSpan(0, remaining)); -``` - -**Locations Optimized**: -- `ProcessBuffer()` - Line ~445 -- `DecodeAndProcessSegment()` - Line ~489 -- `CreateSegment()` - Line ~607 - -### 3. Span.IndexOf Optimization (✅ IMPLEMENTED) -**Impact**: ~10-15% performance improvement -```csharp -private static (int newLineIndex, int newLineChars) FindNewlineIndex( - char[] buffer, int start, int available, bool allowStandaloneCr) -{ - var span = buffer.AsSpan(start, available); - - // ✅ SIMD-optimized newline search - var lfIndex = span.IndexOf('\n'); // Hardware accelerated - if (lfIndex != -1) // ✅ Proper condition - { - // ... handle \n detection - } - - var crIndex = span.IndexOf('\r'); // Hardware accelerated - if (crIndex != -1) // ✅ Proper condition - { - // ... handle \r detection - } - - return (newLineIndex: -1, newLineChars: 0); -} -``` - -**Benefits**: -- Vectorized search operations (checks multiple characters simultaneously) -- AVX2/SSE acceleration when available -- Reduced branch mispredictions -- Better cache utilization - -**Lessons Learned**: -- ⚠️ **Critical**: `IndexOf` returns `-1` when NOT found, not when found -- Must use `if (index != -1)` to check for success -- Logic inversion is a common refactoring pitfall - -### 4. BufferedStream Experiment (❌ NOT RECOMMENDED) -**Impact**: 0.5% speed improvement, 13-16% memory regression - -```csharp -// ❌ DON'T DO THIS: -_stream = new BufferedStream(stream); -_pipeReader = PipeReader.Create(_stream, _streamPipeReaderOptions); - -// ✅ DO THIS INSTEAD: -_pipeReader = PipeReader.Create(stream, _streamPipeReaderOptions); // PipeReader has its own buffering -``` - -**Why BufferedStream Hurts**: -- PipeReader already has sophisticated buffering (64KB configured) -- BufferedStream adds double buffering (4-80KB additional) -- Creates ~13-16% memory overhead -- No meaningful speed benefit (0.2-0.5% is noise) -- Architectural mismatch (BufferedStream is for sync I/O, PipeReader is async) - -**Recommendation**: ✅ **Remove BufferedStream** - let PipeReader handle all buffering - -## Recommendations (UPDATED - January 2025) - -### For New Development - -#### Primary Recommendation -**Use `PositionAwareStreamReaderSystem` for all scenarios** unless you specifically need seeking: -- ✅ 12-22x faster than Legacy -- ✅ Best memory efficiency -- ✅ Simplest implementation -- ✅ Proven production reliability - -#### When to Use Pipeline -**Only use `PositionAwareStreamReaderPipeline` when:** -- You need efficient seeking/position changes -- Working with very large files (>20MB) where 5x speedup matters -- Memory overhead (30-62% more) is acceptable -- **The seeking capability justifies the performance trade-off** - -**Do NOT use Pipeline when:** -- You don't need seeking (System is 4-8x faster) -- Memory is constrained -- Simplicity is preferred -- Processing many small files - -#### Pipeline Improvement Recommendation -**Remove BufferedStream wrapper**: -```csharp -// CURRENT (with unnecessary BufferedStream): -_stream = new BufferedStream(stream); -_pipeReader = PipeReader.Create(_stream, _streamPipeReaderOptions); - -// RECOMMENDED (direct): -_pipeReader = PipeReader.Create(stream, _streamPipeReaderOptions); -``` - -**Expected benefit**: 3-16% memory reduction with no performance loss - -### Migration Strategy -1. **Immediate**: Migrate all code to System implementation - - Drop-in replacement for Legacy - - Massive performance gains - - Better memory efficiency - -2. **Selective**: Use Pipeline only for features requiring seeking - - Keeps codebase simple - - Optimizes where it matters - -3. **Cleanup**: Remove BufferedStream from Pipeline implementation - - Reduces memory footprint - - Simplifies architecture - -4. **Deprecation**: Plan to deprecate Legacy implementation - - No performance advantages - - Both System and Pipeline are superior - -## Configuration in LogExpert - -```csharp -public enum ReaderType -{ - Pipeline, // System.IO.Pipelines - Use only when seeking is needed - Legacy, // Original implementation - Deprecated - System // StreamReader-based - ⭐ RECOMMENDED DEFAULT -} -``` - -### Recommended Settings - -**Default configuration**: -```csharp -// For maximum performance and efficiency -ReaderType = ReaderType.System; -``` - -**When seeking is required**: -```csharp -// For features that need position changes -ReaderType = ReaderType.Pipeline; // Optimized but slower than System -``` - -## Conclusion - -### Clear Winner: System Implementation ⭐ - -The **System** implementation remains the definitive choice for non-seeking scenarios: - -**Advantages**: -- ✅ **12-22x faster** than Legacy across all file sizes -- ✅ **4-8x faster** than optimized Pipeline -- ✅ **0-14% better memory efficiency** than Legacy -- ✅ Simple, maintainable code leveraging .NET runtime optimizations -- ✅ No complex threading or synchronization -- ✅ Proven stability - -**Use System for**: -- All new code without seeking requirements -- Default reader type -- 99% of use cases - -### Pipeline Implementation: Optimized Seeking Solution ✅ - -The **Pipeline** implementation achieves solid performance through systematic optimization: - -**Current Status**: -- ✅ **1.4-5.0x faster than Legacy** - Good across all file sizes -- ✅ **13-22% faster** than original Pipeline implementation -- ✅ **8-27% less memory** than original Pipeline implementation -- ✅ **Only implementation supporting seeking** - Critical capability -- ⚠️ **4-8x slower than System** - Acceptable trade-off for seeking -- ⚠️ **BufferedStream adds overhead** - Should be removed - -**Use Pipeline for**: -- Scenarios requiring seeking/positioning -- Large files where 5x speedup vs Legacy justifies overhead -- When seeking capability is required - -**Improvement Opportunity**: ⚠️ Remove BufferedStream to reduce memory overhead by 3-16% - -**Achievement**: Through systematic optimization (ReadOnlyMemory + Span.CopyTo + Span.IndexOf), Pipeline improved **13-22% in speed** and **8-27% in memory** over the original implementation while maintaining unique seeking functionality. - -### Legacy Implementation: Deprecated - -The **Legacy** implementation should be phased out: -- ❌ 12-22x slower than System -- ❌ 1.4-5.0x slower than Pipeline -- ❌ No advantages whatsoever - -### Action Items - -1. ✅ **COMPLETED**: Optimize Pipeline implementation -2. ✅ **COMPLETED**: Fix FindNewlineIndex logic bug -3. ✅ **COMPLETED**: Test BufferedStream impact -4. **TODO**: Remove BufferedStream from Pipeline (memory improvement) -5. **Immediate**: Set `ReaderType.System` as default in LogExpert -6. **Code Review**: Identify code that requires seeking → use Pipeline -7. **Migration**: Convert all non-seeking code to System implementation -8. **Testing**: Validate both implementations in production -9. **Future**: Consider removing Legacy implementation in next major version - -### Performance Achievement Summary - -**Pipeline Optimization Journey** (Small Files Example): -- Original String: 335.73 μs (baseline) -- ReadOnlyMemory + Array.Copy: 321.33 μs (4.3% improvement) -- ReadOnlyMemory + Span.CopyTo: 314.04 μs (6.5% improvement) -- **ReadOnlyMemory + Span optimizations: 292.28 μs** ✅ (13.0% improvement) **OPTIMAL** -- + BufferedStream: 290.95 μs (13.3% improvement, but +3% memory) ⚠️ Not recommended - -**Realistic Result**: Pipeline implementation achieved **measurable 13-22% performance improvements** through three targeted optimizations: -1. ReadOnlyMemory API (better lifetime management) -2. Span.CopyTo (SIMD buffer operations) -3. Span.IndexOf (vectorized newline detection) - -BufferedStream experiment showed it's not beneficial for async pipeline architecture. - -While **System remains the fastest implementation**, Pipeline provides a **solid, optimized solution for seeking scenarios** with reasonable performance trade-offs. diff --git a/src/docs/performance/PIPELINES_IMPLEMENTATION_STRATEGY.md b/src/docs/performance/PIPELINES_IMPLEMENTATION_STRATEGY.md deleted file mode 100644 index 8f284e40..00000000 --- a/src/docs/performance/PIPELINES_IMPLEMENTATION_STRATEGY.md +++ /dev/null @@ -1,362 +0,0 @@ -# Implementation Strategy: PositionAwareStreamReaderPipeline using System.IO.Pipelines - -## Overview -Create a new `PositionAwareStreamReaderPipeline` class that leverages `System.IO.Pipelines` for high-performance, asynchronous log file reading. This approach offers better memory management, backpressure handling, and throughput compared to the existing Channel-based implementation. - -## Core Advantages of Pipelines - -1. **Memory Efficiency**: Pipelines use a shared memory pool and reduce buffer copies through `ReadOnlySequence` -2. **Natural Backpressure**: Built-in flow control prevents producer from overwhelming consumer -3. **Zero-Copy Operations**: Can examine and process data without unnecessary allocations -4. **Better for Sequential I/O**: Optimized for streaming scenarios like log file reading -5. **Simplified State Management**: `PipeReader`/`PipeWriter` handle buffering complexity - -## Architecture Design - -### Class Structure -``` -PositionAwareStreamReaderPipeline : LogStreamReaderBase -├── PipeReader (reads from stream) -├── Decoder (converts bytes → chars) -├── Line Buffer (accumulates chars until newline) -├── Position Tracking (byte-accurate position) -└── Synchronization (ReadLine blocks on async pipeline) -``` - -### Key Components - -#### 1. **Pipeline Creation** -- Use `PipeReader.Create(stream)` for the source stream -- Configure `StreamPipeReaderOptions`: - - `bufferSize`: 64KB (aligned with existing implementation) - - `minimumReadSize`: 4KB (balance between syscalls and overhead) - - `useZeroByteReads`: false (for compatibility) - -#### 2. **Reading Pattern** -- Background task continuously reads from `PipeReader` -- Process data in `ReadOnlySequence` buffers -- Use `SequenceReader` for efficient scanning -- Advance reader position after processing each segment - -#### 3. **Line Parsing Strategy** - -**Two-Stage Processing:** -- **Stage 1: Byte → Char decoding** - - Use `Decoder.Convert()` with `ReadOnlySequence` - - May need to handle multi-byte sequences split across buffers - - Accumulate chars in rented char array buffer - -- **Stage 2: Char → Line extraction** - - Scan for newline delimiters (`\r`, `\n`, `\r\n`) - - Handle edge cases where newline spans buffer boundaries - - Track byte consumption for position accuracy - -#### 4. **Position Tracking** -- Maintain `_logicalPosition` (line start positions in bytes) -- Track `_bytesPendingInDecoder` (bytes consumed but not yet output as chars) -- Calculate positions using `SequencePosition` and `SequenceReader.Consumed` -- Account for encoding multi-byte characters - -#### 5. **Line Buffer Management** -``` -┌─────────────────────────────────────┐ -│ PipeReader Buffer (bytes) │ -│ ┌─────────────────────────────────┐ │ -│ │ ReadOnlySequence │ │ -│ └─────────────────────────────────┘ │ -└─────────────────────────────────────┘ - │ Decoder - ▼ -┌─────────────────────────────────────┐ -│ Char Accumulation Buffer │ -│ (rented from ArrayPool) │ -└─────────────────────────────────────┘ - │ Line Scanner - ▼ -┌─────────────────────────────────────┐ -│ Completed Line Queue │ -│ (for ReadLine() consumption) │ -└─────────────────────────────────────┘ -``` - -## Detailed Implementation Approach - -### 1. Constructor -```csharp -- Detect BOM/preamble (reuse existing logic) -- Create PipeReader with appropriate options -- Initialize Decoder from encoding -- Rent initial char buffer from ArrayPool -- Start background producer task -- Initialize line queue (BlockingCollection or SemaphoreSlim + Queue) -``` - -### 2. Background Producer Task -```csharp -while (!cancellationToken.IsCancellationRequested) -{ - ReadResult result = await pipeReader.ReadAsync(cancellationToken); - ReadOnlySequence buffer = result.Buffer; - - // Process buffer: - // 1. Decode bytes to chars - // 2. Scan for complete lines - // 3. Queue completed lines - // 4. Track positions - - pipeReader.AdvanceTo(consumed, examined); - - if (result.IsCompleted) - { - // Handle final partial line - // Signal EOF - break; - } -} -``` - -### 3. ReadLine() Implementation -```csharp -- Check if line is available in queue (TryDequeue) -- If not, wait on queue (with cancellation support) -- Return line and update public Position property -- Handle EOF (return null) -- Handle disposal/cancellation (return null) -``` - -### 4. Position Property Setter -```csharp -- Cancel existing pipeline -- Seek underlying stream to new position + preamble -- Reset PipeReader (may need to recreate) -- Clear line queue -- Reset decoder state -- Restart producer task -``` - -### 5. Handling Partial Lines at Buffer Boundaries - -**Problem**: Line may span multiple PipeReader buffers - -**Solution**: -- Track `examinePosition` vs `consumePosition` -- If no newline found in current buffer: - - `AdvanceTo(consumed: startPos, examined: endPos)` to request more data - - Keep unconsumed data in pipeline buffer -- Once newline found: - - `AdvanceTo(consumed: afterNewline, examined: afterNewline)` - -### 6. Handling Multi-byte Sequences at Buffer Boundaries - -**Problem**: UTF-8/Unicode char may be split across buffers - -**Solution**: -- `Decoder.Convert()` with `flush: false` maintains state -- Incomplete sequences remain in decoder internal state -- Next call to `Convert()` completes the character -- Track via `bytesUsed` return value for position accuracy - -### 7. Maximum Line Length Handling -```csharp -- Track chars accumulated for current line -- If exceeds _maximumLineLength: - - Truncate line to max length - - Mark as truncated - - Still consume all bytes until newline (for position accuracy) -``` - -### 8. Disposal Pattern -```csharp -Dispose() -├── Cancel producer task (CancellationTokenSource) -├── Await producer task completion -├── Complete PipeReader (pipeReader.Complete()) -├── Dispose underlying stream -├── Return all ArrayPool buffers -└── Clear line queue -``` - -## Synchronization Strategy - -### Challenge -- Pipelines are async-first -- `ReadLine()` must be synchronous -- Need to bridge async producer → sync consumer - -### Solution Options - -**Option A: BlockingCollection** -```csharp -- Producer writes completed lines to BlockingCollection -- ReadLine() calls Take() which blocks until available -- Simple, built-in blocking semantics -- Slightly higher overhead than manual queue -``` - -**Option B: Manual Queue + SemaphoreSlim** -```csharp -- Producer enqueues lines and signals SemaphoreSlim -- ReadLine() waits on semaphore, then dequeues -- Lower overhead, more control -- Requires careful synchronization -``` - -**Recommendation**: Option B for better performance and consistency with Channel implementation - -## Error Handling - -1. **Stream Read Errors**: Propagate to `ReadLine()` caller -2. **Cancellation**: Return null from `ReadLine()` -3. **Encoding Errors**: Use decoder fallback (same as existing implementations) -4. **Pipeline Exceptions**: Store exception, throw on next `ReadLine()` call - -## Position Accuracy Challenges - -### Challenge 1: Byte Position Calculation -- `ReadOnlySequence.Length` gives total buffered bytes -- Need to track how many bytes corresponded to each line -- Encoding may be variable-width (UTF-8) - -### Solution -```csharp -- Before decoding, note sequence position -- After decoding, calculate bytes consumed via SequenceReader.Consumed -- Track cumulative byte offset -- Each line stores its byte offset and byte length -``` - -### Challenge 2: Decoder Internal State -- Decoder maintains state for incomplete multi-byte sequences -- These bytes are "consumed" but not yet output - -### Solution -```csharp -- Track decoder state transitions -- Use GetBytes() to measure actual byte consumption -- Maintain "pending bytes" counter -``` - -## Testing Strategy - -1. **Unit Tests** - - Exact same test cases as existing PositionAwareStreamReader implementations - - Position accuracy verification - - Newline handling (\r, \n, \r\n) - - Encoding tests (UTF-8, UTF-16, etc.) - - Truncation behavior - - BOM detection - -2. **Performance Tests** - - Compare throughput vs Channel implementation - - Memory allocation profiling - - Large file handling (GB+ files) - - Seek performance - -3. **Integration Tests** - - Use with LogBuffer - - Concurrent position changes - - Cancellation scenarios - -## Performance Expectations - -### Expected Improvements (vs Channel) -| Metric | Improvement | -|--------|-------------| -| Throughput | +10-20% | -| Memory Allocations | -30-40% | -| GC Pressure | Reduced | -| Backpressure Handling | Improved | - -### Actual Results (ACHIEVED - 2025-01-XX) -| Metric | Small Files | Medium Files | Large Files | -|--------|-------------|--------------|-------------| -| **Throughput** | +141% (2.4x) | +498% (6x) | **+8,390% (85x)** ⭐ | -| **Memory** | -62% | -75% | **-98.4%** ⭐ | -| **GC Pressure** | Minimal Gen0/Gen1 | Significantly reduced | Nearly eliminated | -| **Scalability** | Good | Excellent | **Outstanding** | - -**Result**: Performance gains **far exceed expectations**, especially on large files! - -### Key Learnings - -1. **Pipelines Excel at Scale**: The larger the file, the more Pipeline shines - - Small: 2.4x faster - - Medium: 6x faster - - Large: **85x faster** 🚀 - -2. **Memory Efficiency Critical**: 98% memory reduction eliminates GC pressure - - Channel: 53 MB allocated - - Pipeline: 838 KB allocated - - **63x less memory** - -3. **ConcurrentQueue Was Key**: Replacing manual locking with ConcurrentQueue - - Eliminated lock contention - - Improved producer/consumer throughput - - Reduced context switching - -4. **System Reader Surprise**: System.StreamReader is fastest for small files - - But memory usage similar to Pipeline - - Pipeline better for medium/large files - - Consider adaptive selection - -## Integration with LogfileReader - -### Current Reader Selection Logic - -Looking at `LogfileReader.cs`, the reader selection is controlled by: - -```csharp -public enum Readers -{ - Legacy, - System, - Channel -} - -private ILogStreamReader CreateLogStreamReader(Stream stream, EncodingOptions encodingOptions) -{ - return _readerType switch - { - Readers.Legacy => new PositionAwareStreamReaderLegacy(stream, encodingOptions, _maximumLineLength), - Readers.System => new PositionAwareStreamReaderSystem(stream, encodingOptions, _maximumLineLength), - Readers.Channel => new PositionAwareStreamReaderChannel(stream, encodingOptions, _maximumLineLength), - _ => throw new ArgumentOutOfRangeException(nameof(Readers), _readerType, null) - }; -} -``` - -### Integration Steps - -1. **Add Pipeline Reader to Enum**: - ```csharp - public enum Readers - { - Legacy, - System, - Channel, - Pipeline // New option - } - ``` - -2. **Update Factory Method**: - ```csharp - private ILogStreamReader CreateLogStreamReader(Stream stream, EncodingOptions encodingOptions) - { - return _readerType switch - { - Readers.Legacy => new PositionAwareStreamReaderLegacy(stream, encodingOptions, _maximumLineLength), - Readers.System => new PositionAwareStreamReaderSystem(stream, encodingOptions, _maximumLineLength), - Readers.Channel => new PositionAwareStreamReaderChannel(stream, encodingOptions, _maximumLineLength), - Readers.Pipeline => new PositionAwareStreamReaderPipeline(stream, encodingOptions, _maximumLineLength), - _ => throw new ArgumentOutOfRangeException(nameof(Readers), _readerType, null) - }; - } - ``` - -3. **Configuration Support**: Add UI option in Settings dialog to select reader type - -4. **A/B Testing**: Allow runtime switching between readers for performance comparison - ---- - -**This strategy provides a comprehensive roadmap for implementing a high-performance, Pipeline-based stream reader while maintaining full compatibility with the existing LogExpert architecture.**