
Comparing the Cost of Different Multiple-return Techniques in C - silentbicycle
http://spin.atomicobject.com/2013/12/23/c-return-multiple-values/
======
acqq
I don't see the description of the most obvious (to this old C programmer)
approach: if your function "returns multiple values" it probably should be
rewritten to operate on a struct. Once you recognize that, you'd probably
discover that you'll be able to use the same struct in more other functions,
and that you'd need just to pass a single pointer to that stuct around,
significantly reducing the overhead of a lot of parameter passing.

~~~
Arnor
I tend to agree with this approach and use it myself (in the rare instances
that I use C). My only complaint is that as the number of functions touching
the struct grow, maintainability gets harder. Can you share any
rules/strategies to mitigate this problem?

~~~
yoklov
You basically split it up in a similar way that you'd use in an OO language,
except you don't have inheritance and only have composition. Specifically, you
break apart the logically distinct portions of your structs and you make them
structs themselves. E.g. compare (trivial examples, but you get the idea).

    
    
        /* okay */
        struct monster {
          unsigned id;
          int x, y, hp;
          /* etc. */
        };
        /* better */
        struct point { int x, y; };
        struct monster {
          unsigned id;
          struct point pos;
          int hp;
        };
    

This also allows a form of inheritance. For example if you have

    
    
        struct bad_monster {
          struct monster base;
          int badness; /* your imagination is the limit. */
        };
    
        /* this is safe */
        struct bad_monster bad;
        struct monster *some_monster = (struct monster *)&bad; /* equivalently &bad.base */
        some_function_expecting_a_monster(some_monster);
        /* only safe if it was originally a struct bad_monster */
        struct bad_monster *bad_ptr = (struct bad_monster *)some_monster; 
    

Those are the ways I tend to break up structs when writing C code. Basically
by using lots of composition.

~~~
acqq
And the good aspect of C is the "flatness" of the structures: they are not
"objects" with the hidden content and associated functions managing it. It's a
way for the programmer to organize the data as efficiently as possible.

------
hepek
Reminded me of a very good book called "21st Century C"

