perf: keep parser state in bytes until materialization

This commit is contained in:
Joseph Doherty
2026-03-13 10:02:07 -04:00
parent ca7e12e753
commit 9fa2ba97b9

View File

@@ -37,7 +37,7 @@ public readonly struct ParsedCommand
public sealed class NatsParser public sealed class NatsParser
{ {
private static readonly byte[] CrLfBytes = "\r\n"u8.ToArray(); private static ReadOnlySpan<byte> CrLfBytes => "\r\n"u8;
private ILogger? _logger; private ILogger? _logger;
public ILogger? Logger { set => _logger = value; } public ILogger? Logger { set => _logger = value; }
@@ -85,7 +85,7 @@ public sealed class NatsParser
// Look for \r\n to find control line // Look for \r\n to find control line
var reader = new SequenceReader<byte>(buffer); var reader = new SequenceReader<byte>(buffer);
if (!reader.TryReadTo(out ReadOnlySequence<byte> line, CrLfBytes.AsSpan())) if (!reader.TryReadTo(out ReadOnlySequence<byte> line, CrLfBytes))
return false; return false;
// Control line size check // Control line size check
@@ -177,7 +177,7 @@ public sealed class NatsParser
case (byte)'c': case (byte)'c':
if (b1 == (byte)'o') // CONNECT if (b1 == (byte)'o') // CONNECT
{ {
command = ParseConnect(lineSpan); command = ParseConnect(line);
buffer = buffer.Slice(reader.Position); buffer = buffer.Slice(reader.Position);
TraceInOp("CONNECT"); TraceInOp("CONNECT");
return true; return true;
@@ -188,7 +188,7 @@ public sealed class NatsParser
case (byte)'i': case (byte)'i':
if (b1 == (byte)'n') // INFO if (b1 == (byte)'n') // INFO
{ {
command = ParseInfo(lineSpan); command = ParseInfo(line);
buffer = buffer.Slice(reader.Position); buffer = buffer.Slice(reader.Position);
TraceInOp("INFO"); TraceInOp("INFO");
return true; return true;
@@ -430,36 +430,34 @@ public sealed class NatsParser
}; };
} }
private static ParsedCommandView ParseConnect(Span<byte> line) private static ParsedCommandView ParseConnect(ReadOnlySequence<byte> line)
{ {
// CONNECT {json} -- find first space after command var reader = new SequenceReader<byte>(line);
int spaceIdx = line.IndexOf((byte)' '); if (!reader.TryAdvanceTo((byte)' ', advancePastDelimiter: true))
if (spaceIdx < 0)
throw new ProtocolViolationException("Invalid CONNECT"); throw new ProtocolViolationException("Invalid CONNECT");
var json = line[(spaceIdx + 1)..]; var json = line.Slice(reader.Position);
return new ParsedCommandView return new ParsedCommandView
{ {
Type = CommandType.Connect, Type = CommandType.Connect,
Operation = "CONNECT", Operation = "CONNECT",
Payload = new ReadOnlySequence<byte>(json.ToArray()), Payload = json,
MaxMessages = -1, MaxMessages = -1,
}; };
} }
private static ParsedCommandView ParseInfo(Span<byte> line) private static ParsedCommandView ParseInfo(ReadOnlySequence<byte> line)
{ {
// INFO {json} -- find first space after command var reader = new SequenceReader<byte>(line);
int spaceIdx = line.IndexOf((byte)' '); if (!reader.TryAdvanceTo((byte)' ', advancePastDelimiter: true))
if (spaceIdx < 0)
throw new ProtocolViolationException("Invalid INFO"); throw new ProtocolViolationException("Invalid INFO");
var json = line[(spaceIdx + 1)..]; var json = line.Slice(reader.Position);
return new ParsedCommandView return new ParsedCommandView
{ {
Type = CommandType.Info, Type = CommandType.Info,
Operation = "INFO", Operation = "INFO",
Payload = new ReadOnlySequence<byte>(json.ToArray()), Payload = json,
MaxMessages = -1, MaxMessages = -1,
}; };
} }