Googles appar
Huvudmeny

Post a Comment On: cbloom rants

"06-21-12 - Two Alternative Oodles"

16 Comments -

1 – 16 of 16
Blogger nereus said...

I've mentioned it before but c++11 lambdas make coroutines trivial and fast and code transparent. I've built a framework that implements "multitasking" among a bunch of tasks with yields using them and I think it's fabulous compared to how I learned them using protothreads.

ie

enum {YIELD, DONE};

std::function next_task_;

void task1_() {
next_task_ = [&]() { return task2_(); }; return YIELD; };

void task2_() { return DONE; }

public:
void init() {
next_task_ = [&]() { return task1_(); }
}

int invoke() { return next_task_(); }

Look ma, no threads!

EEK in the preview I see that the template angle brackets get elided. Just imagine the std::function correctly defined with an int return and void arg list.

June 22, 2012 at 1:34 PM

Blogger nereus said...

oops upon code review we note that some voids need to be ints...

gotta run.

June 22, 2012 at 1:38 PM

Blogger cbloom said...

I can't follow your code, but I believe you are smoking crack.

Lambdas let your write async code in the callback style, which is very ugly and undesirable.

You could perhaps use macros to make lambdas look like coroutines. It would be something like this :

lambda mycoroutine()
{
step1;
return []{

step2;
return []{

step3;
}}
}


Basically at each coroutine yield you are wrapping up the remainder of the coroutine as a lambda to get called when the coroutine resumes.

While that has the advantage over the switch hack that it allows local variable capture, it doesn't actually give you a true yield that can be done from inside function calls.

