Skip to content

Commit 71e1954

Browse files
authored
Merge pull request #12 from schveiguy/fix11
Limits buffer growth to 2x, to amortize allocations, but prevent trying
2 parents 4327d8a + 549b107 commit 71e1954

File tree

2 files changed

+52
-21
lines changed

2 files changed

+52
-21
lines changed

source/iopipe/buffer.d

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,17 @@ unittest
5757
}
5858

5959
/**
60-
* Array based buffer manager. Uses custom allocator to get the data.
60+
* Array based buffer manager. Uses custom allocator to get the data. Limits
61+
* growth to doubling.
62+
*
63+
* Params:
64+
* T = The type of the elements the buffer manager will use
65+
* Allocator = The allocator to use for adding more elements
66+
* floorSize = The size that can be freely allocated before growth is restricted to 2x.
6167
*
6268
* Based on concept by Dmitry Olshansky
6369
*/
64-
struct BufferManager(T, Allocator = GCNoPointerAllocator)
70+
struct BufferManager(T, Allocator = GCNoPointerAllocator, size_t floorSize = 8192)
6571
{
6672
import std.experimental.allocator.common: stateSize;
6773
import std.experimental.allocator: IAllocator, theAllocator;
@@ -156,52 +162,54 @@ struct BufferManager(T, Allocator = GCNoPointerAllocator)
156162
size_t extend(size_t request)
157163
{
158164
import std.algorithm.mutation : copy;
159-
import std.algorithm.comparison : max;
165+
import std.algorithm.comparison : max, min;
160166
import std.traits : hasMember;
167+
168+
// check to see if we can "move" the data for free.
169+
auto validElems = valid - released;
170+
if(validElems == 0)
171+
valid = released = 0;
172+
161173
if(buffer.length - valid >= request)
162174
{
175+
// buffer has enough free space to accomodate.
163176
valid += request;
164177
return request;
165178
}
166179

167-
auto validElems = valid - released;
168180
if(validElems + request <= buffer.length)
169181
{
170-
// can just move the data
182+
// buffer has enough space if we move the data to the front.
171183
copy(buffer[released .. valid], buffer[0 .. validElems]);
172184
released = 0;
173185
valid = validElems + request;
174186
return request;
175187
}
176188

177189
// otherwise, we must allocate/extend a new buffer
178-
190+
// limit growth to 2x.
191+
immutable maxBufSize = max(buffer.length * 2, floorSize);
179192
static if(hasMember!(Allocator, "expand"))
180193
{
181194
// try expanding, no further copying required
182195
if(buffer.ptr)
183196
{
184197
void[] buftmp = cast(void[])buffer;
185-
if(allocator.expand(buftmp, (request - (buffer.length - valid)) * T.sizeof))
198+
auto reqSize = min(maxBufSize - buffer.length, request - (buffer.length - valid));
199+
if(allocator.expand(buftmp, reqSize * T.sizeof))
186200
{
201+
auto newElems = buffer.length - valid + reqSize;
202+
valid += newElems;
187203
buffer = cast(T[])buftmp;
188-
if(validElems == 0)
189-
{
190-
valid = request;
191-
released = 0;
192-
}
193-
else
194-
{
195-
valid += request;
196-
}
197-
return request;
204+
return newElems;
198205
}
199206
}
200207
}
201208

202209
// copy and allocate a new buffer
203210
auto oldLen = buffer.length;
204-
// grow by at least 1.4
211+
// grow by at least 1.4, but not more than maxBufSize
212+
request = min(request, maxBufSize - validElems);
205213
auto newLen = max(validElems + request, oldLen * 14 / 10, INITIAL_LENGTH);
206214
static if(hasMember!(Allocator, "goodAllocSize"))
207215
newLen = allocator.goodAllocSize(newLen * T.sizeof) / T.sizeof;
@@ -222,7 +230,7 @@ struct BufferManager(T, Allocator = GCNoPointerAllocator)
222230
return request;
223231
}
224232
private:
225-
enum size_t INITIAL_LENGTH = 128;
233+
enum size_t INITIAL_LENGTH = (128 < floorSize ? 128 : floorSize);
226234
T[] buffer;
227235
size_t valid;
228236
size_t released;

source/iopipe/bufpipe.d

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -451,11 +451,12 @@ unittest
451451
* you can process any more data.
452452
*
453453
* Params: chain = The pipe to work on.
454-
* elems = The number of elements to ensure are in the window.
454+
* elems = The number of elements to ensure are in the window. If
455+
* omitted, all elements are extended.
455456
* Returns: The resulting number of elements in the window. This may be less
456457
* than the requested elements if the pipe ran out of data.
457458
*/
458-
size_t ensureElems(Chain)(ref Chain chain, size_t elems)
459+
size_t ensureElems(Chain)(ref Chain chain, size_t elems = size_t.max)
459460
{
460461
while(chain.window.length < elems)
461462
{
@@ -476,6 +477,20 @@ unittest
476477
assert(p.window == "hello, world");
477478
}
478479

480+
// bug #11
481+
unittest
482+
{
483+
auto x = "hello, world".iosrc!((ref c, b) {
484+
if(b.length > c.window.length)
485+
b = b[0 .. c.window.length];
486+
b[] = c.window[0 .. b.length];
487+
c.release(b.length);
488+
return b.length; })
489+
.bufd!(char);
490+
auto elems = x.ensureElems();
491+
assert(elems == 12);
492+
}
493+
479494
private struct BufferedInputSource(T, Allocator, Source, size_t optimalReadSize)
480495
{
481496
Source dev;
@@ -726,6 +741,14 @@ unittest
726741
*
727742
* It is advisable to use a template or lambda that does not require a closure,
728743
* and is not a delegate from a struct that might move.
744+
*
745+
* The result is also alias-this'd to the chain, so it can be used as an iopipe also.
746+
*
747+
* Params:
748+
* fun = Function that accepts as its first parameter the input chain (of
749+
* type Chain), and as its second parameter, the buffer to read into. Only
750+
* buffer types that are supported are used.
751+
* c = The chain to read from
729752
*/
730753
template iosrc(alias fun, Chain)
731754
{

0 commit comments

Comments
 (0)