Skip to content

Commit 1980739

Browse files
authored
Merge pull request connamara#976 from gbirchmeier/965-rebase-sb-pooling
(rebase connamara#965) Reusing StringBuilder with Object Pooling
2 parents c489a31 + 04dc032 commit 1980739

File tree

14 files changed

+174
-29
lines changed

14 files changed

+174
-29
lines changed

QuickFIXn/HttpServer.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Text;
77
using System.Threading;
88
using QuickFix.Fields.Converters;
9+
using QuickFix.ObjectPooling;
910

1011
namespace QuickFix;
1112

@@ -96,7 +97,8 @@ private void ConnectionThreadStart() {
9697
HttpListenerRequest request = context.Request;
9798
HttpListenerResponse response = context.Response;
9899

99-
StringBuilder sb = new StringBuilder();
100+
using PooledStringBuilder pooledSb = new PooledStringBuilder();
101+
StringBuilder sb = pooledSb.Builder;
100102
sb.AppendLine("<html>");
101103
sb.AppendLine(" <head>");
102104
sb.AppendLine(" <title>QuickFIX/n Engine Simple Web Interface</title>");

QuickFIXn/Logger/FileLog.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Diagnostics.CodeAnalysis;
44
using System.Threading;
55
using QuickFix.Fields.Converters;
6+
using QuickFix.ObjectPooling;
67
using QuickFix.Util;
78

89
namespace QuickFix.Logger;
@@ -43,8 +44,8 @@ public FileLog(string fileLogPath, SessionID sessionId)
4344

4445
public static string Prefix(SessionID sessionId)
4546
{
46-
System.Text.StringBuilder prefix = new System.Text.StringBuilder(sessionId.BeginString)
47-
.Append('-').Append(sessionId.SenderCompID);
47+
using PooledStringBuilder pooledSb = new PooledStringBuilder();
48+
System.Text.StringBuilder prefix = pooledSb.Builder.Append(sessionId.BeginString).Append('-').Append(sessionId.SenderCompID);
4849
if (SessionID.IsSet(sessionId.SenderSubID))
4950
prefix.Append('_').Append(sessionId.SenderSubID);
5051
if (SessionID.IsSet(sessionId.SenderLocationID))
@@ -149,7 +150,7 @@ public void Dispose()
149150
GC.SuppressFinalize(this);
150151
}
151152

152-
private bool _disposed = false;
153+
private bool _disposed;
153154
protected virtual void Dispose(bool disposing)
154155
{
155156
if (!disposing)

QuickFIXn/Logger/MelQuickFixLoggerFactory.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Microsoft.Extensions.Logging;
2+
using QuickFix.ObjectPooling;
23

34
namespace QuickFix.Logger;
45

@@ -30,7 +31,8 @@ public ILogger CreateSessionLogger(SessionID sessionId) =>
3031

3132
private static string GetCategoryFromSessionId(SessionID sessionId)
3233
{
33-
System.Text.StringBuilder category = new System.Text.StringBuilder(sessionId.BeginString)
34+
using PooledStringBuilder pooledSb = new PooledStringBuilder();
35+
System.Text.StringBuilder category = pooledSb.Builder.Append(sessionId.BeginString)
3436
.Append('-').Append(sessionId.SenderCompID);
3537
if (SessionID.IsSet(sessionId.SenderSubID))
3638
category.Append('_').Append(sessionId.SenderSubID);

QuickFIXn/Message/FieldMap.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Text;
55
using QuickFix.Fields;
66
using QuickFix.Fields.Converters;
7+
using QuickFix.ObjectPooling;
78

89
namespace QuickFix;
910

@@ -576,7 +577,8 @@ public int CalculateLength()
576577
/// <returns></returns>
577578
public virtual string CalculateString()
578579
{
579-
return CalculateString(new StringBuilder(), FieldOrder);
580+
using PooledStringBuilder pooledSb = new PooledStringBuilder();
581+
return CalculateString(pooledSb.Builder, FieldOrder);
580582
}
581583

582584
/// <summary>
@@ -593,11 +595,11 @@ public virtual string CalculateString(StringBuilder sb, int[] preFields)
593595
{
594596
if (IsSetField(preField))
595597
{
596-
sb.Append(preField + "=" + GetString(preField)).Append(Message.SOH);
598+
sb.Append(preField).Append('=').Append(GetString(preField)).Append(Message.SOH);
597599
if (groupCounterTags.Contains(preField))
598600
{
599-
List<Group> glist = _groups[preField];
600-
foreach (Group g in glist)
601+
List<Group> groupList = _groups[preField];
602+
foreach (Group g in groupList)
601603
sb.Append(g.CalculateString());
602604
}
603605
}
@@ -609,7 +611,7 @@ public virtual string CalculateString(StringBuilder sb, int[] preFields)
609611
continue;
610612
if (preFields.Contains(field.Tag))
611613
continue; //already did this one
612-
sb.Append($"{field.Tag}={field.ToString()}");
614+
sb.Append(field.Tag).Append('=').Append(field.ToString());
613615
sb.Append(Message.SOH);
614616
}
615617

QuickFIXn/Message/Group.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System;
1+
using QuickFix.ObjectPooling;
2+
using System;
23
using System.Text;
34

45
namespace QuickFix
@@ -66,8 +67,10 @@ public virtual Group Clone()
6667
/// Creates a FIX (ish) string representation of this FieldMap (does not change the object state)
6768
/// </summary>
6869
/// <returns></returns>
69-
public override string CalculateString() {
70-
return base.CalculateString(new StringBuilder(), FieldOrder ?? new[] { Delim });
70+
public override string CalculateString()
71+
{
72+
using PooledStringBuilder pooledSb = new PooledStringBuilder();
73+
return base.CalculateString(pooledSb.Builder, FieldOrder ?? new[] { Delim });
7174
}
7275

7376
public override string ToString()

QuickFIXn/Message/Header.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Text;
22
using QuickFix.Fields;
3+
using QuickFix.ObjectPooling;
34

45
namespace QuickFix {
56
public class Header : FieldMap {
@@ -17,8 +18,10 @@ public Header(Header src)
1718
/// Creates a FIX (ish) string representation of this FieldMap (does not change the object state)
1819
/// </summary>
1920
/// <returns></returns>
20-
public override string CalculateString() {
21-
return base.CalculateString(new StringBuilder(), HEADER_FIELD_ORDER);
21+
public override string CalculateString()
22+
{
23+
using PooledStringBuilder pooledSb = new PooledStringBuilder();
24+
return base.CalculateString(pooledSb.Builder, HEADER_FIELD_ORDER);
2225
}
2326

2427
/// <summary>

QuickFIXn/Message/Message.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using QuickFix.DataDictionary;
77
using DD = QuickFix.DataDictionary.DataDictionary;
8+
using QuickFix.ObjectPooling;
89

910
namespace QuickFix;
1011

@@ -853,18 +854,19 @@ protected int BodyLength()
853854

854855
private static string FieldMapToXml(DD? dd, FieldMap fields)
855856
{
856-
StringBuilder s = new StringBuilder();
857+
using PooledStringBuilder pooledSb = new PooledStringBuilder();
858+
StringBuilder s = pooledSb.Builder;
857859

858860
// fields
859861
foreach (var f in fields)
860862
{
861863
s.Append("<field ");
862864
if (dd is not null && dd.FieldsByTag.TryGetValue(f.Key, out var value))
863865
{
864-
s.Append("name=\"" + value.Name + "\" ");
866+
s.Append("name=\"").Append(value.Name).Append("\" ");
865867
}
866-
s.Append("number=\"" + f.Key + "\">");
867-
s.Append("<![CDATA[" + f.Value + "]]>");
868+
s.Append("number=\"").Append(f.Key).Append("\">");
869+
s.Append("<![CDATA[").Append(f.Value).Append("]]>");
868870
s.Append("</field>");
869871
}
870872
// now groups
@@ -968,7 +970,8 @@ private static StringBuilder FieldMapToJson(StringBuilder sb, DD? dd, FieldMap f
968970
/// <returns>an XML string</returns>
969971
public string ToXML(DD? dataDictionary = null)
970972
{
971-
StringBuilder s = new StringBuilder();
973+
using PooledStringBuilder pooledSb = new PooledStringBuilder();
974+
StringBuilder s = pooledSb.Builder;
972975
s.Append("<message>");
973976
s.Append("<header>");
974977
s.Append(FieldMapToXml(dataDictionary, Header));
@@ -1004,7 +1007,8 @@ public string ToJSON(DD? dataDictionary = null, bool convertEnumsToDescriptions
10041007
$"Must be non-null if '{nameof(convertEnumsToDescriptions)}' is true.");
10051008
}
10061009

1007-
StringBuilder sb = new StringBuilder().Append('{').Append("\"Header\":{");
1010+
using PooledStringBuilder pooledSb = new PooledStringBuilder();
1011+
StringBuilder sb = pooledSb.Builder.Append('{').Append("\"Header\":{");
10081012
FieldMapToJson(sb, dataDictionary, Header, convertEnumsToDescriptions, tagsToMask, maskText).Append("},\"Body\":{");
10091013
FieldMapToJson(sb, dataDictionary, this, convertEnumsToDescriptions, tagsToMask, maskText).Append("},\"Trailer\":{");
10101014
FieldMapToJson(sb, dataDictionary, Trailer, convertEnumsToDescriptions, tagsToMask, maskText).Append("}}");

QuickFIXn/Message/Trailer.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Text;
22
using QuickFix.Fields;
3+
using QuickFix.ObjectPooling;
34

45
namespace QuickFix {
56
public class Trailer : FieldMap {
@@ -17,8 +18,10 @@ public Trailer(Trailer src)
1718
/// Creates a FIX (ish) string representation of this FieldMap (does not change the object state)
1819
/// </summary>
1920
/// <returns></returns>
20-
public override string CalculateString() {
21-
return base.CalculateString(new StringBuilder(), TRAILER_FIELD_ORDER);
21+
public override string CalculateString()
22+
{
23+
using PooledStringBuilder pooledSb = new PooledStringBuilder();
24+
return base.CalculateString(pooledSb.Builder, TRAILER_FIELD_ORDER);
2225
}
2326

2427
/// <summary>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
using System.Collections.Concurrent;
3+
using System.Threading;
4+
5+
namespace QuickFix.ObjectPooling;
6+
7+
/// <summary>
8+
/// Default object pool implementation.
9+
/// </summary>
10+
internal sealed class DefaultObjectPool<T> : ObjectPool<T> where T : class
11+
{
12+
private T? _fastItem;
13+
private readonly ConcurrentQueue<T> _items = new();
14+
private int _numItems;
15+
16+
internal int Capacity { get; init; } = Environment.ProcessorCount * 2;
17+
18+
public override T? Get()
19+
{
20+
T? item = _fastItem;
21+
if (item == null || Interlocked.CompareExchange(ref _fastItem, null, item) != item)
22+
{
23+
if (_items.TryDequeue(out item))
24+
{
25+
Interlocked.Decrement(ref _numItems);
26+
}
27+
}
28+
29+
return item;
30+
}
31+
32+
public override bool Return(T item)
33+
{
34+
if (_fastItem != null || Interlocked.CompareExchange(ref _fastItem, item, null) != null)
35+
{
36+
if (Interlocked.Increment(ref _numItems) <= Capacity)
37+
{
38+
_items.Enqueue(item);
39+
return true;
40+
}
41+
42+
Interlocked.Decrement(ref _numItems);
43+
return false;
44+
}
45+
46+
return true;
47+
}
48+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
namespace QuickFix.ObjectPooling;
2+
3+
/// <summary>
4+
/// Object pooling base class.
5+
/// </summary>
6+
internal abstract class ObjectPool<T> where T : class
7+
{
8+
private static readonly DefaultObjectPool<T> s_shared = new();
9+
private static readonly DefaultObjectPool<T> s_bigShared = new() { Capacity = 128 };
10+
11+
/// <summary>
12+
/// Get an object from the pool. If no objects are available, return <see langword="null"/>.
13+
/// </summary>
14+
/// <returns>
15+
/// The object from the pool, or <see langword="null"/> if no objects are available.
16+
/// </returns>
17+
public abstract T? Get();
18+
19+
/// <summary>
20+
/// Return an object to the pool. If the pool is full, the object will not be returned.
21+
/// </summary>
22+
/// <returns>
23+
/// <see langword="true"/> if the object was returned to the pool, <see langword="false"/> if the pool is full.
24+
/// </returns>
25+
public abstract bool Return(T item);
26+
27+
/// <summary>
28+
/// A shared object pool with a default capacity.
29+
/// </summary>
30+
public static ObjectPool<T> Shared => s_shared;
31+
32+
/// <summary>
33+
/// A shared object pool with a larger capacity.
34+
/// </summary>
35+
public static ObjectPool<T> BigShared => s_bigShared;
36+
37+
/// <summary>
38+
/// Create a new object pool with the specified capacity.
39+
/// </summary>
40+
public static ObjectPool<T> Create(int capacity) => new DefaultObjectPool<T>() { Capacity = capacity };
41+
}

0 commit comments

Comments
 (0)