From c22020935a4dc191ca271114e64023f5f487c051 Mon Sep 17 00:00:00 2001 From: Rikard Pavelic Date: Fri, 11 May 2018 08:32:29 +0200 Subject: [PATCH] Optimizing C# Postgres driver Removing allocation from the driver --- .../Postgres/Npgsql/ByteBuffer.cs | 4 +- .../Postgres/Npgsql/NpgsqlAsciiRow.cs | 27 +++++++++++--- .../Postgres/Npgsql/NpgsqlConnector.cs | 22 +++++++++++ .../Postgres/Npgsql/NpgsqlRow.cs | 33 +++++++++-------- .../Postgres/Npgsql/NpgsqlRowDescription.cs | 23 +++++++++--- .../Postgres/Npgsql/State/NpgsqlState.cs | 37 ++++++------------- csharp/Server/Revenj.Http/Program.cs | 27 ++------------ 7 files changed, 95 insertions(+), 78 deletions(-) diff --git a/csharp/Core/Revenj.Core/DatabasePersistence/Postgres/Npgsql/ByteBuffer.cs b/csharp/Core/Revenj.Core/DatabasePersistence/Postgres/Npgsql/ByteBuffer.cs index d1f4f8c0..0181ab3c 100644 --- a/csharp/Core/Revenj.Core/DatabasePersistence/Postgres/Npgsql/ByteBuffer.cs +++ b/csharp/Core/Revenj.Core/DatabasePersistence/Postgres/Npgsql/ByteBuffer.cs @@ -1,5 +1,6 @@ using System; using System.Text; +using Revenj.Utility; namespace Revenj.DatabasePersistence.Postgres.Npgsql { @@ -10,6 +11,7 @@ internal class ByteBuffer private byte[] Buffer = new byte[32]; private int Position; private readonly char[] Chars = new char[256]; + private readonly StringCache Cache = new StringCache(10); public readonly byte[] Large = new byte[65536]; @@ -46,7 +48,7 @@ public string GetUtf8String() if (ch > 126) return UTF8.GetString(Buffer, 0, Position); Chars[i] = (char)ch; } - return new string(Chars, 0, Position); + return Cache.Get(Chars, Position); } return UTF8.GetString(Buffer, 0, Position); } diff --git a/csharp/Core/Revenj.Core/DatabasePersistence/Postgres/Npgsql/NpgsqlAsciiRow.cs b/csharp/Core/Revenj.Core/DatabasePersistence/Postgres/Npgsql/NpgsqlAsciiRow.cs index 60bf280c..c727d356 100644 --- a/csharp/Core/Revenj.Core/DatabasePersistence/Postgres/Npgsql/NpgsqlAsciiRow.cs +++ b/csharp/Core/Revenj.Core/DatabasePersistence/Postgres/Npgsql/NpgsqlAsciiRow.cs @@ -39,17 +39,22 @@ namespace Revenj.DatabasePersistence.Postgres.Npgsql /// internal sealed class StringRowReader : RowReader { - private readonly int _messageSize; + private int _messageSize; private int? _nextFieldSize = null; + private bool disposed = true; public StringRowReader(NpgsqlRowDescription rowDesc, Stream inputStream, byte[] buffer, ByteBuffer bytes) - : base(rowDesc, inputStream, buffer, bytes) + : base(rowDesc, inputStream, buffer, bytes) { } + + public int NextMessage(Stream inputStream) { - _messageSize = PGUtil.ReadInt32(inputStream, buffer); - if (PGUtil.ReadInt16(inputStream, buffer) != rowDesc.NumFields) - { + disposed = false; + _stream = inputStream; + _messageSize = PGUtil.ReadInt32(_stream, buffer); + if (PGUtil.ReadInt16(_stream, buffer) != _rowDesc.NumFields) throw new DataException(); - } + _nextFieldSize = null; + return _messageSize; } protected override object ReadNext() @@ -186,5 +191,15 @@ protected override int GetNextFieldCount() _nextFieldSize = null; return ret; } + + //not an actual dispose ;( + public override void Dispose() + { + if (disposed) return; + disposed = true; + base.Dispose(); + _messageSize = 0; + _nextFieldSize = null; + } } } \ No newline at end of file diff --git a/csharp/Core/Revenj.Core/DatabasePersistence/Postgres/Npgsql/NpgsqlConnector.cs b/csharp/Core/Revenj.Core/DatabasePersistence/Postgres/Npgsql/NpgsqlConnector.cs index 9c5db5ab..41946961 100644 --- a/csharp/Core/Revenj.Core/DatabasePersistence/Postgres/Npgsql/NpgsqlConnector.cs +++ b/csharp/Core/Revenj.Core/DatabasePersistence/Postgres/Npgsql/NpgsqlConnector.cs @@ -180,6 +180,9 @@ public NpgsqlConnector(NpgsqlConnectionStringBuilder ConnectionString, bool Pool _portalIndex = 0; _notificationThreadStopCount = 1; _notificationAutoResetEvent = new AutoResetEvent(true); + rowDescription = new NpgsqlRowDescription(CompatVersion); + rowReader = new StringRowReader(rowDescription, Stream, TmpBuffer, ArrayBuffer); + forwardReader = new ForwardsOnlyRow(rowReader); } public NpgsqlConnector(NpgsqlConnection Connection) @@ -558,6 +561,25 @@ internal NpgsqlBufferedStream Stream set { _stream = value; } } + private bool newDescription; + + private readonly NpgsqlRowDescription rowDescription; + internal NpgsqlRowDescription RowDescription() + { + newDescription = true; + return rowDescription.Process(_stream, OidToNameMapping, TmpBuffer, ArrayBuffer); + } + + private readonly StringRowReader rowReader; + private readonly ForwardsOnlyRow forwardReader; + internal ForwardsOnlyRow NextRow() + { + newDescription = false; + forwardReader.Reset(); + rowReader.NextMessage(_stream); + return forwardReader; + } + /// /// The physical connection socket to the backend. /// diff --git a/csharp/Core/Revenj.Core/DatabasePersistence/Postgres/Npgsql/NpgsqlRow.cs b/csharp/Core/Revenj.Core/DatabasePersistence/Postgres/Npgsql/NpgsqlRow.cs index 41781bda..3ab37740 100644 --- a/csharp/Core/Revenj.Core/DatabasePersistence/Postgres/Npgsql/NpgsqlRow.cs +++ b/csharp/Core/Revenj.Core/DatabasePersistence/Postgres/Npgsql/NpgsqlRow.cs @@ -27,7 +27,6 @@ // TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. using System; -using System.Collections.Generic; using System.IO; using System.Reflection; using System.Resources; @@ -52,12 +51,16 @@ internal abstract class NpgsqlRow : IStreamOwner internal sealed class CachingRow : NpgsqlRow { - private readonly List _data = new List(); - private readonly ForwardsOnlyRow _inner; + private readonly object[] _data; + private readonly int _numFields; public CachingRow(ForwardsOnlyRow fo) { - _inner = fo; + _numFields = fo.NumFields; + _data = new object[_numFields]; + for (int i = 0; i < _data.Length; i++) + _data[i] = fo[i]; + fo.Dispose(); } public override object this[Int32 index] @@ -68,18 +71,11 @@ public override object this[Int32 index] { throw new IndexOutOfRangeException("this[] index value"); } - while (_data.Count <= index) - { - _data.Add(_inner[_data.Count]); - } return _data[index]; } } - public override int NumFields - { - get { return _inner.NumFields; } - } + public override int NumFields { get { return _numFields; } } public override bool IsDBNull(int index) { @@ -112,7 +108,6 @@ public override long GetChars(int i, long fieldoffset, char[] buffer, int buffer public override void Dispose() { - _inner.Dispose(); } } @@ -194,6 +189,11 @@ public override bool IsDBNull(int index) return _reader.IsNextDBNull; } + public void Reset() + { + _lastIndex = -1; + } + public override void Dispose() { _reader.Dispose(); @@ -314,8 +314,8 @@ public override int DoSkip(int length) } protected static readonly Encoding UTF8Encoding = Encoding.UTF8; - private readonly NpgsqlRowDescription _rowDesc; - private readonly Stream _stream; + protected readonly NpgsqlRowDescription _rowDesc; + protected Stream _stream; private Streamer _streamer; private int _currentField = -1; protected readonly byte[] buffer; @@ -479,10 +479,11 @@ public void SkipCharsTo(long position) CurrentCharStreamer.SkipTo(position); } - public void Dispose() + public virtual void Dispose() { CurrentStreamer = null; Skip(_rowDesc.NumFields - _currentField - 1); + _currentField = -1; } } } \ No newline at end of file diff --git a/csharp/Core/Revenj.Core/DatabasePersistence/Postgres/Npgsql/NpgsqlRowDescription.cs b/csharp/Core/Revenj.Core/DatabasePersistence/Postgres/Npgsql/NpgsqlRowDescription.cs index 177504db..e87b4cdf 100644 --- a/csharp/Core/Revenj.Core/DatabasePersistence/Postgres/Npgsql/NpgsqlRowDescription.cs +++ b/csharp/Core/Revenj.Core/DatabasePersistence/Postgres/Npgsql/NpgsqlRowDescription.cs @@ -98,7 +98,8 @@ public FieldData(Stream stream, NpgsqlBackendTypeMapping typeMapping, byte[] buf } } - private readonly FieldData[] fields_data; + private FieldData[] fields_data; + private int fields_data_len; private Dictionary _field_name_index_table; private Dictionary _caseInsensitiveNameIndexTable; private readonly Version _compatVersion; @@ -106,14 +107,23 @@ public FieldData(Stream stream, NpgsqlBackendTypeMapping typeMapping, byte[] buf private readonly static Version KANA_FIX_VERSION = new Version(2, 0, 2, 1); private readonly static Version GET_ORDINAL_THROW_EXCEPTION = KANA_FIX_VERSION; - public NpgsqlRowDescription(Stream stream, NpgsqlBackendTypeMapping type_mapping, Version compatVersion, byte[] buffer, ByteBuffer queue) + public NpgsqlRowDescription(Version compatVersion) { _compatVersion = compatVersion; + fields_data = new FieldData[16]; + } + + public NpgsqlRowDescription Process(Stream stream, NpgsqlBackendTypeMapping type_mapping, byte[] buffer, ByteBuffer queue) + { PGUtil.EatStreamBytes(stream, 4); var num = PGUtil.ReadInt16(stream, buffer); - fields_data = new FieldData[num]; - for (int i = 0; i < fields_data.Length; i++) + fields_data_len = num; + if (num > fields_data.Length) fields_data = new FieldData[num]; + for (int i = 0; i < fields_data_len; i++) fields_data[i] = new FieldData(stream, type_mapping, buffer, queue); + _field_name_index_table = null; + _caseInsensitiveNameIndexTable = null; + return this; } public FieldData this[int index] @@ -123,11 +133,12 @@ public FieldData this[int index] public int NumFields { - get { return (Int16)fields_data.Length; } + get { return fields_data_len; } } private void InitDictionary() { + //TODO: rebuild the dict if (_field_name_index_table != null) return; if (_compatVersion < KANA_FIX_VERSION) @@ -140,7 +151,7 @@ private void InitDictionary() _field_name_index_table = new Dictionary(fields_data.Length, KanaWidthInsensitiveComparer.INSTANCE); _caseInsensitiveNameIndexTable = new Dictionary(fields_data.Length, KanaWidthCaseInsensitiveComparator.INSTANCE); } - for (int i = 0; i < fields_data.Length; i++) + for (int i = 0; i < fields_data_len; i++) { var fd = fields_data[i]; _field_name_index_table[fd.Name] = i; diff --git a/csharp/Core/Revenj.Core/DatabasePersistence/Postgres/Npgsql/State/NpgsqlState.cs b/csharp/Core/Revenj.Core/DatabasePersistence/Postgres/Npgsql/State/NpgsqlState.cs index 63fa28a5..3e7a4ad2 100644 --- a/csharp/Core/Revenj.Core/DatabasePersistence/Postgres/Npgsql/State/NpgsqlState.cs +++ b/csharp/Core/Revenj.Core/DatabasePersistence/Postgres/Npgsql/State/NpgsqlState.cs @@ -325,21 +325,6 @@ private static void IterateThroughAllResponses(IEnumerable /// This method is responsible to handle all protocol messages sent from the backend. /// It holds all the logic to do it. @@ -356,7 +341,7 @@ internal IEnumerable ProcessBackendResponsesEnum( // Process commandTimeout behavior. if ((context.Mediator.CommandTimeout > 0) && - (!CheckForContextSocketAvailability(context, SelectMode.SelectRead))) + (!CheckForContextSocketAvailability(context))) { // If timeout occurs when establishing the session with server then // throw an exception instead of trying to cancel query. This helps to prevent loop as CancelRequest will also try to stablish a connection and sends commands. @@ -430,7 +415,7 @@ internal IEnumerable ProcessExistingBackendResponses(Npgs /// true, if for context socket availability was checked, false otherwise. /// Context. /// Select mode. - internal bool CheckForContextSocketAvailability(NpgsqlConnector context, SelectMode selectMode) + internal bool CheckForContextSocketAvailability(NpgsqlConnector context) { /* Socket.Poll supports integer as microseconds parameter. * This limits the usable command timeout value @@ -438,7 +423,7 @@ internal bool CheckForContextSocketAvailability(NpgsqlConnector context, SelectM */ const int limitOfSeconds = 2147; - bool socketPoolResponse = false; + bool socketPoolResponse = context.Socket.Available > 0; int secondsToWait = context.Mediator.CommandTimeout; /* In order to bypass this limit, the availability of @@ -446,11 +431,11 @@ internal bool CheckForContextSocketAvailability(NpgsqlConnector context, SelectM */ while ((secondsToWait > limitOfSeconds) && (!socketPoolResponse)) { // - socketPoolResponse = context.Socket.Poll(1000000 * limitOfSeconds, selectMode); + socketPoolResponse = context.Socket.Poll(1000000 * limitOfSeconds, SelectMode.SelectRead); secondsToWait -= limitOfSeconds; } - return socketPoolResponse || context.Socket.Poll(1000000 * secondsToWait, selectMode); + return socketPoolResponse || context.Socket.Poll(1000000 * secondsToWait, SelectMode.SelectRead); } private enum BackEndMessageCode @@ -509,13 +494,11 @@ private enum AuthenticationRequestType protected IEnumerable ProcessBackendResponses_Ver_3(NpgsqlConnector context) { - using (new ContextResetter(context)) + try { Stream stream = context.Stream; NpgsqlMediator mediator = context.Mediator; - NpgsqlRowDescription lastRowDescription = null; - var buffer = context.TmpBuffer; var queue = context.ArrayBuffer; List errors = null; @@ -660,7 +643,7 @@ protected IEnumerable ProcessBackendResponses_Ver_3(Npgsq } break; case BackEndMessageCode.RowDescription: - yield return lastRowDescription = new NpgsqlRowDescription(stream, context.OidToNameMapping, context.CompatVersion, buffer, queue); + yield return context.RowDescription(); break; case BackEndMessageCode.ParameterDescription: @@ -676,7 +659,7 @@ protected IEnumerable ProcessBackendResponses_Ver_3(Npgsq break; case BackEndMessageCode.DataRow: - yield return new ForwardsOnlyRow(new StringRowReader(lastRowDescription, stream, buffer, queue)); + yield return context.NextRow(); break; case BackEndMessageCode.ReadyForQuery: @@ -810,6 +793,10 @@ protected IEnumerable ProcessBackendResponses_Ver_3(Npgsq } } } + finally + { + context.RequireReadyForQuery = true; + } } diff --git a/csharp/Server/Revenj.Http/Program.cs b/csharp/Server/Revenj.Http/Program.cs index f7b9674e..6100140a 100644 --- a/csharp/Server/Revenj.Http/Program.cs +++ b/csharp/Server/Revenj.Http/Program.cs @@ -30,37 +30,16 @@ static void Main(string[] args) They can't be changed in runtime via command line arguments. Specify arguments in config file using ", ex); } - /*Platform.Container container; - if (!Enum.TryParse(ConfigurationManager.AppSettings["Revenj.Container"], out container)) - container = Platform.Container.Autofac;*/ Console.WriteLine("Starting server"); if (httpServer == "Socket" || httpServer == "Revenj") { - var server = Platform.Start();//container); + var server = Platform.Start(); server.Run(); } else { - try - { - var server = Platform.Start();//container); - server.Run(); - } - catch (Exception ex) - { - var tle = ex.InnerException as TypeLoadException; - if (tle != null && tle.TypeName == "System.Net.HttpListener") - { - throw new TypeLoadException(@"Unable to load HttpListener. -Newer Mono versions (4.2+) have incompatible Mono.Security. -Either delete Mono.Security.dll from the Revenj folder so it can use Mono default one, -use an older Mono version (pre 4.2) or use Revenj builtin web server. -To run Revenj builtin web server add Revenj.HttpServer=Revenj to command line or add - -to ", ex.InnerException); - } - throw; - } + var server = Platform.Start(); + server.Run(); } } }