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:
BBasile 2018-06-13 11:20:31 +02:00 committed by The Dlang Bot
parent 7a2018538c
commit 894982e32d

View file

@ -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);