Skip to content

Commit 5556482

Browse files
committed
Optimized memory allocations using stackalloc, spans and pooled arrays
1 parent c6ff107 commit 5556482

21 files changed

+1442
-373
lines changed

src/FirebirdSql.Data.FirebirdClient/Client/Managed/AuthBlock.cs

Lines changed: 154 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ sealed class AuthBlock
5252

5353
public bool WireCryptInitialized { get; private set; }
5454

55+
private const int STACKALLOC_LIMIT = 512;
56+
5557
public AuthBlock(GdsConnection connection, string user, string password, WireCryptOption wireCrypt)
5658
{
5759
_srp256 = new Srp256Client();
@@ -64,64 +66,164 @@ public AuthBlock(GdsConnection connection, string user, string password, WireCry
6466
WireCrypt = wireCrypt;
6567
}
6668

67-
public byte[] UserIdentificationData()
69+
public byte[] UserIdentificationData()
6870
{
6971
using (var result = new MemoryStream(256))
7072
{
71-
var userString = Environment.GetEnvironmentVariable("USERNAME") ?? Environment.GetEnvironmentVariable("USER") ?? string.Empty;
72-
var user = Encoding.UTF8.GetBytes(userString);
73-
result.WriteByte(IscCodes.CNCT_user);
74-
result.WriteByte((byte)user.Length);
75-
result.Write(user, 0, user.Length);
73+
{
74+
var userString = Environment.GetEnvironmentVariable("USERNAME") ?? Environment.GetEnvironmentVariable("USER") ?? string.Empty;
75+
var slen = Encoding.UTF8.GetByteCount(userString);
76+
byte[] rented = null;
77+
Span<byte> user = slen > STACKALLOC_LIMIT
78+
? (rented = System.Buffers.ArrayPool<byte>.Shared.Rent(slen)).AsSpan(0, slen)
79+
: stackalloc byte[slen];
80+
int real_len = Encoding.UTF8.GetBytes(userString, user);
81+
result.WriteByte(IscCodes.CNCT_user);
82+
result.WriteByte((byte)real_len);
83+
result.Write(user);
84+
if (rented != null)
85+
{
86+
System.Buffers.ArrayPool<byte>.Shared.Return(rented, clearArray: true);
87+
}
88+
}
7689

77-
var host = Encoding.UTF8.GetBytes(Dns.GetHostName());
78-
result.WriteByte(IscCodes.CNCT_host);
79-
result.WriteByte((byte)host.Length);
80-
result.Write(host, 0, host.Length);
90+
{
91+
var hostName = Dns.GetHostName();
92+
var slen = Encoding.UTF8.GetByteCount(hostName);
93+
byte[] rented = null;
94+
Span<byte> host = slen > STACKALLOC_LIMIT
95+
? (rented = System.Buffers.ArrayPool<byte>.Shared.Rent(slen)).AsSpan(0, slen)
96+
: stackalloc byte[slen];
97+
int real_len = Encoding.UTF8.GetBytes(hostName, host);
98+
result.WriteByte(IscCodes.CNCT_host);
99+
result.WriteByte((byte)real_len);
100+
result.Write(host);
101+
if (rented != null)
102+
{
103+
System.Buffers.ArrayPool<byte>.Shared.Return(rented, clearArray: true);
104+
}
105+
}
81106

82107
result.WriteByte(IscCodes.CNCT_user_verification);
83108
result.WriteByte(0);
84109

85110
if (!string.IsNullOrEmpty(User))
86111
{
87-
var login = Encoding.UTF8.GetBytes(User);
88-
result.WriteByte(IscCodes.CNCT_login);
89-
result.WriteByte((byte)login.Length);
90-
result.Write(login, 0, login.Length);
91-
92-
var pluginNameBytes = Encoding.UTF8.GetBytes(_srp256.Name);
93-
result.WriteByte(IscCodes.CNCT_plugin_name);
94-
result.WriteByte((byte)pluginNameBytes.Length);
95-
result.Write(pluginNameBytes, 0, pluginNameBytes.Length);
96-
var specificData = Encoding.UTF8.GetBytes(_srp256.PublicKeyHex);
97-
WriteMultiPartHelper(result, IscCodes.CNCT_specific_data, specificData);
98-
99-
var plugins = string.Join(",", new[] { _srp256.Name, _srp.Name });
100-
var pluginsBytes = Encoding.UTF8.GetBytes(plugins);
101-
result.WriteByte(IscCodes.CNCT_plugin_list);
102-
result.WriteByte((byte)pluginsBytes.Length);
103-
result.Write(pluginsBytes, 0, pluginsBytes.Length);
112+
{
113+
var slen = Encoding.UTF8.GetByteCount(User);
114+
byte[] rented = null;
115+
Span<byte> bytes = slen > STACKALLOC_LIMIT
116+
? (rented = System.Buffers.ArrayPool<byte>.Shared.Rent(slen)).AsSpan(0, slen)
117+
: stackalloc byte[slen];
118+
int real_len = Encoding.UTF8.GetBytes(User, bytes);
119+
result.WriteByte(IscCodes.CNCT_login);
120+
result.WriteByte((byte)real_len);
121+
result.Write(bytes);
122+
if (rented != null)
123+
{
124+
System.Buffers.ArrayPool<byte>.Shared.Return(rented, clearArray: true);
125+
}
126+
}
127+
{
128+
var slen = Encoding.UTF8.GetByteCount(_srp256.Name);
129+
byte[] rented = null;
130+
Span<byte> bytes = slen > STACKALLOC_LIMIT
131+
? (rented = System.Buffers.ArrayPool<byte>.Shared.Rent(slen)).AsSpan(0, slen)
132+
: stackalloc byte[slen];
133+
int real_len = Encoding.UTF8.GetBytes(_srp256.Name, bytes);
134+
result.WriteByte(IscCodes.CNCT_plugin_name);
135+
result.WriteByte((byte)real_len);
136+
result.Write(bytes[..real_len]);
137+
if (rented != null)
138+
{
139+
System.Buffers.ArrayPool<byte>.Shared.Return(rented, clearArray: true);
140+
}
141+
}
142+
{
143+
var slen = Encoding.UTF8.GetByteCount(_srp256.PublicKeyHex);
144+
byte[] rented = null;
145+
Span<byte> specificData = slen > STACKALLOC_LIMIT
146+
? (rented = System.Buffers.ArrayPool<byte>.Shared.Rent(slen)).AsSpan(0, slen)
147+
: stackalloc byte[slen];
148+
Encoding.UTF8.GetBytes(_srp256.PublicKeyHex.AsSpan(), specificData);
149+
WriteMultiPartHelper(result, IscCodes.CNCT_specific_data, specificData);
150+
if (rented != null)
151+
{
152+
System.Buffers.ArrayPool<byte>.Shared.Return(rented, clearArray: true);
153+
}
154+
}
155+
{
156+
var slen1 = Encoding.UTF8.GetByteCount(_srp256.Name);
157+
byte[] rented1 = null;
158+
Span<byte> bytes1 = slen1 > STACKALLOC_LIMIT
159+
? (rented1 = System.Buffers.ArrayPool<byte>.Shared.Rent(slen1)).AsSpan(0, slen1)
160+
: stackalloc byte[slen1];
161+
Span<byte> bytes2 = stackalloc byte[1];
162+
var slen3 = Encoding.UTF8.GetByteCount(_srp.Name);
163+
byte[] rented3 = null;
164+
Span<byte> bytes3 = slen3 > STACKALLOC_LIMIT
165+
? (rented3 = System.Buffers.ArrayPool<byte>.Shared.Rent(slen3)).AsSpan(0, slen3)
166+
: stackalloc byte[slen3];
167+
int l1 = Encoding.UTF8.GetBytes(_srp256.Name.AsSpan(), bytes1);
168+
int l2 = Encoding.UTF8.GetBytes(",".AsSpan(), bytes2);
169+
int l3 = Encoding.UTF8.GetBytes(_srp.Name.AsSpan(), bytes3);
170+
result.WriteByte(IscCodes.CNCT_plugin_list);
171+
result.WriteByte((byte)(l1+l2+l3));
172+
result.Write(bytes1);
173+
result.Write(bytes2);
174+
result.Write(bytes3);
175+
if (rented1 != null)
176+
{
177+
System.Buffers.ArrayPool<byte>.Shared.Return(rented1, clearArray: true);
178+
}
179+
if (rented3 != null)
180+
{
181+
System.Buffers.ArrayPool<byte>.Shared.Return(rented3, clearArray: true);
182+
}
183+
}
104184

105-
result.WriteByte(IscCodes.CNCT_client_crypt);
106-
result.WriteByte(4);
107-
result.Write(TypeEncoder.EncodeInt32(WireCryptOptionValue(WireCrypt)), 0, 4);
185+
{
186+
result.WriteByte(IscCodes.CNCT_client_crypt);
187+
result.WriteByte(4);
188+
Span<byte> bytes = stackalloc byte[4];
189+
if (!BitConverter.TryWriteBytes(bytes, IPAddress.NetworkToHostOrder(WireCryptOptionValue(WireCrypt))))
190+
{
191+
throw new InvalidOperationException("Failed to write wire crypt option bytes.");
192+
}
193+
result.Write(bytes);
194+
}
108195
}
109196
else
110197
{
111-
var pluginNameBytes = Encoding.UTF8.GetBytes(_sspi.Name);
198+
var slen = Encoding.UTF8.GetByteCount(_sspi.Name);
199+
byte[] rented = null;
200+
Span<byte> pluginNameBytes = slen > STACKALLOC_LIMIT
201+
? (rented = System.Buffers.ArrayPool<byte>.Shared.Rent(slen)).AsSpan(0, slen)
202+
: stackalloc byte[slen];
203+
int pluginNameLen = Encoding.UTF8.GetBytes(_sspi.Name.AsSpan(), pluginNameBytes);
112204
result.WriteByte(IscCodes.CNCT_plugin_name);
113-
result.WriteByte((byte)pluginNameBytes.Length);
114-
result.Write(pluginNameBytes, 0, pluginNameBytes.Length);
205+
result.WriteByte((byte)pluginNameLen);
206+
result.Write(pluginNameBytes[..pluginNameLen]);
207+
115208
var specificData = _sspi.InitializeClientSecurity();
116209
WriteMultiPartHelper(result, IscCodes.CNCT_specific_data, specificData);
117210

118211
result.WriteByte(IscCodes.CNCT_plugin_list);
119-
result.WriteByte((byte)pluginNameBytes.Length);
120-
result.Write(pluginNameBytes, 0, pluginNameBytes.Length);
212+
result.WriteByte((byte)pluginNameLen);
213+
result.Write(pluginNameBytes[..pluginNameLen]);
121214

122215
result.WriteByte(IscCodes.CNCT_client_crypt);
123216
result.WriteByte(4);
124-
result.Write(TypeEncoder.EncodeInt32(IscCodes.WIRE_CRYPT_DISABLED), 0, 4);
217+
Span<byte> wireCryptBytes = stackalloc byte[4];
218+
if (!BitConverter.TryWriteBytes(wireCryptBytes, IPAddress.NetworkToHostOrder(IscCodes.WIRE_CRYPT_DISABLED)))
219+
{
220+
throw new InvalidOperationException("Failed to write wire crypt disabled bytes.");
221+
}
222+
result.Write(wireCryptBytes);
223+
if (rented != null)
224+
{
225+
System.Buffers.ArrayPool<byte>.Shared.Return(rented, clearArray: true);
226+
}
125227
}
126228

127229
return result.ToArray();
@@ -309,7 +411,21 @@ void ReleaseAuth()
309411
_sspi = null;
310412
}
311413

312-
static void WriteMultiPartHelper(Stream stream, byte code, byte[] data)
414+
static void WriteMultiPartHelper(MemoryStream stream, byte code, byte[] data)
415+
{
416+
const int MaxLength = 255 - 1;
417+
var part = 0;
418+
for (var i = 0; i < data.Length; i += MaxLength) {
419+
stream.WriteByte(code);
420+
var length = Math.Min(data.Length - i, MaxLength);
421+
stream.WriteByte((byte)(length + 1));
422+
stream.WriteByte((byte)part);
423+
stream.Write(data, i, length);
424+
part++;
425+
}
426+
}
427+
428+
static void WriteMultiPartHelper(MemoryStream stream, byte code, ReadOnlySpan<byte> data)
313429
{
314430
const int MaxLength = 255 - 1;
315431
var part = 0;
@@ -319,7 +435,7 @@ static void WriteMultiPartHelper(Stream stream, byte code, byte[] data)
319435
var length = Math.Min(data.Length - i, MaxLength);
320436
stream.WriteByte((byte)(length + 1));
321437
stream.WriteByte((byte)part);
322-
stream.Write(data, i, length);
438+
stream.Write(data[i..(i+length)]);
323439
part++;
324440
}
325441
}

src/FirebirdSql.Data.FirebirdClient/Client/Managed/DataProviderStreamWrapper.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515

1616
//$Authors = Jiri Cincura ([email protected])
1717

18+
using System;
1819
using System.IO;
1920
using System.Runtime.CompilerServices;
21+
using System.Runtime.InteropServices;
2022
using System.Threading;
2123
using System.Threading.Tasks;
2224

@@ -36,12 +38,31 @@ public int Read(byte[] buffer, int offset, int count)
3638
{
3739
return _stream.Read(buffer, offset, count);
3840
}
41+
42+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
43+
public int Read(Span<byte> buffer, int offset, int count)
44+
{
45+
return _stream.Read(buffer[offset..(offset+count)]);
46+
}
47+
3948
[MethodImpl(MethodImplOptions.AggressiveInlining)]
4049
public ValueTask<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default)
4150
{
4251
return new ValueTask<int>(_stream.ReadAsync(buffer, offset, count, cancellationToken));
4352
}
4453

