Skip to content

Commit bcdaebb

Browse files
committed
Changed default buffer pool strategy by simpler linear search.
1 parent 2ebf73f commit bcdaebb

File tree

2 files changed

+90
-91
lines changed

2 files changed

+90
-91
lines changed

FlashCap.Core/BufferPool.cs

+78-73
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
//
88
////////////////////////////////////////////////////////////////////////////
99

10+
using System;
11+
using System.ComponentModel;
1012
using System.Diagnostics;
1113
using System.Linq;
1214
using System.Threading;
@@ -19,114 +21,117 @@ protected BufferPool()
1921
{
2022
}
2123

22-
public abstract byte[] Rent(int size, bool exactSize);
24+
public abstract byte[] Rent(int minimumSize);
2325
public abstract void Return(byte[] buffer);
2426
}
2527

2628
public sealed class DefaultBufferPool :
2729
BufferPool
2830
{
29-
// Imported from:
30-
// https://github.com/kekyo/GitReader/blob/main/GitReader.Core/Internal/BufferPool.cs
31-
32-
// Tried and tested, but simple strategies were the fastest.
33-
// Probably because each buffer table and lookup fragments on the CPU cache.
34-
35-
private const int bufferHolderLength = 13;
36-
37-
[DebuggerStepThrough]
38-
private sealed class BufferHolder
31+
private sealed class BufferElement
3932
{
40-
private readonly byte[]?[] buffers;
33+
private readonly int size;
34+
private readonly WeakReference wr;
4135

42-
public BufferHolder(int maxReservedBufferElements) =>
43-
this.buffers = new byte[maxReservedBufferElements][];
44-
45-
public byte[]? Rent(int size, bool exactSize)
36+
public BufferElement(byte[] buffer)
4637
{
47-
for (var index = 0; index < this.buffers.Length; index++)
48-
{
49-
var buffer = this.buffers[index];
50-
if (buffer != null && (exactSize ? (buffer.Length == size) : (buffer.Length >= size)))
51-
{
52-
if (Interlocked.CompareExchange(ref this.buffers[index], null, buffer) == buffer)
53-
{
54-
return buffer;
55-
}
56-
}
57-
}
58-
59-
return null;
38+
this.size = buffer.Length;
39+
this.wr = new WeakReference(buffer);
6040
}
6141

62-
public bool Return(byte[] buffer)
63-
{
64-
for (var index = 0; index < this.buffers.Length; index++)
65-
{
66-
if (this.buffers[index] == null)
67-
{
68-
if (Interlocked.CompareExchange(ref this.buffers[index], buffer, null) == null)
69-
{
70-
return true;
71-
}
72-
}
73-
}
42+
public bool IsAvailable =>
43+
this.wr.IsAlive;
7444

75-
// It was better to simply discard a buffer instance than the cost of extending the table.
76-
return false;
77-
}
45+
public bool IsAvailableAndFit(int minimumSize) =>
46+
this.wr.IsAlive && (minimumSize <= this.size);
47+
48+
public byte[]? ExtractBuffer() =>
49+
(byte[]?)this.wr.Target;
7850
}
7951

80-
private readonly BufferHolder[] bufferHolders;
81-
private int saved;
52+
private readonly BufferElement?[] bufferElements;
8253

8354
public DefaultBufferPool() : this(16)
8455
{
8556
}
8657

8758
public DefaultBufferPool(int maxReservedBufferElements) =>
88-
this.bufferHolders = Enumerable.Range(0, bufferHolderLength).
89-
Select(_ => new BufferHolder(maxReservedBufferElements)).
90-
ToArray();
59+
this.bufferElements = new BufferElement?[maxReservedBufferElements];
60+
61+
[EditorBrowsable(EditorBrowsableState.Advanced)]
62+
public int UnsafeAvailableCount =>
63+
this.bufferElements.Count(bufferHolder => bufferHolder?.IsAvailable ?? false);
9164

92-
public override byte[] Rent(int size, bool exactSize)
65+
public override byte[] Rent(int minimumSize)
9366
{
94-
// NOTE: Size is determined on a "less than" basis when not exact size,
95-
// which may result in placement in different buckets and missed opportunities for reuse.
96-
// This implementation is ignored penalties.
97-
int saved;
98-
var bufferHolder = this.bufferHolders[size % this.bufferHolders.Length];
99-
if (bufferHolder.Rent(size, exactSize) is { } b)
100-
{
101-
saved = Interlocked.Decrement(ref this.saved);
102-
}
103-
else
67+
for (var index = 0; index < this.bufferElements.Length; index++)
10468
{
105-
b = new byte[size];
106-
saved = this.saved;
69+
var bufferElement = this.bufferElements[index];
70+
71+
// First phase:
72+
// * Determined: size and exactSize
73+
// * NOT determined: Availability
74+
if (bufferElement?.IsAvailableAndFit(minimumSize) ?? false)
75+
{
76+
if (object.ReferenceEquals(
77+
Interlocked.CompareExchange(
78+
ref this.bufferElements[index],
79+
null,
80+
bufferElement),
81+
bufferElement) &&
82+
// Second phase
83+
// * Determined: size, exactSize and availability
84+
bufferElement.ExtractBuffer() is { } buffer)
85+
{
86+
#if DEBUG
87+
Debug.WriteLine($"DefaultBufferPool: Rend: Size={buffer.Length}/{minimumSize}, Index={index}");
88+
#endif
89+
return buffer;
90+
}
91+
}
92+
else if (!(bufferElement?.IsAvailable ?? true))
93+
{
94+
// Remove corrected element (and forgot).
95+
Interlocked.CompareExchange(
96+
ref this.bufferElements[index],
97+
null,
98+
bufferElement);
99+
}
107100
}
108101

109102
#if DEBUG
110-
Debug.WriteLine($"DefaultBufferPool: Rend: Size={b.Length}/{size}, ExactSize={exactSize}, Saved={saved}");
103+
Debug.WriteLine($"DefaultBufferPool: Created: Size={minimumSize}");
111104
#endif
112-
return b;
105+
return new byte[minimumSize];
113106
}
114107

115108
public override void Return(byte[] buffer)
116109
{
117-
int saved;
118-
var bufferHolder = this.bufferHolders[buffer.Length % this.bufferHolders.Length];
119-
if (bufferHolder.Return(buffer))
120-
{
121-
saved = Interlocked.Increment(ref this.saved);
122-
}
123-
else
110+
var newBufferElement = new BufferElement(buffer);
111+
112+
for (var index = 0; index < this.bufferElements.Length; index++)
124113
{
125-
saved = this.saved;
114+
var bufferElement = this.bufferElements[index];
115+
if (bufferElement == null || !bufferElement.IsAvailable)
116+
{
117+
if (object.ReferenceEquals(
118+
Interlocked.CompareExchange(
119+
ref this.bufferElements[index],
120+
newBufferElement,
121+
bufferElement),
122+
bufferElement))
123+
{
124+
#if DEBUG
125+
Debug.WriteLine($"DefaultBufferPool: Returned: Size={buffer.Length}, Index={index}");
126+
#endif
127+
return;
128+
}
129+
}
126130
}
127131

132+
// It was better to simply discard a buffer instance than the cost of extending the table.
128133
#if DEBUG
129-
Debug.WriteLine($"DefaultBufferPool: Returned: Size={buffer.Length}, Saved={saved}");
134+
Debug.WriteLine($"DefaultBufferPool: Discarded: Size={buffer.Length}");
130135
#endif
131136
}
132137
}

