January 21, 2008 A lesson on structs, recursion, stack size and pointers. | ||||
---|---|---|---|---|
| ||||
I suppose this isn't limited to D, but I thought I'd write it here anyways, 'cause I just figured this out while writing Walnut. Since I'm writing a scripting engine, most of the methods have to have the same function signature so they can be interchangeable (don't ask, I don't know why). We have a struct, Value, which is 16 bytes on a 32-bit machine. So the function signature I went with originally was this: static Value myFunc(Value self, Value cc, Value[] arguments ...){} Recently while reverse engineering the resulting code in IDA Pro, I figured out that this was spitting out a rather atrocious 48-byte stack signature for each call, and to optimize things, D was performing a rep movsd to get the parameters accross; and because this wasn't trivial code, it had separated it out and was calling this algorithm from each of my methods to copy the arguments accross. My first thought was to use SSE2 registers to pass the Value structs in, but I realized that for recursion, you still need to put it on the stack, so the idea doesn't help. So.... static Value* myFunc(ref Value self, ref Value cc, arguments[] ...){} apparently consumes far less. The problem I'm now facing is that since I'm always returning pointers I need to allocate Value's somewhere other than the constructor's call stack. My first thought was to write a block allocator, since everything is being handled by Value struct. I thought that doing so could take a great deal of load off the GC. Bad idea. I figured out that because it was originally on the stack, the GC wasn't being stressed by it anyways. When I was passing by Value, as soon as it went out of scope, it was automatically crushed as a local variable. So now, having a pointer, I needed to allocate it somewhere outside the stack, and was facing the memory management problem I think I was originally trying to avoid at some point when I had actually thought this through. I need Values to have local scope to the function calling the constructor on it; but that function often doesn't exist at compile time. So I kind of wanted to fake a closure? The real answer is that D's GC already cleans up if nothing points to it. So, the pointer on the stack gets cleared, and later the GC comes along and digests the Value struct itself. |
January 21, 2008 Re: A lesson on structs, recursion, stack size and pointers. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Dan | "Dan" <murpsoft@hotmail.com> wrote in message news:fn0pfk$kup$1@digitalmars.com... > > static Value* myFunc(ref Value self, ref Value cc, arguments[] ...){} > Your other issues aside, have you considered using/passing some kind of context which would hold all these values instead? In MiniD (which uses an API heavily inspired by Lua), native functions take two params -- a class reference to the current "thread", and a number representing the number of params passed to this function. The function then asks the thread object for any params it needs, since they're all on the thread's stack anyway. To return values, the native function pushes all the values it wants to return on the thread's stack and returns a number indicating how many values to return (functions can return any number of values). I don't know anything about the internals of Walnut, and therefore if this scheme would be possible for you, but it sure is faster to pass a pointer and an int. It also solves the return value allocation issue. |
Copyright © 1999-2021 by the D Language Foundation