[http://shop.oreilly.com/product/0636920025108.do](http://shop.oreilly.com/product/0636920025108.do)

~~~
eropple
Sorry, but I seriously have to disagree: 21st Century C is one of the worst
tech books I've had the misfortune to read. It's written by a butthurt C
devotee whose interest is as much in sneering at other languages as it is in
teaching something. It argues brace styles and text editors. It's loaded with
inaccuracies. It can't be bothered to mention when it's using a header file to
introduce a function. Typos abound. It seriously, without the slightest trace
of humor, includes a chapter called "Becoming a Better Typist." It's
just...crap. I stopped buying O'Reilly books after I couldn't get a refund for
this mess.

This article is by clueful professionals. That book is everything except that.

~~~
mitchty
Are you reading the same book as me, there is NO (FULL STOP) chapter named
becoming a better typist. There is a note with that title at page 100 (ebook
not sure on book), its also 4 short paragraphs long.

Is the entire book great? No, to be honest it touched too little on the C side
for me, and yeah the punk analogies was a bit annoying at times, but it did
get me to look critically at how I wrote c. Much like this blog I'm writing
for clarity first and not optimization.

What would you recommend for a c programming book instead of this book?

~~~
eropple
Sorry, yeah, it was a section, not a chapter. My bad. Its existence is still
stupid and is a good anecdote regarding the book's lack of focus.

I haven't yet found a decent C book. I've found many good C++ books and have
backported the lessons I've learned there into C, however.

~~~
mitchty
Agreed as to overall lack of focus, I was annoyed that until chapter 7 there
really wasn't much C to speak of, and the last chapters where he finally got
into interesting things, were far too short.

That said I did learn a few tricks, and generally appreciated the books
overall tone of you don't need to do crazy pointer stuff/malloc/realloc in C
in general. If that makes any sense, have you read the Embedded TDD in C book
by Pragmatic Programmers? [http://pragprog.com/book/jgade/test-driven-
development-for-e...](http://pragprog.com/book/jgade/test-driven-development-
for-embedded-c)

What C++ books would you recommend specifically? I find most to not really be
of much help in straight C given the differences of the two languages
(basically huge reliance on the standard library etc..).

------
justincormack
The other method is to change the calling convention and use a different ABI.
NetBSD syscalls have multiple return values in registers, although it is
little used (pipe returns two args, plus functions that return 64 bits on 32
bit platforms. If you were inventing a new ABI this could be nice. Multiple
return values are very idiomatic...

~~~
mikeash
From the ABI perspective, struct returns are multiple return values. ARM64
allows structs to be returned in registers if the struct members are suitable
for it.

~~~
justincormack
I didnt know that. Apparently amd64 lets you return two 64 bit values in
registers too, but no more.

~~~
mprovost
Two is still an incredibly handy feature. I would guess that the most common
case for returning multiple values is data and size, like all the functions
that return a string and the length of the string.

~~~
justincormack
Yes. Or the value. error style as used by node.js. forcing errors unto the
domain gets messy.

------
MichaelGG
When compiling at full optimization and link-time code gen optimization, will
the compiler and linker "get smart" about passing structs around? I'm
inexperienced here, but it always seemed odd that a struct with two words
can't just be shoved into registers (calling or returning).

Is there a reason the x86/64 ABIs don't specify such a way? Also, it seems
that the 128-bit and larger registers aren't used as often to pass values. For
something like a GUID, wouldn't they be a perfect fit? Or does the processor
optimize stack handling so that it's almost as cheap as registers?

~~~
pmjordan
The x86-64 ABI does specify for multi-word structs to be returned in
registers. If I remember correctly, it's up to 4 registers - rax, rdx and 2
others which I'll need to look up. This only happens if the struct fields on
their own would be returned in this way, so an array of 16 chars would not
typically be returned in 2 8-byte registers. To use SSE or AVX registers for
anything other than float or double scalars, you'll need to use the special
vector types.

I think x86 lets you return in eax and edx, but only if you're returning a
single 64-bit value, not a struct with 2 32-bits. Not 100% on all of this.

~~~
stephencanon
> I think x86 lets you return in eax and edx, but only if you're returning a
> single 64-bit value, not a struct with 2 32-bits.

Depends entirely on the platform ABI. Under the OS X calling conventions 2x32b
is returned in eax:edx (even if the 32b values are floats, which is annoying).

~~~
pmjordan
Ah, true. x86 ABIs are much less unified than x86-64 ones.

------
Peaker
IIRC the ARM ABI for C actually uses an implicit output parameter for a
returned struct - so it's identical to passing the output parameters manually.

------
j_baker
I'm curious: why are lower numbered benchmarks on the openbsd machine so much
slower than higher-numbered benchmarks? Clearly, the machine is older and less
powerful than the others, but I wouldn't expect that to alter the timing in
this way. Is this a result of the machine, the OS, or the version of the OS?

~~~
silentbicycle
I'm not sure, and digging into it would likely be a separate post. It's
probably due to OpenBSD having very different cache strategies from OSX or
Linux. It seemed to consistently do badly on the first run or two, then catch
up on on the rest. It's often recommended to throw away the first run in a set
of benchmarks, due to the added overhead of disk IO, but I wanted to retain
that because something interesting is going on there.

The graphs also show more overhead for returning a struct with additional
padding. OpenBSD's memory allocator favors security rather than speed, and the
extra size may have costs for buffer overruns felt elsewhere. I like testing C
code on OpenBSD -- it's almost as good as valgrind for detecting memory bugs,
but with significantly less overhead, since the memory checking is integrated
into the OS.

------
wyager
How feasible is it to change the C standard to allow for implicit struct
unpacking? So if I have `point foo(void)`, I can do `x, y, z = foo()`. It
seems like it would work well with the many ABIs that return small structs in
registers.

------
IsTom
Was the data allocated on stack or heap when using pointers? I would assume
the later as it's not stated explicitely and perhaps there is something to win
here for short-lived values.

------
Tloewald
Well, I guess the final point is almost worth it -- code for clarity.

~~~
mitchty
Would've liked to see the exact code to better gauge what clarity involves
specifically.

~~~
silentbicycle
I'm planning on posting a repo with the benchmarks soon.

~~~
mitchty
Cool beans. I saw this this morning and was excited.

I've been dealing with rewrites of a few internal utilities in C (was going to
use go, but the runtime has issues) and was going to start testing how struct
passing with errors in a struct for example would look in C.

I'll keep an eye out thanks!

~~~
silentbicycle
The code is up:
[https://github.com/atomicobject/multiple_return_in_c_benchma...](https://github.com/atomicobject/multiple_return_in_c_benchmarks)

------
theoh
Why not just write everything in Continuation Passing Style? (joke)

~~~
silentbicycle
That's why I mentioned compiling SML to C, actually. :) It's worth evaluating
costs for what generated C looks like when something else (SML, Chicken
Scheme, etc.) is compiling its constructs to C, just as much as it is for what
people will typically write.

------
andrewla
As with all benchmarks, without standard deviations, or, preferably, the
complete test data, it is meaningless to attempt to infer anything from this
data.

~~~
silentbicycle
Yeah, I'm planning on posting the benchmark source, but between Christmas, a
rather nasty ice storm, and a bad cold, I'm a bit behind. It will be up in a
few days.