54+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
55+
public ValueTask<int> ReadAsync(Memory<byte> buffer, int offset, int count, CancellationToken cancellationToken = default)
56+
{
57+
return _stream.ReadAsync(buffer.Slice(offset, count), cancellationToken);
58+
}
59+
60+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
61+
public void Write(ReadOnlySpan<byte> buffer)
62+
{
63+
_stream.Write(buffer);
64+
}
65+
4566
[MethodImpl(MethodImplOptions.AggressiveInlining)]
4667
public void Write(byte[] buffer, int offset, int count)
4768
{
@@ -53,6 +74,12 @@ public ValueTask WriteAsync(byte[] buffer, int offset, int count, CancellationTo
5374
return new ValueTask(_stream.WriteAsync(buffer, offset, count, cancellationToken));
5475
}
5576

77+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
78+
public ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, int offset, int count, CancellationToken cancellationToken = default)
79+
{
80+
return _stream.WriteAsync(buffer.Slice(offset, count), cancellationToken);
81+
}
82+
5683
[MethodImpl(MethodImplOptions.AggressiveInlining)]
5784
public void Flush()
5885
{

src/FirebirdSql.Data.FirebirdClient/Client/Managed/FirebirdNetworkHandlingWrapper.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,33 @@ public int Read(byte[] buffer, int offset, int count)
8787
var dataLength = ReadFromInputBuffer(buffer, offset, count);
8888
return dataLength;
8989
}
90+
91+
public int Read(Span<byte> buffer, int offset, int count)
92+
{
93+
if (_inputBuffer.Count < count) {
94+
var readBuffer = _readBuffer;
95+
int read;
96+
try {
97+
read = _dataProvider.Read(readBuffer, 0, readBuffer.Length);
98+
}
99+
catch (IOException) {
100+
IOFailed = true;
101+
throw;
102+
}
103+
if (read != 0) {
104+
if (_decryptor != null) {
105+
_decryptor.ProcessBytes(readBuffer, 0, read, readBuffer, 0);
106+
}
107+
if (_decompressor != null) {
108+
read = HandleDecompression(readBuffer, read);
109+
readBuffer = _compressionBuffer;
110+
}
111+
WriteToInputBuffer(readBuffer, read);
112+
}
113+
}
114+
var dataLength = ReadFromInputBuffer(buffer, offset, count);
115+
return dataLength;
116+
}
90117
public async ValueTask<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default)
91118
{
92119
if (_inputBuffer.Count < count)
@@ -120,6 +147,24 @@ public async ValueTask<int> ReadAsync(byte[] buffer, int offset, int count, Canc
120147
return dataLength;
121148
}
122149

150+
public async ValueTask<int> ReadAsync(Memory<byte> buffer, int offset, int count, CancellationToken cancellationToken = default)
151+
{
152+
var rented = new byte[count];
153+
try
154+
{
155+
var read = await ReadAsync(rented, 0, count, cancellationToken).ConfigureAwait(false);
156+
rented.AsSpan(0, read).CopyTo(buffer.Span.Slice(offset, read));
157+
return read;
158+
}
159+
finally { }
160+
}
161+
162+
public void Write(ReadOnlySpan<byte> buffer)
163+
{
164+
foreach (var b in buffer)
165+
_outputBuffer.Enqueue(b);
166+
}
167+
123168
public void Write(byte[] buffer, int offset, int count)
124169
{
125170
for (var i = offset; i < count; i++)
@@ -132,6 +177,14 @@ public ValueTask WriteAsync(byte[] buffer, int offset, int count, CancellationTo
132177
return ValueTask.CompletedTask;
133178
}
134179

180+
public ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, int offset, int count, CancellationToken cancellationToken = default)
181+
{
182+
var span = buffer.Span.Slice(offset, count);
183+
foreach (var b in span)
184+
_outputBuffer.Enqueue(b);
185+
return ValueTask2.CompletedTask;
186+
}
187+
135188
public void Flush()
136189
{
137190
var buffer = _outputBuffer.ToArray();
@@ -206,6 +259,15 @@ int ReadFromInputBuffer(byte[] buffer, int offset, int count)
206259
return read;
207260
}
208261

262+
int ReadFromInputBuffer(Span<byte> buffer, int offset, int count)
263+
{
264+
var read = Math.Min(count, _inputBuffer.Count);
265+
for (var i = 0; i < read; i++) {
266+
buffer[offset+i] = _inputBuffer.Dequeue();
267+
}
268+
return read;
269+
}
270+
209271
void WriteToInputBuffer(byte[] data, int count)
210272
{
211273
for (var i = 0; i < count; i++)

0 commit comments

Comments
 (0)