make the queue fully nogc and optimize it with a freelist (#161)
make the queue fully nogc and optimize it with a freelist merged-on-behalf-of: BBasile <BBasile@users.noreply.github.com>
This commit is contained in:
parent
7a2018538c
commit
894982e32d
|
@ -7,13 +7,7 @@
|
||||||
module dyaml.queue;
|
module dyaml.queue;
|
||||||
|
|
||||||
|
|
||||||
/// Queue collection.
|
import std.traits : hasMember, hasIndirections;
|
||||||
import core.stdc.stdlib;
|
|
||||||
import core.memory;
|
|
||||||
|
|
||||||
import std.container;
|
|
||||||
import std.traits;
|
|
||||||
|
|
||||||
|
|
||||||
package:
|
package:
|
||||||
|
|
||||||
|
@ -22,71 +16,128 @@ package:
|
||||||
/// Needed in some D:YAML code that needs a queue-like structure without too much
|
/// Needed in some D:YAML code that needs a queue-like structure without too much
|
||||||
/// reallocation that goes with an array.
|
/// reallocation that goes with an array.
|
||||||
///
|
///
|
||||||
/// This should be replaced once Phobos has a decent queue/linked list.
|
/// Allocations are non-GC and are damped by a free-list based on the nodes
|
||||||
///
|
/// that are removed. Note that elements lifetime must be managed
|
||||||
/// Uses manual allocation through malloc/free.
|
/// outside.
|
||||||
///
|
|
||||||
/// Also has some features uncommon for a queue, e.g. iteration. Couldn't bother with
|
|
||||||
/// implementing a range, as this is used only as a placeholder until Phobos gets a
|
|
||||||
/// decent replacement.
|
|
||||||
struct Queue(T)
|
struct Queue(T)
|
||||||
if(!hasMember!(T, "__dtor"))
|
if (!hasMember!(T, "__xdtor"))
|
||||||
{
|
{
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Linked list node containing one element and pointer to the next node.
|
|
||||||
|
// Linked list node containing one element and pointer to the next node.
|
||||||
struct Node
|
struct Node
|
||||||
{
|
{
|
||||||
T payload_;
|
T payload_;
|
||||||
Node* next_ = null;
|
Node* next_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start of the linked list - first element added in time (end of the queue).
|
// Start of the linked list - first element added in time (end of the queue).
|
||||||
Node* first_ = null;
|
Node* first_;
|
||||||
/// Last element of the linked list - last element added in time (start of the queue).
|
// Last element of the linked list - last element added in time (start of the queue).
|
||||||
Node* last_ = null;
|
Node* last_;
|
||||||
/// Cursor pointing to the current node in iteration.
|
// Cursor pointing to the current node in iteration.
|
||||||
Node* cursor_ = null;
|
Node* cursor_;
|
||||||
|
// free-list
|
||||||
|
Node* stock;
|
||||||
|
|
||||||
/// Length of the queue.
|
// Length of the queue.
|
||||||
size_t length_ = 0;
|
size_t length_;
|
||||||
|
|
||||||
|
// allocate a new node or recycle one from the stock.
|
||||||
|
Node* makeNewNode(T thePayload, Node* theNext = null) @trusted nothrow @nogc
|
||||||
|
{
|
||||||
|
import std.experimental.allocator : make;
|
||||||
|
import std.experimental.allocator.mallocator : Mallocator;
|
||||||
|
|
||||||
|
Node* result;
|
||||||
|
if (stock !is null)
|
||||||
|
{
|
||||||
|
result = stock;
|
||||||
|
stock = result.next_;
|
||||||
|
result.payload_ = thePayload;
|
||||||
|
result.next_ = theNext;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = Mallocator.instance.make!(Node)(thePayload, theNext);
|
||||||
|
// GC can dispose T managed member if it thinks they are no used...
|
||||||
|
static if (hasIndirections!T)
|
||||||
|
{
|
||||||
|
import core.memory : GC;
|
||||||
|
GC.addRange(result, Node.sizeof);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// free the stock of available free nodes.
|
||||||
|
void freeStock() @trusted @nogc nothrow
|
||||||
|
{
|
||||||
|
import std.experimental.allocator.mallocator : Mallocator;
|
||||||
|
|
||||||
|
while (stock !is null)
|
||||||
|
{
|
||||||
|
Node* toFree = stock;
|
||||||
|
stock = stock.next_;
|
||||||
|
static if (hasIndirections!T)
|
||||||
|
{
|
||||||
|
import core.memory : GC;
|
||||||
|
GC.removeRange(toFree);
|
||||||
|
}
|
||||||
|
Mallocator.instance.deallocate((cast(ubyte*) toFree)[0 .. Node.sizeof]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
@disable void opAssign(ref Queue);
|
@disable void opAssign(ref Queue);
|
||||||
@disable bool opEquals(ref Queue);
|
@disable bool opEquals(ref Queue);
|
||||||
@disable int opCmp(ref Queue);
|
@disable int opCmp(ref Queue);
|
||||||
|
|
||||||
|
@disable this(this);
|
||||||
|
|
||||||
|
~this() @safe nothrow @nogc
|
||||||
|
{
|
||||||
|
freeStock();
|
||||||
|
stock = first_;
|
||||||
|
freeStock();
|
||||||
|
}
|
||||||
|
|
||||||
/// Start iterating over the queue.
|
/// Start iterating over the queue.
|
||||||
void startIteration() @safe pure nothrow @nogc
|
void startIteration() @safe pure nothrow @nogc
|
||||||
{
|
{
|
||||||
cursor_ = first_;
|
cursor_ = first_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get next element in the queue.
|
/// Returns: The next element in the queue.
|
||||||
ref const(T) next() @safe pure nothrow @nogc
|
ref const(T) next() @safe pure nothrow @nogc
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(!empty);
|
assert(!empty);
|
||||||
assert(cursor_ !is null);
|
assert(cursor_ !is null);
|
||||||
}
|
}
|
||||||
body
|
do
|
||||||
{
|
{
|
||||||
const previous = cursor_;
|
const previous = cursor_;
|
||||||
cursor_ = cursor_.next_;
|
cursor_ = cursor_.next_;
|
||||||
return previous.payload_;
|
return previous.payload_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Are we done iterating?
|
/// Returns: true if itrating is not possible anymore, false otherwise.
|
||||||
bool iterationOver() @safe pure nothrow const @nogc
|
bool iterationOver() @safe pure nothrow const @nogc
|
||||||
{
|
{
|
||||||
return cursor_ is null;
|
return cursor_ is null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push new item to the queue.
|
/// Push a new item to the queue.
|
||||||
void push(T item) @safe nothrow
|
void push(T item) @nogc @safe nothrow
|
||||||
{
|
{
|
||||||
Node* newLast = new Node(item, null);
|
Node* newLast = makeNewNode(item);
|
||||||
if(last_ !is null) { last_.next_ = newLast; }
|
if (last_ !is null)
|
||||||
if(first_ is null) { first_ = newLast; }
|
last_.next_ = newLast;
|
||||||
|
if (first_ is null)
|
||||||
|
first_ = newLast;
|
||||||
last_ = newLast;
|
last_ = newLast;
|
||||||
++length_;
|
++length_;
|
||||||
}
|
}
|
||||||
|
@ -97,39 +148,53 @@ struct Queue(T)
|
||||||
{
|
{
|
||||||
assert(idx <= length_);
|
assert(idx <= length_);
|
||||||
}
|
}
|
||||||
body
|
do
|
||||||
{
|
{
|
||||||
if (idx == 0)
|
if (idx == 0)
|
||||||
{
|
{
|
||||||
first_ = new Node(item, first_);
|
first_ = makeNewNode(item, first_);
|
||||||
++length_;
|
++length_;
|
||||||
}
|
}
|
||||||
// Adding before last added element, so we can just push.
|
// Adding before last added element, so we can just push.
|
||||||
else if(idx == length_) { push(item); }
|
else if (idx == length_)
|
||||||
|
{
|
||||||
|
push(item);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Get the element before one we're inserting.
|
// Get the element before one we're inserting.
|
||||||
Node* current = first_;
|
Node* current = first_;
|
||||||
foreach(i; 1 .. idx) { current = current.next_; }
|
foreach (i; 1 .. idx)
|
||||||
|
current = current.next_;
|
||||||
|
|
||||||
|
assert(current);
|
||||||
// Insert a new node after current, and put current.next_ behind it.
|
// Insert a new node after current, and put current.next_ behind it.
|
||||||
current.next_ = new Node(item, current.next_);
|
current.next_ = makeNewNode(item, current.next_);
|
||||||
++length_;
|
++length_;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the next element in the queue and remove it.
|
/// Returns: The next element in the queue and remove it.
|
||||||
T pop() @safe nothrow
|
T pop() @safe nothrow
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(!empty, "Trying to pop an element from an empty queue");
|
assert(!empty, "Trying to pop an element from an empty queue");
|
||||||
}
|
}
|
||||||
body
|
do
|
||||||
{
|
{
|
||||||
T result = peek();
|
T result = peek();
|
||||||
Node* popped = first_;
|
|
||||||
|
Node* oldStock = stock;
|
||||||
|
Node* old = first_;
|
||||||
first_ = first_.next_;
|
first_ = first_.next_;
|
||||||
|
|
||||||
|
// start the stock from the popped element
|
||||||
|
stock = old;
|
||||||
|
old.next_ = null;
|
||||||
|
// add the existing "old" stock to the new first stock element
|
||||||
|
if (oldStock !is null)
|
||||||
|
stock.next_ = oldStock;
|
||||||
|
|
||||||
if (--length_ == 0)
|
if (--length_ == 0)
|
||||||
{
|
{
|
||||||
assert(first_ is null);
|
assert(first_ is null);
|
||||||
|
@ -139,31 +204,31 @@ struct Queue(T)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the next element in the queue.
|
/// Returns: The next element in the queue.
|
||||||
ref inout(T) peek() @safe pure nothrow inout @nogc
|
ref inout(T) peek() @safe pure nothrow inout @nogc
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(!empty, "Trying to peek at an element in an empty queue");
|
assert(!empty, "Trying to peek at an element in an empty queue");
|
||||||
}
|
}
|
||||||
body
|
do
|
||||||
{
|
{
|
||||||
return first_.payload_;
|
return first_.payload_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is the queue empty?
|
/// Returns: true of the queue empty, false otherwise.
|
||||||
bool empty() @safe pure nothrow const @nogc
|
bool empty() @safe pure nothrow const @nogc
|
||||||
{
|
{
|
||||||
return first_ is null;
|
return first_ is null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return number of elements in the queue.
|
/// Returns: The number of elements in the queue.
|
||||||
size_t length() @safe pure nothrow const @nogc
|
size_t length() @safe pure nothrow const @nogc
|
||||||
{
|
{
|
||||||
return length_;
|
return length_;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@safe unittest
|
@safe nothrow unittest
|
||||||
{
|
{
|
||||||
auto queue = Queue!int();
|
auto queue = Queue!int();
|
||||||
assert(queue.empty);
|
assert(queue.empty);
|
||||||
|
|
Loading…
Reference in a new issue