Shop OBEX P1 Docs P2 Docs Learn Events
libstdc++ overkill - Is there a better way around it? — Parallax Forums

libstdc++ overkill - Is there a better way around it?

denominatordenominator Posts: 242
edited 2012-02-08 07:39 in Propeller 1
In another thread, I mentioned that Dr_Acula‎'s PacMan would compile down to around 16K. That's true, but I used a "big hammer" to get it there. I wonder if there's a better way.

Here's the basic problem:
class MyClass
{
private:
    int x;

public:
    MyClass() { x = 0; }
    ~MyClass();
};

int main()
{
    MyClass* m1 = new MyClass();
    char* p = new char[1000];
}

Both the statements in main() use operator new(). This becomes obvious when you compile and link these with propeller-elf-gcc, as the linker reports missing operator new() and operator delete(), However, if you compile/link with propeller-elf-g++, everything is great unless you use LMM. In this case, you overflow the 32K LMM memory by 72396 bytes!!!

Wow, that's a lot of overhead for C++ memory management in a small processor like the Propeller! Looking at the linker map, almost all of the overhead has to do with exceptions. C++ did not have so much overhead back in the way old days, before C++ had exception handling, and people (myself included) wrote a lot of useful stuff with it.

I tried compiling with "-fno-exceptions" (I was really hoping that would work), and I also tried including "(std::nothrow)" after the new (this was a shot in the dark), but neither of these helped.

The way I finally worked around it is by adding this code (cloned from libstdc++) a the top of main:
#include <cstdlib>
#include <new>

using std::new_handler;
using std::malloc;
using std::bad_alloc;

extern new_handler __new_handler;

void *
operator new (std::size_t sz)
{
  void *p;

  /* malloc (0) is unpredictable; avoid it.  */
  if (sz == 0)
    sz = 1;
  p = (void *) malloc (sz);
  while (p == 0)
    {
      new_handler handler = __new_handler;
      if (! handler)
        std::abort();
      handler ();
      p = (void *) malloc (sz);
    }

  return p;
}
 
void*
operator new[] (std::size_t sz)
{
  return ::operator new(sz);
}

By doing this, I replaced the operator new/delete in the standard library with equivalents that do not use exceptions. In the case of the tiny test program above, that reduces the size from nearly 100K down to 4K, and it fits nicely in LMM.

Two other things to consider: I think it's going to be nice to use C++ instead of C for projects targeted for "old-school" LMM-only propeller systems, and that includes using "new" to make classes and allocate arrays. I think others will too, and therefore this is probably going to be a common/typical issue with G++ on the Propeller. It's true with other embedded systems; check out this thread from someone using G++ for an ARM processor: http://stackoverflow.com/questions/998248/how-can-i-stop-g-from-linking-unwanted-exception-handling-code; I didn't look hard, but I'm sure you can find more examples of other folks with the same issue on other systems.

The libstdc++ manual even speaks to it: http://gcc.gnu.org/onlinedocs/libstdc++/manual/using_exceptions.html#intro.using.exception.no

So the questions are:

1) Do the "library keepers" plan on creating an exception-free libstdc++ library?

2) Does/should the -fno-exceptions control the linker to tell it to link to the exception-free library instead of the standard library?

3) Should the documentation include information on how to completely avoid exceptions and exception overhead?

