Shop OBEX P1 Docs P2 Docs Learn Events
Pure Virtual Functions Cause Instant Code Bloat — Parallax Forums

Pure Virtual Functions Cause Instant Code Bloat

photomankcphotomankc Posts: 943
edited 2013-11-22 21:16 in Propeller 1
I was curious what it is about about pure virtual declarations that will cause hub overflow in all but contrived examples? I have several projects with class hierarchies that look like this:
 InterfaceClass (Defines the minimum set of methods you can expect from all derived objects)
          |
          | ------------------------|
      Derived1                   Derived2

Now in most cases the functions in InterfaceClass are just stubs. There can be no implementation because we have no idea how to do that yet so they ought to be declared as "pure virtual" and only defined in Derived1 and Derived2. However, in every real project so far I have not been able to do that. Instead I have to put in either an empty definition { } or return a dummy variable { return -1;}. If I put the '=0' at the end of the declaration then my project gets the ".text will not fit in hub" error message on linking. remove the '=0' and it compiles and links fine. What drives me nuts is that I can make a very contrived example with a couple of classes and no real code behind it and no problems at all tiny code size and no errors. It only seems to nail me when I try a larger real-world application. I do this on other platforms without issues so I don't think its a syntax issue. In fact some of my code I have ported over to a Linux system and it works fine there if I make pure virtuals out of the methods instead of the dummy bodies I have to use in PropGCC.

It's not that big a thing. I get along without them in PropGCC the issue is in porting. Therer are some things that I share between a Beagle Bone Black and a Propeller like an interface classes for I2C and certain types of sensors. I would prefer to have the code port over without editing and I would prefer an unimplemented but required function to produce a compile error vs incorrect run-time operation. I can post up my current project if needed.
«13