(I also don't see how you can yield inside branches or loops in a clean way with lambdas, which the switch method can; unless there's a solution that which I don't see, the lambda way is actually worse than the switch way).

Note that lack of variable capture in the switch hack is the very *least* of its problems. I don't see how lambdas help with the real major issue, which is yielding from inside subroutines.

June 22, 2012 at 3:06 PM

Blogger nereus said...

It's actually quite easy to write hierarchical state machines this way, and yield transparently from the lowest level. And at every level that [&] gives you not capture at the point of lambda definition, which is not what you want, but access to the full current state at lambda invocation.

I'm guessing by the "switch hack" you're referring to the macroized duffy device that lurks behind the c technique, and yes indeed things get ugly pretty quickly.

If you can't read that code (I should have spent ten minutes writing a nice class, you're right, but frankly I'm more interested in my own code), then you haven't thought it through yet. But you're a smart guy. You will, you will.

One of the reasons I decided to go this way, and for which I will be eternally grateful to you, was reading all your posts on threading. I've been writing complicated threaded code for 15 years, and I thought maybe there was something I wasn't getting, because anything interesting eventually gets too complicated for me to understand, as an enumerated state machine. But nope, given the complexity of the model, threads turn out to be the greatest job protection technique ever invented.

Cheers!

June 22, 2012 at 5:33 PM

Blogger cbloom said...

I think maybe we have a translation difficulty.

Anyhoo, to be clear there are two features of the idea #2 that I consider to be crucial :

1. It looks exactly like ordinary old synchronous code to the client.

2. You can flip a global switch and it *acts* exactly like old synchronous code, in the sense that I can step through it in the debugger and it just goes through line by line, not jumping all over the place in some crazy state machine / callback thing.


Sort of an aside, but I think the current fad of closures and continuations and functional programming is going to lead to a whole lot of horrible garbage code. Good code is linear and imperative.

June 22, 2012 at 5:51 PM

Blogger won3d said...

Well, I prefer begin/end. The type system helps more than some bare int, and manipulating iterator-likes feels better to me than having the separate induction variable for loops since there is an implicit synchronization of state there.

June 22, 2012 at 8:47 PM

Blogger won3d said...

I should say: for things like read/write, you're generally thinking "how big is this buffer" as opposed to arbitrary data ranges, so I'm not surprised (or particularly concerned) about this interface for I/O. But if your concern is about pointer-sised ints and stuff...

As an aside: favorite annoying thing for me lately has been the non-portability of size_t printf format strings.

June 22, 2012 at 8:49 PM

Blogger won3d said...

...and like an idiot I posted to the wrong thread. This has not been a good Friday for me.

June 22, 2012 at 8:50 PM

Blogger cbloom said...

" As an aside: favorite annoying thing for me lately has been the non-portability of size_t printf format strings. "

It's so fucking retarded, and it's one of those things where if you're a compiler writer and you don't make your stdlib match what the other major compilers do, you're just being a dick for no reason. (eg. gcc should obviously support the MSVC formats and vice versa).

This is also one of the little things that makes me dislike working in my RAD code. In my home code I just use autoprintf and I never have this problem.

June 23, 2012 at 9:52 AM

Blogger won3d said...

Yeah, there should be at least a flag or something for GCC. But clearly MSVC should support C99's %zu.

June 24, 2012 at 9:13 AM

Blogger jfb said...

Hi Charles,

What about just switching the stack pointer and backing up a couple registers?

I've been doing this with 0.5KB stacks on a microcontroller (each just a byte array). The switch routine consists of "push callee-saved registers and return pointer, set stack pointer to new stack, pop callee-saved registers, return to return pointer". Very fast and small, can still debug normally.

James

July 6, 2012 at 7:25 AM

Blogger cbloom said...

@jfb Yeah, I've thought about that; I even wrote a post about it

http://cbloomrants.blogspot.com/2011/03/03-12-11-c-coroutines-with-stack.html

then deleted it because all you have to do is change compiler settings and it stops working. And I have to support N compilers on M platforms and it becomes a total disaster.

It would be an awesome project if someone wants to write stack-saving coroutines as an open source library and support all the flavors.

July 6, 2012 at 8:03 AM

Blogger jfb said...

Hmm, I'm only doing it on one compiler (Clang). However it's only based on the platform calling convention so I don't see how a different compiler would break anything -- though I am doing stack switching, not the stack copying you described, so perhaps the issues are different.

When continuing a function I call the helper that switches to that function's stack is all. I can't see how the compiler would know the difference as long as the calling convention is followed?

July 6, 2012 at 8:26 AM

Blogger cbloom said...

BTW I think you may be right; I've been putting off replying until I have some more moments to think about it clearly, but that time keeps staying in the future.

July 15, 2012 at 6:18 PM

Blogger cbloom said...

@jfb - have looked into this a little bit. It's such a huge win for me that I think I will do it for Oodle 1.1 or 1.2 ; you're right that it's basically trivial, but with some minor annoyances :

1. You have to know how to make your compiler not do anything to fuck you up in the optimizer.

(god damn you C standards committee, stop fucking around with weirdo ideas like "concepts" and give me shit I actually need, like a standard way to say "don't optimize across this line" and a standard way to disable all optimization for a function, shit like that that real programmers need every day)

2. You have to know how to turn off any stack-checking or stack-extending type of stuff your compiler may add

I saw a good trick for working with systems that try to make sure your stack pointer is from the stack memory range. What you do is in your main thread you just alloca a huge array, and then use that to allocate your coroutine stacks from; that way they are valid stack pointers, they just happen to all be in the main thread stack.

3. On Windows you have to worry about SEH ; even if you don't intentionally use it, they check its integrity in system calls. It's just a few variables in the TEB/TIB to update, but probably best to just use Fibers on Windows and not try to beat the system.


I see that newer POSIX has this nice "context" stuff that should make this super easy in theory. Of course basically none of the platforms we work on has new POSIX.

(god damn you again C standards committee; give me a way to check for existence of a symbol, like ifexist(setcontext) )

July 20, 2012 at 12:54 PM

Blogger Jesse James Lactin said...

I know I'm 5 years LTTP, but I'm considering doing just that (implementing stackful coroutines). It's something I want as part of a standard library (I'm currently replacing libm) after using [set][get]context under Linux (which is fucked, because it's prototype is no longer standard C, let alone C++, so it's been deprecated).

July 22, 2017 at 10:41 AM

You can use some HTML tags, such as <b>, <i>, <a>

This blog does not allow anonymous comments.

Comment moderation has been enabled. All comments must be approved by the blog author.

You will be asked to sign in after submitting your comment.