From f1aca261ae08c99f94ac4aa9babe9b33d0c2f5df Mon Sep 17 00:00:00 2001 From: Timerix Date: Mon, 14 Sep 2026 23:09:16 +0200 Subject: [PATCH] replaced ReadByte with 64kb buffer in parser --- ParadoxSaveParser.Lib/SaveParserEU4.cs | 124 +++++++++++++------------ 1 file changed, 65 insertions(+), 59 deletions(-) diff --git a/ParadoxSaveParser.Lib/SaveParserEU4.cs b/ParadoxSaveParser.Lib/SaveParserEU4.cs index d6e07c7..2c1f431 100644 --- a/ParadoxSaveParser.Lib/SaveParserEU4.cs +++ b/ParadoxSaveParser.Lib/SaveParserEU4.cs @@ -1,4 +1,4 @@ -global using System; +global using System; global using System.Collections.Generic; global using System.IO; global using System.Text; @@ -80,69 +80,75 @@ public class SaveParserEU4 return true; } - while (_saveFile.CanRead) + // Reading the save one byte at a time through Stream.ReadByte() costs a virtual call + // per byte, which dominated the parsing time. Bytes are pulled into this buffer + // instead, so the stream is touched once per 64 KB and the inner loop reads an array. + byte[] buffer = new byte[64 * 1024]; + int bufferLength; + while ((bufferLength = _saveFile.Read(buffer, 0, buffer.Length)) > 0) { - int c = _saveFile.ReadByte(); - column++; - switch (c) + for (int i = 0; i < bufferLength; i++) { - case -1: - if (TryCompleteStringToken()) - yield return strToken; - _stringBuilderPool.Return(strb); - yield break; - case '\"': - isQuoteOpen = !isQuoteOpen; - isStrInQuotes = true; - break; - case ' ': - case '\t': - case '\r': - if (TryCompleteStringToken()) - yield return strToken; - break; - case '\n': - if (TryCompleteStringToken()) - yield return strToken; - line++; - column = 0; - break; - case '=': - if (TryCompleteStringToken()) - yield return strToken; - yield return new Token - { - type = TokenType.Equals, - line = line, column = (short)column - }; - break; - case '{': - if (TryCompleteStringToken()) - yield return strToken; - yield return new Token - { - type = TokenType.BracketOpen, - line = line, column = (short)column - }; - break; - case '}': - if (TryCompleteStringToken()) - yield return strToken; - yield return new Token - { - type = TokenType.BracketClose, - line = line, column = (short)column - }; - break; - default: - // Skip control characters, which are invisible and causing frontend bugs. - // I dont know why there are so many of them in strings. - if (c >= 0x20) - strb.Append((char)c); - break; + int c = buffer[i]; + column++; + switch (c) + { + case '\"': + isQuoteOpen = !isQuoteOpen; + isStrInQuotes = true; + break; + case ' ': + case '\t': + case '\r': + if (TryCompleteStringToken()) + yield return strToken; + break; + case '\n': + if (TryCompleteStringToken()) + yield return strToken; + line++; + column = 0; + break; + case '=': + if (TryCompleteStringToken()) + yield return strToken; + yield return new Token + { + type = TokenType.Equals, + line = line, column = (short)column + }; + break; + case '{': + if (TryCompleteStringToken()) + yield return strToken; + yield return new Token + { + type = TokenType.BracketOpen, + line = line, column = (short)column + }; + break; + case '}': + if (TryCompleteStringToken()) + yield return strToken; + yield return new Token + { + type = TokenType.BracketClose, + line = line, column = (short)column + }; + break; + default: + // Skip control characters, which are invisible and causing frontend bugs. + // I dont know why there are so many of them in strings. + if (c >= 0x20) + strb.Append((char)c); + break; + } } } + // end of file: the last token may still be unterminated + if (TryCompleteStringToken()) + yield return strToken; _stringBuilderPool.Return(strb); }