coroutines pool and memory issues with Lua

Started by
8 comments, last by wuh84 14 years, 1 month ago
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
Advertisement
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.
Thanks!

So, you were saying there is no way to end a coroutine, is there?

hao
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.

Fruny: Ftagn! Ia! Ia! std::time_put_byname! Mglui naflftagn std::codecvt eY'ha-nthlei!,char,mbstate_t>

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
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;endlocal func2 = function()	print("in test func 2");	 coroutine.yield();	return true;endlocal funcEnd = function()	return false;endlocal threadFunc = function(f)	while(f[1]()) do	endendlocal 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 loopdispatch[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
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
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
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 () endlocal 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;endlocal func2 = function(t)	print("in test func 2");	if (coroutine.yield()) then return true; end	return true;endlocal funcEnd = function()	return false;endlocal threadFunc = function(f,t)	while(f[1]()) do	endendlocal 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 againcoroutine.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]
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

This topic is closed to new replies.

Advertisement