FlashCap.Core/PixelBuffer.cs

+12-18
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ internal unsafe void CopyIn(
5757
{
5858
this.bufferPool.Return(imageContainer);
5959
}
60-
imageContainer = this.bufferPool.Rent(totalSize, false);
60+
imageContainer = this.bufferPool.Rent(totalSize);
6161
this.imageContainer = imageContainer;
6262

6363
Debug.WriteLine($"Allocated: Size={totalSize}");
@@ -162,17 +162,14 @@ internal unsafe ArraySegment<byte> InternalExtractImage(BufferStrategies strateg
162162
sizeof(NativeMethods.BITMAPINFOHEADER) +
163163
sizeImage;
164164

165-
if (this.transcodedImageContainer == null ||
166-
this.transcodedImageContainer.Length != totalSize)
165+
if (transcodedImageContainer == null)
167166
{
168-
if (this.transcodedImageContainer != null)
169-
{
170-
this.bufferPool.Return(this.transcodedImageContainer);
171-
}
172-
this.transcodedImageContainer = this.bufferPool.Rent(totalSize, true);
167+
transcodedImageContainer = new byte[totalSize];
168+
this.transcodedImageContainer = transcodedImageContainer;
173169
}
170+
Debug.Assert(transcodedImageContainer.Length == totalSize);
174171

175-
fixed (byte* pTranscodedImageContainer = this.transcodedImageContainer)
172+
fixed (byte* pTranscodedImageContainer = transcodedImageContainer)
176173
{
177174
var pBfhTo = (NativeMethods.BITMAPFILEHEADER*)pTranscodedImageContainer;
178175
var pBihTo = (NativeMethods.BITMAPINFOHEADER*)(pBfhTo + 1);
@@ -211,11 +208,11 @@ internal unsafe ArraySegment<byte> InternalExtractImage(BufferStrategies strateg
211208
if (strategy == BufferStrategies.ForceReuse)
212209
{
213210
this.isValidTranscodedImage = true;
214-
return new ArraySegment<byte>(this.transcodedImageContainer);
211+
return new ArraySegment<byte>(transcodedImageContainer);
215212
}
216213
else
217214
{
218-
return new ArraySegment<byte>(this.transcodedImageContainer);
215+
return new ArraySegment<byte>(transcodedImageContainer);
219216
}
220217
}
221218
}
@@ -225,15 +222,12 @@ internal unsafe ArraySegment<byte> InternalExtractImage(BufferStrategies strateg
225222
{
226223
case BufferStrategies.ForceReuse:
227224
return new ArraySegment<byte>(imageContainer, 0, this.imageContainerSize);
228-
case BufferStrategies.CopyWhenDifferentSizeOrReuse:
229-
if (imageContainer.Length == this.imageContainerSize)
230-
{
231-
return new ArraySegment<byte>(imageContainer);
232-
}
233-
break;
225+
case BufferStrategies.CopyWhenDifferentSizeOrReuse when
226+
imageContainer.Length == this.imageContainerSize:
227+
return new ArraySegment<byte>(imageContainer);
234228
}
235229

236-
var copied = this.bufferPool.Rent(this.imageContainerSize, true);
230+
var copied = new byte[this.imageContainerSize];
237231
Array.Copy(imageContainer, copied, copied.Length);
238232

239233
Debug.WriteLine($"Copied: CurrentSize={imageContainer.Length}, Size={this.imageContainerSize}");

0 commit comments

Comments
 (0)