Sign in to follow this  
wuh84

coroutines pool and memory issues with Lua

Recommended Posts

Hi there, I use Lua as a scripting language in my code. I am using many coroutines here and there, create and abandon in a somewhat high frequency. Even though I haven't yet done a profiling to see if it is a bottleneck, to my sense, I would like to implement a coroutines pool to ease it at first glance. What I can't do is to terminate a coroutine and restart it. If it normally ends, I can hold the reference and push another function to make it run again, at least I think I can now but I havn't tried. But what if I don't want a yielded corouint any further resume, just let it go and reuse the coroutine, saying push another new function and make it run. It seems there is not a feasible way to do this. Because my code will heavily rely on scripts, I think it will be useful to think about it at the very beginning of the whole blueprint. I looked up into the code. each time lua creates a thread, it will alloc some space and there seems no memory management. I was thinking if I could do coroutines pool in another way that I myself handle memory management. I guess pool allocation will somehow help because the size of each allocation varies a little, allowing I supposed. Does this make sense? Thanks for any suggestions hao

Share this post


Link to post
Share on other sites
Certainly. You can either have your allocator recognize lua_State-sized allocations and delegate them to a pool allocator, or you can simply change luaE_newthread and luaE_freethread (both in lstate.c) to use your pool allocator directly. However, you don't need to do those things now. It's easy to change them later if you discover that your code is taking too long because of thread creation.

Share this post


Link to post
Share on other sites
Quote:
Original post by Endurion
Setting it to nil and/or removing all references to the coroutine should get rid of it (the GC will take care of it).

From inside the coroutine simply exit it.


Yes, you are right. But I would not like to nullify it totally and give it back to GC. I want to reuse it in order to alleviate the creation of each thread. So, once the thread yields, how could I clear up the stack and refresh it like a new spawn one?

thanks!

hao

Share this post


Link to post
Share on other sites
You can do this but it's not so obvious how. You need 2 levels of indirection since it seems you can only pass in the params to the seed function only once.

So you need to pass in a table instead of a raw function. This table will hold the dispatch function you want that coroutine to use for that moment. Here is a test impl.



local dispatch={};

local func1 = function()
print("in test func 1");
coroutine.yield();
return true;
end

local func2 = function()
print("in test func 2");
coroutine.yield();
return true;
end

local funcEnd = function()
return false;
end

local threadFunc = function(f)
while(f[1]()) do
end
end

local co = coroutine.create(threadFunc);

dispatch[1]=func1;
print(coroutine.status(co));
coroutine.resume(co,dispatch); --first call to resume we pass in the dispatch param which will be what initialize the threadFunc

--now after upon resuming co, we can leave out the dispatch param since its stored in the coroutines stack now.

dispatch[1]=func2;
print(coroutine.status(co));
coroutine.resume(co);

dispatch[1]=func1;
print(coroutine.status(co));
coroutine.resume(co);

--to kill the coroutine we pass in a terminate functor which will return false and break the threadFunc loop

dispatch[1]=funcEnd;
coroutine.resume(co);
print(coroutine.status(co));




Now you can setup 100's of these coroutines and farm out tasklets as you wish, using this system. Coroutines will wait in sleep state until they are needed and they won't be collected by the GC.

Good Luck!

-ddn

Share this post


Link to post
Share on other sites
Quote:
Original post by ddn3

Now you can setup 100's of these coroutines and farm out tasklets as you wish, using this system. Coroutines will wait in sleep state until they are needed and they won't be collected by the GC.

-ddn


It looks awesome! I still have a question about it. What if func1 yields on purpose and I won't let it resume but instead pass by to func2?

Thanks

hao

Share this post


Link to post
Share on other sites
Yeah the last response was misunderstanding your question, so this new one.

By changing the dispatch[1] param between calls you are swapping out active functors for the given coroutine. So after func1 yeild, you can set out the dispatch[1]=func2 and this will now bind co to func2 and it will only execute that function until you choose to terminate it or change its dispatch function once again.

[EDIT: yeah i understand your question now, you want to swap out between yeild, i just tried it on my side it doesn't swap until the functor returns. I'll see if its possible to swap it while between yields]

Good Luck!

-ddn

Share this post


Link to post
Share on other sites
Here is a possible solution if you really want the ability to swap out a coroutines execution stack midway through a task. This doesn't actually swap out the stack of the coroutine it branches it, it strikes a balance between usability and functionality.



local dispatch={};

local cont = function () end
local branch = function (f) dispatch[1]() end

local func1 = function(yield)
print("in test func 1a");
if (coroutine.yield()) then return true; end
print("in test func 1b");
if (coroutine.yield()) then return true; end
return true;
end

local func2 = function(t)
print("in test func 2");
if (coroutine.yield()) then return true; end
return true;
end

local funcEnd = function()
return false;
end

local threadFunc = function(f,t)
while(f[1]()) do
end
end

local co = coroutine.create(threadFunc);

dispatch[1]=func1;
print("call 1 "..coroutine.status(co));
coroutine.resume(co,dispatch);

dispatch[1]=func2; --swap now (between yield how to?)
print("call 2 "..coroutine.status(co));
--return true, as 2nd param to resume if we want to
-- escape the current execution stack and execute the dispatch function again
coroutine.resume(co,true);

print("call 3 "..coroutine.status(co));
coroutine.resume(co);

dispatch[1]=funcEnd;
coroutine.resume(co);
print("call 4 "..coroutine.status(co));







The idea is to use the data exchange which can occur between the coroutine and main thread upon resume/yield pairs. So by this convention you pass in a function to resume, which is automatically executed by the coroutine no questions asked.

here is output

all 1 suspended
in test func 1a
call 2 suspended
in test func 2 --test function 1 has been killed and function 2 is executing
call 3 suspended
in test func 2
call 4 dead

Good Luck!

[EDIT: there is a another way, more work on the user side but it fits your criterion better, returning an escape flag upon resume instead of a functor]

-ddn

[Edited by - ddn3 on February 21, 2010 2:55:49 PM]

Share this post


Link to post
Share on other sites
Quote:
Original post by ddn3
Yeah the last response was misunderstanding your question, so this new one.

By changing the dispatch[1] param between calls you are swapping out active functors for the given coroutine. So after func1 yeild, you can set out the dispatch[1]=func2 and this will now bind co to func2 and it will only execute that function until you choose to terminate it or change its dispatch function once again.

[EDIT: yeah i understand your question now, you want to swap out between yeild, i just tried it on my side it doesn't swap until the functor returns. I'll see if its possible to swap it while between yields]

Good Luck!

-ddn


Thanks for your help and hard try!

I think swap them in between could be a solution but I doubt if it could leverage creating a thread. Anyways, it seems there is no offical calls to terminate coroutines outside partly because they handle by g-c. So I guess the most method so far is just manage the memory allocation from the high above. It's pretty much a good idea since other allocation seems not handle very well, lua just calls realloc. Luckily, this optimization could delay to the very end of whole project!

Thanks ddn again

hao

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this