Comments

  • jazzedjazzed Posts: 11,803
    edited 2012-02-07 21:42
    Nice work :)
    And I thought C++ would only be good on Propeller without new/delete.
    So the questions are:

    1) Do the "library keepers" plan on creating an exception-free libstdc++ library?

    2) Does/should the -fno-exceptions control the linker to tell it to link to the exception-free library instead of the standard library?

    3) Should the documentation include information on how to completely avoid exceptions and exception overhead?

    1) There is no plan for it on first release.
    2) Not sure. What do ya think Eric?
    3) Your example is compelling. Would you like to start a propgcc wiki page on this?
  • Heater.Heater. Posts: 21,230
    edited 2012-02-08 04:07
    A while back I was running C++ programs on the Prop compiled with zpu-gcc and running under the Zog ZPU byte code interpreter.
    My conclusion is that C++ is very suitable for small embedded systems like the Prop.
    1) Using it as compiler for regular C code has no overheads and some benefits.
    2) Using templates can be helpful with no overheads over C.
    3) Using classes can be beneficial with no overheads over achieving the same effect with C.
    4) Just using statically declared objects in C++ enables us to use objects in the style of Spin.
    5) Exceptions are right out, which is OK by be anyway as they are the spawn of the devil anyway;)
    6) Getting into advanced things like inheritance, virtual functions etc may never be practically useful.
    7) new and delete really should not carry any over head over performing the same effect with malloc/free in C.

    There is another way to get around the new/delete problem. Use "placed new" as in the example below:
    #include <new>        // Needed for "placement new"
    #include <stdlib.h>
    
    class MyClass
    {
    private:
        int x;
    
    public:
        MyClass() { x = 0; }
        ~MyClass();
    };
    
    int main()
    {
       // Get space for MyClass on the stack like so...
       // char MyClassSpace[sizeof(MyClass)];
    
       // ...or on the heap like so...
       void* MyClassSpace = malloc(sizeof(MyClass));
    
       // Used "placed new" to get MyClass where we want it.
       MyClass* m1 = new(MyClassSpace) MyClass();              // m1 == MyClassSpace  after this
    }
    
  • ersmithersmith Posts: 6,099
    edited 2012-02-08 07:24
    Nice work, denominator! We do need some more exploration on how to get C++ code working in the confined space of hub memory, and yours is an important step.
    1) Do the "library keepers" plan on creating an exception-free libstdc++ library?

    2) Does/should the -fno-exceptions control the linker to tell it to link to the exception-free library instead of the standard library?

    3) Should the documentation include information on how to completely avoid exceptions and exception overhead?

    As Steve said, there's no immediate plans for a new libstdc++, mostly because we're running out of time before release. Obviously it's something that would be nice to have down the road. In theory it should be straightforward change g++ to link with different libraries if -fno-exception is given.

    Eric
  • denominatordenominator Posts: 242
    edited 2012-02-08 07:25
    Ugh! That's not quite what I was looking for.

    In my opinion, the "placement new" form looks overly wordy and gives more room for mistakes.

    The first form (on the stack) gives you no more than just declaring the object on the stack directly, and you have to remember to call it's destructor with m1->~MyClass();

    The second form suffers from giveing us twice as many pointers to be managed, and we have to remember exactly which to free; ostensibly you'd call the destructor via m1->~MyClass() then free the memory via "free(MyClassSpace)".

    On of the reasons why managed code has caught on is the fact that programmers cannot reliably manage memory, and you've doubled the problem. It seems to me the second form could be made tighter like this:
        MyClass* m1 = new(malloc(sizeof(MyClass))) MyClass();
        delete m1;
    

    This uses the standard 'operator delete' from the library, which works because it just calls free and it doesn't have any exception handling. This form is reasonable, and though just a bit wordy. However, I'm not sure that I'd ever use it given that I could just place the two "operator new()" functions in a separate object file and add that object during link.

    This leads to several questions:

    1) Assuming we all agree on exceptions being "devil spawn", at least in the context of a microcontroller, why aren't the default libraries going to be exception free?

    2) In my tests, inheritance and virtual function pointers work just fine, with very little overhead. I can perhaps see where you might say they "may never be practically useful" in that giant overbearing class structures like the whole C++ streaming I/O most likely won't be useful for Propeller programming. However, that's not to say the inheritance/virtual function features might not be useful in the context of providing a certain set of functionality that uses inheritance to tune it to different I/O mechanisms. For example, a library that tries to provide the generic I/O of a Bus Pirate could quite well use an inheritance structure to provide the different protocols (spi, i2c, one-wire) yet present a common interface to the library user. Or is there a bigger problem with inheritance/virtual functions that I'm missing?
  • denominatordenominator Posts: 242
    edited 2012-02-08 07:33
    ersmith wrote: »
    ... mostly because we're running out of time before release...

    Eric

    Got it.
  • Heater.Heater. Posts: 21,230
    edited 2012-02-08 07:39
    denominator,

    I agree that "placed new" is messy and it is not intended to be used the way I have suggested. However it can get you out of a hole if need be, like if normal new/delete insists on sucking in a huge lot of code.

    Actually in my C++ explorations for small codes on embedded devices I did not go as far as to look at the overheads associated with inheritance and vtables etc.

    I'll have to get home and remind myself how I did this with zpugcc and zog.
Sign In or Register to comment.