Comments

  • photomankcphotomankc Posts: 943
    edited 2013-10-16 07:20
    Disregaurd the notion it works in contrived examples! I made a goof and forgot to add the inheritance specifier to the examples on both attempts. It does in fact blow up and overflow even on a simple demo example:
    class Base
    { 
    public:
      virtual ~Base() {};
      virtual void doFoo()=0;
    protected:
      int m_dummy;
    };
    
    
    class Derived : public Base
    {
    public:
        virtual ~Derived(){};
        void doFoo();
    };
    
    void Derived::doFoo()
    {
        // Do something here
    }
    
    int main(void)
    {
        Derived derived;
        return 0;
    }
    
    


    This will fail and produce the following:
    propeller-elf-c++ -I . -L . -o cmm/PureVirtual.elf -Os -mcmm -fno-exceptions -fno-rtti -std=c99 PureVirtual.c
    cc1plus.exe: warning: command line option '-std=c99' is valid for C/ObjC but not for C++ [enabled by default]
    
    c:/propgcc/bin/../lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld.exe: region `hub' overflowed by 22204 bytes
    
    Done. Build Failed!
    
  • Heater.Heater. Posts: 21,230
    edited 2013-10-16 23:10
    Problem is that when you get into the world of inheritance and polymorphism the generated code will start to grow. You now have polymorphism. That is to say you have many functions (methods) that will have the same name, in the base class and in the various children, and which of those methods actually gets called depends on the data types (objects) you are working with at the time.

    In your example somePtr->doFoo() could mean two different things depending on the type of somePtr.

    In order do that the compiler has to add some look up tables to your code containing the various virtual functions (vtables) it also needs to add some code to figure out which function in the vtable to call given a particular type. I'm not familiar with how it actually does that but that is the general idea.

    Having said that I was surprised that this simple example blows all of HUB RAM so easily. I compiled it on my PC with
    gcc -static -o virtual -std=c99 -Wall -Os virtual.cpp -lstdc++
    and it came to 750KB !!
  • Heater.Heater. Posts: 21,230
    edited 2013-10-16 23:32
    Further investigation...

    I compiling that code on my PC with gcc, instead of c++ or g++, and leaving out the -lstdc++ like so:

    gcc -o virtual -static -Wall -Os main.cpp derived.cpp
    gives  me the following errors:
    /tmp/ccFwy8gy.o: In function `Derived::~Derived()':
    derived.cpp:(.text._ZN7DerivedD0Ev[_ZN7DerivedD0Ev]+0x8): undefined reference to `operator delete(void*)'
    /tmp/ccFwy8gy.o: In function `Base::~Base()':
    derived.cpp:(.text._ZN4BaseD0Ev[_ZN4BaseD0Ev]+0x8): undefined reference to `operator delete(void*)'
    /tmp/ccFwy8gy.o:(.rodata._ZTI7Derived[_ZTI7Derived]+0x0): undefined reference to `vtable for __cxxabiv1::__si_class_type_info'
    /tmp/ccFwy8gy.o:(.rodata._ZTV4Base[_ZTV4Base]+0x20): undefined reference to `__cxa_pure_virtual'
    /tmp/ccFwy8gy.o:(.rodata._ZTI4Base[_ZTI4Base]+0x0): undefined reference to `vtable for __cxxabiv1::__class_type_info
    

    Notice the undefined references to "vtable". I can add "-lstdc++" to that command and it will build. cpp an

    So we see that using virtual classes means we are having to pull in the standard C++ library (libstdc+) and that is huge.

    Quite why it has to drag in so much stuff to do this is beyond me.


    Note: I split your example into separate files for main, virtual and derived, with appropriate header files, as I have found it impossible to compile virtual classes sometimes unless they are in their own files.
  • Heater.Heater. Posts: 21,230
    edited 2013-10-16 23:50
    Further investigation...

    Yep, removing all that "virtual" and "=0" stuff and I am only down to 700KB !!

    There are those who warn that C++ is not suitable for small embedded systems as it causes severe code bloat and performance issues.

    My experiments had shown that is is possible to use C++ classes and other features and the code will be almost byte for byte the same as if you had coded the thing in C with pointers and structs. C++ is quite OK for small systems if used in a style like the objects in Spin.

    I had always suspected that inheritance and polymorphism would cause size problems. I never realized it was this bad.
    I'm sure 99% of that 700K being included in my static linux binary is not required to build such a simple program.
  • David BetzDavid Betz Posts: 14,516
    edited 2013-10-17 03:35
    photomankc wrote: »
    Disregaurd the notion it works in contrived examples! I made a goof and forgot to add the inheritance specifier to the examples on both attempts. It does in fact blow up and overflow even on a simple demo example:
    class Base
    { 
    public:
      virtual ~Base() {};
      virtual void doFoo()=0;
    protected:
      int m_dummy;
    };
    
    
    class Derived : public Base
    {
    public:
        virtual ~Derived(){};
        void doFoo();
    };
    
    void Derived::doFoo()
    {
        // Do something here
    }
    
    int main(void)
    {
        Derived derived;
        return 0;
    }
    
    


    This will fail and produce the following:
    propeller-elf-c++ -I . -L . -o cmm/PureVirtual.elf -Os -mcmm -fno-exceptions -fno-rtti -std=c99 PureVirtual.c
    cc1plus.exe: warning: command line option '-std=c99' is valid for C/ObjC but not for C++ [enabled by default]
    
    c:/propgcc/bin/../lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/bin/ld.exe: region `hub' overflowed by 22204 bytes
    
    Done. Build Failed!
    
    It seems like one thing that can cause code bloat is the support for runtime type information. Is it possible that that is on by default? If so, what happens if you turn it off?

    Try using this option:
    -fno-rtti
    
  • danielstrittdanielstritt Posts: 43
    edited 2013-10-17 04:08
    Just a hunch, but when I tried C++ it came out huge as well, until I removed the reference to iostream header. Even if you don't call anything in it, it seems to make the file huge

    Daniel
  • Heater.Heater. Posts: 21,230
    edited 2013-10-17 04:10
    Makes little difference when building on my PC with:

    c++ -static -fno-exceptions -fno-rtti -o virtual -Os -Wall virtual.cpp

    still comes in at 750KB!

    Thing is needs __cxa_pure_virtual and delete() from stdc++, as show when you compile with gcc and don't add "-lstdc++".

    That seems to pull in a ton of junk.
  • David BetzDavid Betz Posts: 14,516
    edited 2013-10-17 04:11
    David Betz wrote: »
    It seems like one thing that can cause code bloat is the support for runtime type information. Is it possible that that is on by default? If so, what happens if you turn it off?

    Try using this option:
    -fno-rtti
    
    Oops, sorry! I see you're already using that option.
  • Heater.Heater. Posts: 21,230
    edited 2013-10-17 04:33
    I can get it down to about 680KB by adding "-s -Wl,--gc-sections" options. Still a long way to go.
  • David BetzDavid Betz Posts: 14,516
    edited 2013-10-17 04:40
    Try this:
    class Base
    { 
    public:
      virtual ~Base() {};
      virtual void doFoo()=0;
    protected:
      int m_dummy;
    };
    
    
    class Derived : public Base
    {
    public:
        virtual ~Derived(){};
        void doFoo();
    };
    
    void Derived::doFoo()
    {
        // Do something here
    }
    
    int main(void)
    {
        Derived derived;
        return 0;
    }
    
    extern "C" void __cxa_pure_virtual() { while (1); }
    
  • Heater.Heater. Posts: 21,230
    edited 2013-10-17 04:47
    Makes no difference.
  • David BetzDavid Betz Posts: 14,516
    edited 2013-10-17 04:49
    Heater. wrote: »
    Makes no difference.
    Hmmm... Here is what I got:
    david-betzs-macbook-pro:~ dbetz$ propeller-elf-gcc -Os -fno-rtti -o virtual.elf virtual.cpp -lstdc++
    david-betzs-macbook-pro:~ dbetz$ ls -l *.elf
    -rwxr-xr-x  1 dbetz  staff  12117 Oct 17 07:39 virtual.elf
    david-betzs-macbook-pro:~ dbetz$ propeller-elf-objdump -h virtual.elf
    
    virtual.elf:     file format elf32-propeller
    
    Sections:
    Idx Name          Size      VMA       LMA       File off  Algn
      0 .boot         00000020  00000000  00000000  000000d4  2**0
                      CONTENTS, ALLOC, LOAD, READONLY, CODE
      1 .lmmkernel    00000520  00000000  00000020  000000f4  2**2
                      CONTENTS, ALLOC, LOAD, CODE
      2 .init         000000b8  00000540  00000540  00000614  2**2
                      CONTENTS, ALLOC, LOAD, READONLY, CODE
      3 .text         00000448  000005f8  000005f8  000006cc  2**2
                      CONTENTS, ALLOC, LOAD, READONLY, CODE
      4 .fini         0000003c  00000a40  00000a40  00000b14  2**2
                      CONTENTS, ALLOC, LOAD, READONLY, CODE
      5 .hub          0000003c  00000a7c  00000a7c  00000b50  2**2
                      CONTENTS, ALLOC, LOAD, DATA
      6 .math.kerext  000000dc  000006c0  00000ab8  00000b8c  2**2
                      CONTENTS, ALLOC, LOAD, READONLY, CODE
      7 .ctors        00000004  00000b94  00000b94  00000c68  2**0
                      CONTENTS, ALLOC, LOAD, READONLY, CODE
      8 .dtors        00000004  00000b98  00000b98  00000c6c  2**0
                      CONTENTS, ALLOC, LOAD, READONLY, CODE
      9 .bss          000000b0  00000b9c  00000b9c  00000c70  2**2
                      ALLOC
     10 .hub_heap     00000004  00000c4c  00000c4c  00000c70  2**0
                      ALLOC
     11 .debug_aranges 00000020  00000000  00000000  00000c70  2**0
                      CONTENTS, READONLY, DEBUGGING
     12 .debug_info   000004c1  00000000  00000000  00000c90  2**0
                      CONTENTS, READONLY, DEBUGGING
     13 .debug_abbrev 000000f0  00000000  00000000  00001151  2**0
                      CONTENTS, READONLY, DEBUGGING
     14 .debug_line   0000012d  00000000  00000000  00001241  2**0
                      CONTENTS, READONLY, DEBUGGING
     15 .debug_frame  00000024  00000000  00000000  00001370  2**2
                      CONTENTS, READONLY, DEBUGGING
     16 .debug_loc    0000001e  00000000  00000000  00001394  2**0
                      CONTENTS, READONLY, DEBUGGING
     17 .debug_ranges 00000010  00000000  00000000  000013b2  2**0
                      CONTENTS, READONLY, DEBUGGING
    
  • David BetzDavid Betz Posts: 14,516
    edited 2013-10-17 04:51
    I suppose this could be a difference between the release_1_0 branch of propgcc that SimpleIDE is using and the default branch which is what I'm using.
  • photomankcphotomankc Posts: 943
    edited 2013-10-17 06:01
    Heater. wrote: »
    Problem is that when you get into the world of inheritance and polymorphism the generated code will start to grow. You now have polymorphism. That is to say you have many functions (methods) that will have the same name, in the base class and in the various children, and which of those methods actually gets called depends on the data types (objects) you are working with at the time.

    In your example somePtr->doFoo() could mean two different things depending on the type of somePtr.

    In order do that the compiler has to add some look up tables to your code containing the various virtual functions (vtables) it also needs to add some code to figure out which function in the vtable to call given a particular type. I'm not familiar with how it actually does that but that is the general idea.

    Having said that I was surprised that this simple example blows all of HUB RAM so easily. I compiled it on my PC with
    gcc -static -o virtual -std=c99 -Wall -Os virtual.cpp -lstdc++
    and it came to 750KB !!


    There has to be more to that story. I have several projects in the Beagle Bone Black that make use of these same classes using pure-virtual and they are nowhere near that 750K size. I just finished writing an interface to a TMP102 sensor using my I_I2C ---- FS_I2C hierarchy that uses virtual and pure-virtual functions both. The completed executable is 24.3K and includes all the Linux file handling nonsense to work with "/dev/i2c-1".

    In the above example in fact if you just take away the "=0;" and replace it with "{ };" this will compile to 2,724 bytes. All the v-tables are still there as is all the polymorphic ability it's just the fact that I'm telling the compiler that doFoo() MUST be implemented in children vs CAN be implemented in children that seems to send the code into orbit. That isn't making sense to me.

    This will compile to a perfectly acceptable size of 2,732 bytes:
    class Base
    { 
    public:
      virtual ~Base() {};
      virtual void doFoo() {};
    protected:
      int m_dummy[100];
    };
    
    
    class Derived : public Base
    {
    public:
        virtual ~Derived(){};
        void doFoo();
    };
    
    void Derived::doFoo()
    {
        m_dummy[0]++;
    }
    
    int main(void)
    {
        Derived derived;
        derived.doFoo();
        return 0;
    }
    
  • David BetzDavid Betz Posts: 14,516
    edited 2013-10-17 06:16
    To follow up on this a bit more, with the simple declaration of the __cxa_pure_virtual added, I get the following code size:
    david-betzs-macbook-pro:~ dbetz$ propeller-elf-objdump -p virtual.elf
    
    virtual.elf:     file format elf32-propeller
    
    Program Header:
        LOAD off    0x000000d4 vaddr 0x00000000 paddr 0x00000000 align 2**0
             filesz 0x00000020 memsz 0x00000020 flags r-x
        LOAD off    0x000000f4 vaddr 0x00000000 paddr 0x00000020 align 2**2
             filesz 0x00000520 memsz 0x00000520 flags rwx
        LOAD off    0x00000614 vaddr 0x00000540 paddr 0x00000540 align 2**2
             filesz 0x00000578 memsz 0x00000578 flags rwx
        LOAD off    0x00000b8c vaddr 0x000006c0 paddr 0x00000ab8 align 2**2
             filesz 0x000000dc memsz 0x000000dc flags r-x 10000000
        LOAD off    0x00000c68 vaddr 0x00000b94 paddr 0x00000b94 align 2**2
             filesz 0x00000008 memsz 0x000000bc flags rwx
    private flags = 0x1: [prop1]
    

    This contains only 3152 bytes of code/data if you add up the memsz fields above.
  • Heater.Heater. Posts: 21,230
    edited 2013-10-17 06:33
    What command are you using to compile that on the Beagle Bone Black?

    On my Raspberry Pi this command:

    gcc -s -static -fno-exceptions -fno-rtti -o virtual -Os -Wall virtual.cpp -lstdc++

    gets me a executable of 508592 byes.

    Of course if you forget the "-static" it is:

    only 3495 bytes :)

    I have an ARM cross compiler here that will get us down to a 40KB static build:

    /opt/gcc-arm-none-eabi-4_7-2013q3/bin/arm-none-eabi-gcc -s -static -fno-exceptions -fno-rtti -o virtual -Os -Wall virtual.cpp -lstdc++

    Or there is propgcc which gets that to 3752 bytes!

    /opt/parallax/bin/propeller-elf-gcc -s -static -fno-exceptions -fno-rtti -o virtual -Os -Wall virtual.cpp -lstdc++

    Make that pure virtual and propgcc is back to 69K and it complains about not fitting in HUB

    This propgcc version 4.6.1 (propellergcc-alpha_v1_9_0_2157)
  • photomankcphotomankc Posts: 943
    edited 2013-10-17 07:24
    Heater. wrote: »
    What command are you using to compile that on the Beagle Bone Black?

    On my Raspberry Pi this command:

    gcc -s -static -fno-exceptions -fno-rtti -o virtual -Os -Wall virtual.cpp -lstdc++

    gets me a executable of 508592 byes.

    Of course if you forget the "-static" it is:

    only 3495 bytes :)

    I have an ARM cross compiler here that will get us down to a 40KB static build:

    /opt/gcc-arm-none-eabi-4_7-2013q3/bin/arm-none-eabi-gcc -s -static -fno-exceptions -fno-rtti -o virtual -Os -Wall virtual.cpp -lstdc++

    Or there is propgcc which gets that to 3752 bytes!

    /opt/parallax/bin/propeller-elf-gcc -s -static -fno-exceptions -fno-rtti -o virtual -Os -Wall virtual.cpp -lstdc++

    Make that pure virtual and propgcc is back to 69K and it complains about not fitting in HUB

    This propgcc version 4.6.1 (propellergcc-alpha_v1_9_0_2157)


    Ok looks like you got that one. Didn't realize that I was dynamic linking. So without pure-virtual function in the example:
    g++ -static -fno-exceptions -fno-rtti -Wall -lstdc++ -std=c++0x -c -I./include -I./sensors -I./bus_protocol -c virtual.cpp -o build/virtual.o
    g++ -static build/virtual.o -o ./bin/virtual-test

    -rwxr-xr-x 1 robot users 583K Oct 17 09:05 virtual-test




    Now with a pure-virtual added back in:
    g++ -static -fno-exceptions -fno-rtti -Wall -lstdc++ -std=c++0x -c -I./include -I./sensors -I./bus_protocol -c virtual.cpp -o build/virtual.o
    g++ -static build/virtual.o -o ./bin/tvirtual-test

    -rwxr-xr-x 1 robot users 640K Oct 17 09:08 virtual-test

    So almost 57K of bloat to specify a virtual as pure-virtual. I'm a little surprised by that.

    Like I said, virtuals without pure specifier are workable for sure and give me the benefit of making code that can have the support objects change without refactoring things everywhere but will be at the risk of run-time bugs if all the required interface functions are not over-ridden. If we can get to where pure-virtual doesn't crush the hub into the ground that would be great.
  • David BetzDavid Betz Posts: 14,516
    edited 2013-10-17 07:32
    photomankc wrote: »
    Like I said, virtuals without pure specifier are workable for sure and give me the benefit of making code that can have the support objects change without refactoring things everywhere but will be at the risk of run-time bugs if all the required interface functions are not over-ridden. If we can get to where pure-virtual doesn't crush the hub into the ground that would be great.
    Have you tried adding this line to your program? I guess Heater did and it didn't help him for some reason. It certainly fixes the problem for me.
    extern "C" void __cxa_pure_virtual() { while (1); }
    
  • photomankcphotomankc Posts: 943
    edited 2013-10-17 07:37
    Yes, that works!

    Project Directory: C:/Users/kcrane2/Documents/SimpleIDE/My Projects/PureVirtual/

    propeller-elf-c++ -I . -L . -o cmm/PureVirtual.elf -Os -mcmm -fno-exceptions -fno-rtti PureVirtual.c
    propeller-elf-objdump -h cmm/PureVirtual.elf
    Done. Build Succeeded!

    Code Size is 2736 bytes (2908 total).


    It's the same with either:
    virtual void doFoo() {}; or
    virtual void doFoo()=0;


    Awesome!
  • David BetzDavid Betz Posts: 14,516
    edited 2013-10-17 07:38
    photomankc wrote: »
    Yes, that works!

    Project Directory: C:/Users/kcrane2/Documents/SimpleIDE/My Projects/PureVirtual/

    propeller-elf-c++ -I . -L . -o cmm/PureVirtual.elf -Os -mcmm -fno-exceptions -fno-rtti PureVirtual.c
    propeller-elf-objdump -h cmm/PureVirtual.elf
    Done. Build Succeeded!

    Code Size is 2736 bytes (2908 total).


    It's the same with either:
    virtual void doFoo() {}; or
    virtual void doFoo()=0;


    Awesome!
    I can't claim any credit for this. I found it with Google! :-)
  • photomankcphotomankc Posts: 943
    edited 2013-10-17 07:47
    So it looks the standard version of __cxa_pure_virtual() prints out something to the console and aborts and I'm guessing that drags in a bunch of cruft to do it.

    EDIT: I see it also throws exceptions and thus crams exceptions back in as well. Quite the party it throws.
  • Heater.Heater. Posts: 21,230
    edited 2013-10-17 08:25
    Wow. With that last version of the virtual code posted here and adding the __cxa_pure_virtual() definition it now works.

    $ /opt/parallax/bin/propeller-elf-gcc -s -static -fno-exceptions -fno-rtti -o virtual -Os -Wall virtual.cpp -lstdc++

    Gives 3868 bytes for the non-pure virtual and 3880 with the pure virtual.

    Looks like a made a mistake originally, sorry.
  • Heater.Heater. Posts: 21,230
    edited 2013-10-17 08:29
    I'm still wondering what goes on.

    That same setup with pure virtual gives 40K with my ARM cross compiler, 700K on my PC, and 500K on my Raspi.
  • David BetzDavid Betz Posts: 14,516
    edited 2013-10-17 08:30
    Heater. wrote: »
    Wow. With that last version of the virtual code posted here and adding the __cxa_pure_virtual() definition it now works.

    $ /opt/parallax/bin/propeller-elf-gcc -s -static -fno-exceptions -fno-rtti -o virtual -Os -Wall virtual.cpp -lstdc++

    Gives 3868 bytes for the non-pure virtual and 3880 with the pure virtual.

    Looks like a made a mistake originally, sorry.
    So maybe you can redefine __cxa_pure_virtual to light an LED or something like that rather than dragging in cout and everything that involves! :-)
  • photomankcphotomankc Posts: 943
    edited 2013-10-17 09:19
    So I created a header named "small-cpp.h" and defined __cxa_pure_virtual() within that. I can then add that to the main project .cpp file and this clears up the problem in my current project. I can use pure-virtual functions in the project and it compiles to within a few bytes of the previous versions without any pure-virtuals. This is great! Thanks for the help in figuring this out. I came across across lots of "you can't use C++ on a micro" discussions but not that specific function.
  • David BetzDavid Betz Posts: 14,516
    edited 2013-10-17 09:22
    Glad we could help. Good luck with your project!
  • Heater.Heater. Posts: 21,230
    edited 2013-10-17 10:59
    photomankc,
    I came across across lots of "you can't use C++ on a micro"
    Oh yeah. I have been in involved in some very long debates about that on this forum and others. You know:
    "You can't use classes and objects they wast a huge lot of space", demonstrably that is not true.
    "You can't use templates...same ..same...", demonstrably not true.
    And so on.
    All the time ignoring the fact that C++ is what is used to program Arduinos which are tiny.

    One argument that always stumped me was the "You can't use inheritance and virtual functions it wastes a lot of space"

    Thankfully thanks to your question and David's answer we now have a counter to that argument as well.

    Hopefully I can eventually find out how to get a similar result on the PC and Raspi.
  • photomankcphotomankc Posts: 943
    edited 2013-10-17 14:03
    Heater. wrote: »
    photomankc,
    Thankfully thanks to your question and David's answer we now have a counter to that argument as well.

    Hopefully I can eventually find out how to get a similar result on the PC and Raspi.


    In this case I think the main argument could come down to performance. There is more work involved in resolving the virtual call for polymorphic types and if the function is doing very little work and being called in a tight loop the overhead could be excessive. So virtual getters/setters of a variety of different object types called in tightly executed loop are a degenerate case where it could cost many times what a static function call would. On big-boy CPU's screwing up the cache in a performance intensive app is a disaster. As with all things. Use it wisely. In my mind the benefits of a single library of code for sensors/buses/motors/ect outweighs some loss of performance. If I have really intense code that needs to do something very fast I put that in C and use another COG to get-r-done.
  • Heater.Heater. Posts: 21,230
    edited 2013-10-18 00:33
    photomankc,

    On the PC or even the PI I am not so worried about the huge size of these binaries or even the performance usually. But it is going to bug me now. What has been done here is:

    a) Show that using inheritance and pure virtual methods does not require half a megabyte of binary as is normally produced by GCC. It's only a few KB.

    b) There is a "trick" with propgcc that gets the huge binaries down to a few KB.

    So now I want to know why the same the same source with the same trick and compiled with the same options does not work on the PI and PC. It still produces half megabyte binaries. What other tricks do I need with these
    compilers?

    As for the overheads of C++. What I have found so far is that if you actually need something in your code you can do it in C or C++ and find that C++ does it with no overhead and perhaps even allows better oprtimization to produce smaller faster binaries. Much coding in C is is not amenable to optimization because type information and such is lost.

    Examples:

    1) Classes:

    If you need many similar "objects" in your program that can all be manipulated by the same functions then you can do that in C. You will probably put the objects data into structs and you will probably pass pointers to those structs as parameters to the functions.

    Or you could do that in C++. Have your objects defined by classes and have those functions as methods.

    All that happens is that the "object pointer" is still there but now it is hiddden from you and passed around to methods as "this". That is not C++ overhead, that is something you needed anyway and had to do manually in C

    If you compile C and C++ versions of this you will find the code can come out byte for byte the same!

    2) Templates:

    If you need to apply similar processing to different types, ints, longs, floats, "fish" etc. You can do that in C. Perhaps you have to write out the code N times, once for each type. Or perhaps you pass void pointers around together with some helper functions for each type. Yuk.

    Or in C++ you write your processing once and template the types. The compiler takes care of it. There is no C++ overhead here. You needed to do that stuff anyway.

    3) Inheritance, polymorphism

    If you need your code to dispatch to different functions depending on the type of data it has then you can do that in C. You will probably put your types into different structs together with some field indicating its "type". You will probably need to write code to check that "type" field and look up which actual function to call. Yuk.

    Or you can do it in C++ and let the compiler insert all that dispatching business. There is no overhead here (hopefully) you needed to do that in your code anyway.

    I said "hopefully" here as the exeriment/comparison has not been done yet.

    None of these things is a C++ overhead if you actually need them in your code. You can write it by hand or let C++ do it for you. I suspect the C++ generated code can often be better than hand written C to do these things.

    If you don't need classes in your code, you only have one of everything, don't use classes. That little "this" pointer goes away and you get standard C code. And so on for the other features.

    There is no reason to go back to .c files:)

    Sorry for rambling on here...
  • photomankcphotomankc Posts: 943
    edited 2013-10-18 07:27
    I'd be interested to know that as well for the binary size. I'm guessing there is more standard library being pulled in somewhere under the curtains which I'm sure you've guessed as well. Seems there are some other __cxa_ functions that are buried in the guts as well when I was reading more about pure_virtual.


    Otherwise, you're preaching to the choir here. I have seen all kinds of arguments on how abstraction is overhead and C++ is too bloated (I think you can see how that could be thought though) and C produces better, tighter code. Most of them turn out to be only marginally true when real results are compared so I agree that there is little reason to wave it off from all embedded development completely as many claim you must do. So long as these issues are addressed as we see the code is perfectly well suited to the task.

    On polymorphic classes though there are some real hits that can matter:

    - The compiler may not know what type is going to be used in this instance and thus can not inline any of the function/method calls even the trivial ones. That in itself can be a hit performance-wise.

    - The method calls may have to be resolved via the v-table. In cases where the compiler can tell 100% what object is going to be called it can place the function call statically but not if there is ambiguity about what the actual type will be at run-time it's going to have to be the v-table lookup.

    - To resolve that virtual method may take 2 times longer or more since there are a couple more steps involved in getting the address of the right method to call. That may or may not be significant.


    Now if the function does some math and a bunch of processing then all that may be a fart in the wind. Who cares if it took 10nS longer to call the virtual area() method for the Circle descendant of Shape when that took 100uS to complete anyway? Probably nobody. However if you call the virtual getXLen() of the Square descendant of Rectangle type of Shape in some tight loop that needs to get millions of those widths you could see rather significant impact there vs a standard, inlined, assignment. If you need to do that in the minimum period of time then this could be a place you have to reconsider the approach.

    I've always been prone to write things hybrid anyway. I tend to have a procedural style main loop with OOP subsystems that loop interacts with. My OOP college professors would get very offended by that but it always worked well for me. No need to go back to .c files. Global functions work just as good in C++ if you need one.
Sign In or Register to comment.