Skip to content

Instantly share code, notes, and snippets.

@Zshazz
Last active August 29, 2015 14:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Zshazz/fed0400674f034e09495 to your computer and use it in GitHub Desktop.
Save Zshazz/fed0400674f034e09495 to your computer and use it in GitHub Desktop.
Some helper items to make it easier to work with @nogc
import std.stdio;
// prevent many allocFails from being allocated because of
// template instantiations...
static immutable Error allocFail = new Error("Allocation failed");
mixin template SemiStackSink(size_t guessSize = 60)
{
import core.stdc.stdlib : malloc, realloc, free;
struct Data
{
char[guessSize] arr = void;
char[] arrSlice;
size_t capacity = guessSize;
bool memoryOwned = true;
/+ // untested
this(this)
{
debug
{
import std.stdio: writeln();
writeln("copy called");
}
if(arrSlice.length < guessSize)
{
// we can use our stack array to hold it
arr[0..arrSlice.length][] = arrSlice[];
arrSlice = arr[0..arrSlice.length];
}
else if(memoryOwned) // we need to malloc new memory...
{
void * newMem = core.stdc.stdlib.malloc(char.sizeof * capacity);
if(!newMem)
{
throw allocFail;
}
char[] newSlice = (cast(char*) newMem)[0..capacity];
newSlice[] = arrSlice[]; // copy old info
arrSlice = newSlice;
}
memoryOwned = true;
}
+/
this(this) @disable;
void opAssign(typeof(this) other) @disable;
~this()
{
// If arrSlice is just pointing to the stack, we don't need to free
// Also if we don't own the memory, obviously we shouldn't free it.
if(arrSlice.ptr is arr.ptr || !memoryOwned)
return;
core.stdc.stdlib.free(arrSlice.ptr);
}
/// Resets the buffer's pointer
void reset()
{
arrSlice = arrSlice[0..0];
}
/// Sink target for usage in @nogc environments
void sink(in char[] str) @nogc
{
foreach(ch; str)
{
if(arrSlice.length == capacity)
{
size_t newCapacity = capacity*2;
void * newMem;
if(arrSlice.ptr is arr.ptr)
newMem = core.stdc.stdlib.malloc(char.sizeof * newCapacity);
else
newMem = core.stdc.stdlib.realloc(arrSlice.ptr, char.sizeof * newCapacity);
if(!newMem)
{
throw allocFail;
}
char[] newSlice = (cast(char*) newMem)[0..capacity];
assert(newSlice.ptr is newMem);
if(arrSlice.ptr is arr.ptr)
{
newSlice[] = arrSlice[]; // we malloc'd, so we need to copy old bytes
}
arrSlice = newSlice;
capacity = newCapacity;
}
arrSlice = arrSlice.ptr[0..arrSlice.length+1];
arrSlice[$-1] = ch;
}
}
Data move()()
{
static assert(0, "No! Don't try to move this thing! You'll destroy _everything_!");
}
/+ // untested
void release()
{
memoryOwned = false;
}
+/
import std.range: isOutputRange, put;
void toString(OutputRange)(scope OutputRange r)
if(isOutputRange!(OutputRange, const char[]))
{
put(r, arrSlice);
}
// Regular toString doesn't exist because it couldn't be made @nogc
// without expecting the caller to manually free the memory since we'd
// really want to malloc a fresh string instead of reusing arrSlice
// (as reusing arrSlice would make it so that the string would be free'd
// at the end of the current instantiation of Data's scope, which would
// likely be unexpected for the caller.)
//
// Thus, it would either be useless (not @nogc) or error-prone. It's
// better to just support the toString(OutputRange) method and allow
// the caller to provide something they explicitly manage.
//
// Also, because this is mixed in, technically the caller can just
// grab and use the undocumented arrSlice and they'll understand that
// it's all their fault if they use it wrong and it falls apart.
}
auto makeContext() {
Data data;
// This looks weird. Logically, data.arr[0..0] should point to the stack
// in THIS scope, but it apparently is "smart" and is allocating data on
// the parent's stack because it's going to be moved there.
data.arrSlice = data.arr[0..0];
return data;
}
}
import std.range: isOutputRange, put;
import std.traits: isIntegral, isSigned;
void toString(T, OutputRange)(T integer, scope OutputRange r)
if(isIntegral!T && isOutputRange!(OutputRange, const char[]))
{
import std.algorithm: max;
import std.conv: to; // only used for CTFE...
enum minStr = std.conv.to!string(T.min);
enum maxStr = std.conv.to!string(T.max);
enum maxLen = std.algorithm.max( minStr.length, maxStr.length );
static if(isSigned!T)
{
// because -T.min can't be represented in a T,
// we just hardcode that one case.
if(integer == T.min)
{
put(r, minStr);
return;
}
bool negative = integer < 0;
}
char[maxLen] buffer = void;
size_t idx = buffer.length;
do
{
buffer[--idx] = cast(char)(integer%10 + '0');
}
while(integer /= 10);
static if(isSigned!T)
{
if(negative) buffer[--idx] = '-';
}
put(r, buffer[idx..$]);
}
unittest
{
import std.typetuple: TypeTuple;
mixin SemiStackSink!() MainSinkData;
auto inst = MainSinkData.makeContext();
foreach(numToTest; TypeTuple!(ulong.min, ulong.max, long.min, long.max, byte.min, ubyte.max))
{
inst.reset();
assert(inst.arr.ptr is inst.arrSlice.ptr);
() @nogc
{
inst.sink("Hello ");
numToTest.toString(&inst.sink);
}();
import std.conv: text;
assert(text("Hello ", numToTest) == inst.arrSlice);
}
inst.reset();
string hugeString;
import std.datetime;
StopWatch sw = AutoStart.yes;
//foreach(_; 0..200_000_000) // w00t, it works!
foreach(_; 0 .. 200)
hugeString ~= 'a';
sw.stop();
auto gcAllocTime = sw.peek();
//writeln("GC allocation took ", gcAllocTime.msecs, "ms");
sw.reset();
sw.start();
() @nogc
{
inst.sink(hugeString);
}();
sw.stop();
auto s3AllocTime = sw.peek();
//writeln("SemiStackSink took ", s3AllocTime.msecs, "ms");
// Well, this is kinda risky... but technically it should always
// be true (at least, I can't think of a way for it to not)
assert(s3AllocTime < gcAllocTime);
assert(inst.arrSlice == hugeString);
assert(inst.arr.ptr !is inst.arrSlice.ptr);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment