Why does std::stack::pop() not throw an exception if the stack is empty?

Discussion in 'C++' started by Debajit Adhikary, Feb 3, 2011.

  1. Why does std::stack::pop() not throw an exception if the stack is empty and there is nothing to pop?

    (I'm designing a specialized Stack for my own code and would like to know the tradeoffs with this approach (which requires one to manually check if the stack is empty vs. throwing an exception.

    My guess here would be that although C++ supports exception-handling, it comes with a small runtime overhead, and therefore, for maximum performance, the decision was made not to throw an exception in std::stack::pop).
     
    Debajit Adhikary, Feb 3, 2011
    #1
    1. Advertising

  2. On Feb 3, 1:51 pm, Debajit Adhikary <> wrote:
    > Why does std::stack::pop() not throw an exception if the stack is empty and there is nothing to pop?
    >
    > (I'm designing a specialized Stack for my own code and would like to know the tradeoffs with this approach (which requires one to manually check if the stack is empty vs. throwing an exception.
    >
    > My guess here would be that although C++ supports exception-handling, it comes with a small runtime overhead, and therefore, for maximum performance, the decision was made not to throw an exception in std::stack::pop).


    You could be right under a very particular interpretation of your
    words. Allow me to clarify, to ensure we're on the same page.

    It's quite possible to implement exceptions so that the following two
    programs will have basically the same runtime when passed no (command
    line) arguments.
    //program 1
    int main(int argc, char** argv)
    { return (argc == 3) ? 1 : 0;
    }
    //program 2
    int main(int argc, char** argv)
    { try
    { if (argc == 3)
    throw 1;
    }catch (int )
    { return 1;
    }
    return 0;
    }

    Let me emphasis that this is quite possible. A standard modern linux
    and gcc distro on X86_64 has this behavior. Now, on the codepath where
    argc == 3, then the first program is likely much faster than the
    second. This is what most people say when they're talking about
    "exception overhead", that is the overhead of having a possible throw,
    but not actually executing it. To repeat, on a good implementation
    like gcc on linux, if you don't throw, you don't pay a penalty.

    On windows (32 and 64) and some other platforms, the situation is
    different. Sadly, the compiler writers and ABI writers for those
    platforms entirely missed the memo on how exceptions ought to be
    implemented, and as such, you will see a measurable difference between
    the runtime for programs like the above. Obviously not just for one
    iteration, but if you have a program on windows that has possible
    throw statements which are never executed, you can get measurably
    faster code if you change the throws to return codes. Sad but true.

    So, with that out of the way, there's one more issue to deal with. Why
    does pop not throw an exception? Because even on the "ideal"
    implementation of gcc on linux, there is still overhead to throw
    because you must check the condition. The exception part is just a red
    herring. The answer is the same even if you asked "Why doesn't pop
    return an error code when the stack is empty?". Internally, it has to
    have code like:
    void stack::pop()
    { if (is_empty())
    throw something;
    do_pop();
    }
    The execution of "is_empty()" on every pop is likely unnecessary for a
    large majority of code, but it takes cycles even if it's not
    necessary. The exception vs return code overhead discussion is
    entirely irrelevant. It's a discussion of whether it's worth it to
    check preconditions or call it UB. The C++ standard philosophy is to
    only pay for what you use, and to provide building blocks for
    developers. If the developer want to use a stack which has pop throw
    on an empty stack, they're welcome to wrap std::stack. However, if it
    was the reverse, you couldn't wrap std::stack and get rid of the
    "is_empty()" check if it was inside std::stack::pop, which is exactly
    why it's not there.

    PS: There is actual overhead for exceptions even on the ideal
    implementation. The overhead is not execution time, but size. That
    overhead is size of executable, and size of virtual, not resident,
    memory. Those exception handlers have to go somewhere. If you have
    virtual memory, it's almost a non-issue for a good implementation - if
    you don't throw an exception, then the exception handlers never get
    loaded to main memory, and it's almost as if they didn't even exist.
    However, for platforms without virtual memory, such as some embedded
    platforms, that memory overhead may be unacceptable.
     
    Joshua Maurice, Feb 4, 2011
    #2
    1. Advertising

  3. Debajit Adhikary

    Paul Guest

    "Joshua Maurice" <> wrote in message
    news:...

    >The execution of "is_empty()" on every pop is likely unnecessary for a
    >large majority of code, but it takes cycles even if it's not
    >necessary.


    Surely this condition must be tested before pop() deletes the object and
    decrements the stack , anyway?
     
    Paul, Feb 4, 2011
    #3
  4. Re: Why does std::stack::pop() not throw an exception if the stackis empty?

    On 04.02.2011 01:12, Joshua Maurice wrote:
    > On Feb 3, 1:51 pm, Debajit Adhikary<> wrote:
    >> Why does std::stack::pop() not throw an exception if the stack is empty and there is nothing to pop?

    > [...]
    > On windows (32 and 64) and some other platforms, the situation is
    > different. Sadly, the compiler writers and ABI writers for those


    The truth is: 32 bit Windows had to respect binary compatibility, while
    under Linux the binary compatibility is not relevant.

    I think for that reason 32 bit exception handling wasn't changed, when
    the new exception handling mechanisms, which are quite more effective,
    had been developed.

    64 bit Windows exception handling (and the compiler generated code - VS
    C++) is quite different to the 32 bit one and the implementation should
    have the same low/none runtime overhead as under Linux.


    Andre
     
    Andre Kaufmann, Feb 4, 2011
    #4
  5. On Feb 3, 10:21 pm, Andre Kaufmann <> wrote:
    > On 04.02.2011 01:12, Joshua Maurice wrote:
    >
    > > On Feb 3, 1:51 pm, Debajit Adhikary<>  wrote:
    > >> Why does std::stack::pop() not throw an exception if the stack is empty and there is nothing to pop?

    > > [...]
    > > On windows (32 and 64) and some other platforms, the situation is
    > > different. Sadly, the compiler writers and ABI writers for those

    >
    > The truth is: 32 bit Windows had to respect binary compatibility, while
    > under Linux the binary compatibility is not relevant.
    >
    > I think for that reason 32 bit exception handling wasn't changed, when
    > the new exception handling mechanisms, which are quite more effective,
    > had been developed.


    Indeed.

    But, well, there's no reason they had to piggyback C++ exception
    handling on top of SEH. Is there? Would it have been reasonable to
    extend the ABI to have SEH and C++ exceptions as different things?

    > 64 bit Windows exception handling (and the compiler generated code - VS
    > C++) is quite different to the 32 bit one and the implementation should
    > have the same low/none runtime overhead as under Linux.


    Last I tested, there's still a measurable overhead, whereas with gcc
    on linux there is no measurable overhead.
     
    Joshua Maurice, Feb 4, 2011
    #5
  6. Debajit Adhikary

    Goran Guest

    On Feb 3, 10:51 pm, Debajit Adhikary <> wrote:
    > Why does std::stack::pop() not throw an exception if the stack is empty and there is nothing to pop?
    >
    > (I'm designing a specialized Stack for my own code and would like to know the tradeoffs with this approach (which requires one to manually check if the stack is empty vs. throwing an exception.
    >
    > My guess here would be that although C++ supports exception-handling, it comes with a small runtime overhead, and therefore, for maximum performance, the decision was made not to throw an exception in std::stack::pop).


    My 2c: pop is a cleanup operation, and as such, it should have a
    nothrow exception safety guarantee.

    It's kinda like calling delete (or free) on a NULL pointer: it does
    squat, but alternative of reporting an error seems worse.

    Goran.
     
    Goran, Feb 4, 2011
    #6
  7. On 4 fév, 08:40, Goran <> wrote:
    > On Feb 3, 10:51 pm, Debajit Adhikary <> wrote:
    >
    > > Why does std::stack::pop() not throw an exception if the stack is empty and there is nothing to pop?

    >
    > > (I'm designing a specialized Stack for my own code and would like to know the tradeoffs with this approach (which requires one to manually check if the stack is empty vs. throwing an exception.

    >
    > > My guess here would be that although C++ supports exception-handling, it comes with a small runtime overhead, and therefore, for maximum performance, the decision was made not to throw an exception in std::stack::pop).

    >
    > My 2c: pop is a cleanup operation, and as such, it should have a
    > nothrow exception safety guarantee.


    It calls the destructor of contained object; a nothrow guarantee would
    mean that you silently discard exceptions thrown from the object's
    destructor. Not what you generally want.

    > It's kinda like calling delete (or free) on a NULL pointer: it does
    > squat, but alternative of reporting an error seems worse.


    There is sure a tension between robustness and correctness but in
    this case, IMO considering that deleting a NULL pointer is an error is
    a bit to much. Being NULL is after all a valid state for a pointer.

    --
    Michael
     
    Michael Doubez, Feb 4, 2011
    #7
  8. On 3 fév, 22:51, Debajit Adhikary <> wrote:
    > Why does std::stack::pop() not throw an exception if the stack is empty and there is nothing to pop?


    Because of 23.1/10 of the standard:
    no erase(), pop_back() or pop_front() function throws an exception.

    Erasing an element that doesn't exists or popping and empty container
    is in itself not an error (just like emptying an empty bin). If your
    logic requires to pop only non empty container, the requirement is at
    your level, not at the level of the container (unless the underlying
    container requires it).


    > (I'm designing a specialized Stack for my own code and would like to know the tradeoffs with this
    > approach (which requires one to manually check if the stack is empty vs. throwing an exception.
    >
    > My guess here would be that although C++ supports exception-handling, it comes with a small runtime overhead, and therefore, for maximum performance, the decision was made not to throw an exception in std::stack::pop).


    I don't think so. pop() can throw an exception from the underlying
    container or the contained element's destructor. IMO the decision was
    purely a matter of preferring robustness. An other alternative would
    have to make this UB.

    --
    Michael
     
    Michael Doubez, Feb 4, 2011
    #8
  9. Debajit Adhikary

    Paul Guest

    "Michael Doubez" <> wrote in message
    news:...
    On 4 fév, 08:40, Goran <> wrote:
    > On Feb 3, 10:51 pm, Debajit Adhikary <> wrote:
    >
    > > Why does std::stack::pop() not throw an exception if the stack is empty
    > > and there is nothing to pop?

    >
    > > (I'm designing a specialized Stack for my own code and would like to
    > > know the tradeoffs with this approach (which requires one to manually
    > > check if the stack is empty vs. throwing an exception.

    >
    > > My guess here would be that although C++ supports exception-handling, it
    > > comes with a small runtime overhead, and therefore, for maximum
    > > performance, the decision was made not to throw an exception in
    > > std::stack::pop).

    >
    > My 2c: pop is a cleanup operation, and as such, it should have a
    > nothrow exception safety guarantee.


    It calls the destructor of contained object; a nothrow guarantee would
    mean that you silently discard exceptions thrown from the object's
    destructor. Not what you generally want.

    > It's kinda like calling delete (or free) on a NULL pointer: it does
    > squat, but alternative of reporting an error seems worse.


    There is sure a tension between robustness and correctness but in
    this case, IMO considering that deleting a NULL pointer is an error is
    a bit to much. Being NULL is after all a valid state for a pointer.

    ..................................................................................

    Is it guaranteed delete will be called on a NULL pointer?

    What happens if you do:

    stack<int> x; //Internal pointer may be initialised to NULL
    x.push(4); //Is pointer still NULL?
    x.pop(); //What is pointer value now, is it guaranteed to be NULL?
    x.pop(); //?

    As it is undefined behaviour to call pop on an empty stack, then it's not
    good practise to do this.
     
    Paul, Feb 4, 2011
    #9
  10. Michael Doubez <> wrote:
    > I don't think so. pop() can throw an exception from the underlying
    > container or the contained element's destructor.


    Isn't it a kind of hard rule that destructors must never throw?
     
    Juha Nieminen, Feb 4, 2011
    #10
  11. Debajit Adhikary

    Goran Guest

    On Feb 4, 11:21 am, "Paul" <> wrote:
    > > My 2c: pop is a cleanup operation, and as such, it should have a
    > > nothrow exception safety guarantee.

    >
    > It calls the destructor of contained object; a nothrow guarantee would
    > mean that you silently discard exceptions thrown from the object's
    > destructor. Not what you generally want.


    I disagree very much. Throwing destructor does not matter. Throwing
    destructor is a massive bug, reasons aplenty. If it happens, I'd quite
    frankly like to terminate more than anything else.

    Goran.
     
    Goran, Feb 4, 2011
    #11
  12. Debajit Adhikary

    Paul Guest

    "Goran" <> wrote in message
    news:...
    On Feb 4, 11:21 am, "Paul" <> wrote:
    > > My 2c: pop is a cleanup operation, and as such, it should have a
    > > nothrow exception safety guarantee.

    >
    > It calls the destructor of contained object; a nothrow guarantee would
    > mean that you silently discard exceptions thrown from the object's
    > destructor. Not what you generally want.


    I disagree very much. Throwing destructor does not matter. Throwing
    destructor is a massive bug, reasons aplenty. If it happens, I'd quite
    frankly like to terminate more than anything else.

    .........................................................

    You disagree with Michael then, not me. Sorry my fault for not indenting
    with '>'.
     
    Paul, Feb 4, 2011
    #12
  13. On 4 fév, 14:03, Goran <> wrote:
    > On Feb 4, 11:21 am, "Paul" <> wrote:
    >
    > > > My 2c: pop is a cleanup operation, and as such, it should have a
    > > > nothrow exception safety guarantee.

    >
    > > It calls the destructor of contained object; a nothrow guarantee would
    > > mean that you silently discard exceptions thrown from the object's
    > > destructor. Not what you generally want.

    >
    > I disagree very much. Throwing destructor does not matter. Throwing
    > destructor is a massive bug, reasons aplenty. If it happens, I'd quite
    > frankly like to terminate more than anything else.


    This is an open subject. The only thing forbidden is throwing from the
    destructor of during an exception stack unwinding and there are ways
    to avoid that.

    There is at least one library where throwing from the destructor is
    legitimate (an sql library IIRC); it uses RAI for commit/execute of
    the SQL query. An exception is thrown upon error.

    However, I prefer a std::logic_error thrown from a destructor and
    being warned (even if it kills the program) than having something
    catch it silently and let the program continue to run in a incorrect
    state.

    --
    Michael
     
    Michael Doubez, Feb 4, 2011
    #13
  14. On 4 fév, 14:01, Juha Nieminen <> wrote:
    > Michael Doubez <> wrote:
    > > I don't think so. pop() can throw an exception from the underlying
    > > container or the contained element's destructor.

    >
    >   Isn't it a kind of hard rule that destructors must never throw?


    There has been a proposal IIRC but there are some case where it is a
    valid design.

    I think it was discussed in c++.moderated group.

    --
    Michael
     
    Michael Doubez, Feb 4, 2011
    #14
  15. Debajit Adhikary

    James Kanze Guest

    On Feb 3, 9:51 pm, Debajit Adhikary <> wrote:
    > Why does std::stack::pop() not throw an exception if the stack
    > is empty and there is nothing to pop?


    > (I'm designing a specialized Stack for my own code and would
    > like to know the tradeoffs with this approach (which requires
    > one to manually check if the stack is empty vs. throwing an
    > exception.


    > My guess here would be that although C++ supports
    > exception-handling, it comes with a small runtime overhead,
    > and therefore, for maximum performance, the decision was made
    > not to throw an exception in std::stack::pop).


    I doubt that performance had much to do with it. Popping an
    empty stack is a programming error; programming errors shouldn't
    trigger exceptions, but rather immediate termination of the
    process.

    The tradition in C++ is to not require checking such programming
    errors, and to leave them as undefined behavior. A good
    implementation can then check, and then abort, in debugging
    mode; if the check may be too expensive, it can also turn it off
    in optimizing mode, and still be conforming.

    --
    James Kanze
     
    James Kanze, Feb 4, 2011
    #15
  16. Debajit Adhikary

    James Kanze Guest

    On Feb 4, 3:06 pm, Michael Doubez <> wrote:
    > On 4 fév, 14:01, Juha Nieminen <> wrote:


    > > Michael Doubez <> wrote:
    > > > I don't think so. pop() can throw an exception from the underlying
    > > > container or the contained element's destructor.


    > > Isn't it a kind of hard rule that destructors must never throw?


    > There has been a proposal IIRC but there are some case where it is a
    > valid design.


    There are certain, very special cases where it is appropriate
    for a destructor to throw. But only in such very special cases.
    Such classes can't be put into a standard container, since
    (§17.4.3.6):

    In particular, the effects are undefined in the
    following cases:
    [...]
    if any replacement function or handler function or
    destructor operation throws an exception, unless
    specifically allowed in the applicable Required
    behavior paragraph.

    A type used in a standard container is not allowed to exit
    a destructor via an exception.

    --
    James Kanze
     
    James Kanze, Feb 4, 2011
    #16
  17. Re: Why does std::stack::pop() not throw an exception if the stackis empty?

    On 04.02.2011 07:49, Joshua Maurice wrote:
    > On Feb 3, 10:21 pm, Andre Kaufmann<> wrote:
    >> On 04.02.2011 01:12, Joshua Maurice wrote:
    >>
    >>> On Feb 3, 1:51 pm, Debajit Adhikary<> wrote:
    >>>> Why does std::stack::pop() not throw an exception if the stack is empty and there is nothing to pop?
    >>> [...]
    >>> On windows (32 and 64) and some other platforms, the situation is
    >>> different. Sadly, the compiler writers and ABI writers for those

    >>
    >> The truth is: 32 bit Windows had to respect binary compatibility, while
    >> under Linux the binary compatibility is not relevant.
    >>
    >> I think for that reason 32 bit exception handling wasn't changed, when
    >> the new exception handling mechanisms, which are quite more effective,
    >> had been developed.

    >
    > Indeed.
    >
    > But, well, there's no reason they had to piggyback C++ exception
    > handling on top of SEH. Is there?


    There is no reason to do so, and they hadn't done that. There was a bug
    in an older compiler, which resulted in C++ code to catch even SEH
    exceptions (if SEH has been enabled), but that was no intention and has
    been corrected.

    > Would it have been reasonable to
    > extend the ABI to have SEH and C++ exceptions as different things?


    Yes - and they are a quite different. SEH is the exception handling
    built in the OS - it can be used by the compiler but mustn't.
    In MSVC++ it's not active anymore by default and IIRC the compiler
    switch /EHa is deprecated for new versions of the compiler - but I don't
    know for sure.

    SEH is used to handle OS exceptions like "access violations".

    How does Linux support handling of processor/system exceptions ? (not
    only calling a callback function - but also offering a complete
    infrastructure to handle and deal with exceptions).


    To make a long story short:

    SEH is only enabled if activated by the compiler switch /EHa. Otherwise
    the C++ compiler does ignore SEH and uses only C++ exception handling.
    If such an exception occurs the OS simply terminates the application and
    generates a dump file.


    >> 64 bit Windows exception handling (and the compiler generated code - VS
    >> C++) is quite different to the 32 bit one and the implementation should
    >> have the same low/none runtime overhead as under Linux.

    >
    > Last I tested, there's still a measurable overhead, whereas with gcc
    > on linux there is no measurable overhead.


    There is no overhead under Win64, same as under Linux.

    Perhaps you had some compiler switches active (which included SEH handling).
    Have you had a look at the generated assembly code and checked if SEH is
    disabled ?

    Andre
     
    Andre Kaufmann, Feb 4, 2011
    #17
  18. Debajit Adhikary

    Miles Bader Guest

    Leigh Johnston <> writes:
    > IIRC the last time I looked at the performance overhead of exceptions
    > in VC++ I found the following two things to contain overhead:
    >
    > 1) try/catch implementation
    > 2) throw
    >
    > i.e. there is no overhead in a function which contains a throw
    > statement but the throw does not actually occur and as this is the
    > most common case (lots of try/catch blocks or lots of throwing smell
    > of bad design) I decided it was not much of a concern.


    But remember that in typical C++ code you'll usually have many more
    _implicit_ try-catch blocks, inserted by the compiler to call
    the destructors of local objects.

    So you can't judge the cost simply by looking for "try" in your source code...

    -miles

    --
    Joy, n. An emotion variously excited, but in its highest degree arising from
    the contemplation of grief in another.
     
    Miles Bader, Feb 5, 2011
    #18
  19. On Feb 4, 9:49 am, Andre Kaufmann <> wrote:
    > On 04.02.2011 07:49, Joshua Maurice wrote:
    > > On Feb 3, 10:21 pm, Andre Kaufmann<> wrote:
    > >> 64 bit Windows exception handling (and the compiler generated code - VS
    > >> C++) is quite different to the 32 bit one and the implementation should
    > >> have the same low/none runtime overhead as under Linux.

    >
    > > Last I tested, there's still a measurable overhead, whereas with gcc
    > > on linux there is no measurable overhead.

    >
    > There is no overhead under Win64, same as under Linux.
    >
    > Perhaps you had some compiler switches active (which included SEH handling).
    > Have you had a look at the generated assembly code and checked if SEH is
    > disabled ?


    Let me find my test code, and let me rerun it. I'll post the command
    line options as well.

    ....

    Ok. Here we go. My test code is at the end. All results are from runs
    executed today.

    As I mentioned last time I did this and posted it to comp.lang.c++, I
    don't even want to claim anything besides there is measurable overhead
    when using exceptions and not throwing them, vs error return codes, vs
    no function output. Specifically, I don't want to answer "How much
    overhead?" - I just want to say "Some significantly measurable
    amount."

    Also, if you see any problems with my test, please say so, so I can
    fix it and rerun it.

    ----

    Win XP Pro, Service Pack 3,

    dxdiag info:
    OS: Microsoft Windows XP Professional (5.1, Build 2600),
    System Model: HP xw6600 Workstation
    Processor: Intel(R) Xeon(R) CPU E5405 @ 2.00 GHz (4 CPUs)
    Memory: 3328MB RAM

    Test results for one execution (with output rearranged):

    ...>jjtest.exe 60000 -10
    Manually Inlined Optimized Return Code : 3.609
    Manually Inlined Return Code : 3.609
    Inlineable Global Return Code : 5.64
    Inlineable Member Return Code : 3.625
    Virtual Return Code : 18.906
    Virtual Exception : 19.875
    Virtual Return Code Fake Try : 20.891

    I get similar results each time I run the test.

    Compile options according to the "Command Line" dialog box inside the
    VC Project dialog box:

    /Ox /Ob2 /Oi /Ot /Oy /GT /GL /I "C:\boost_1_35_0" /I "C:\Program Files
    \Java\jdk1.6.0_20\include" /I "C:\Program Files\Java
    \jdk1.6.0_20\include\win32" /I "C:\p4_ws\edit_2\base\include" /D
    "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MT" /D "BOOST_ALL_DYN_LINK" /D
    "NOMINMAX" /D "_VC80_UPGRADE=0x0710" /D "_AFXDLL" /D "_MBCS" /GF /FD /
    EHsc /MD /GS- /Fo"Release\\" /Fd"Release\vc90.pdb" /W4 /nologo /c /
    Wp64 /Zi /TP /errorReport:prompt

    Link options:

    /OUT:"Release/jjtest.exe" /INCREMENTAL:NO /NOLOGO /LIBPATH:"C:
    \boost_1_35_0\stage\lib" /LIBPATH:"C:\Program Files\Java
    \jdk1.6.0_20\lib" /LIBPATH:"C:\p4_ws\edit_2\base\target\pmcmnasrt\bin
    \Debug" /MANIFEST /MANIFESTFILE:"Release
    \jjtest.exe.intermediate.manifest" /MANIFESTUAC:"level='asInvoker'
    uiAccess='false'" /DEBUG /PDB:"Release/test.pdb" /SUBSYSTEM:CONSOLE /
    OPT:REF /OPT:ICF /LTCG /DYNAMICBASE:NO /MACHINE:X86 /
    ERRORREPORT:pROMPT jvm.lib

    ----

    dxdiag info:
    Operating System: Windows 7 Enterprise 64-bit (6.1, Build 7600)
    System Model: HP Z400 Workstation
    Processor: Intel(R) Xeon(R) CPU W3565 @ 3.20GHz (4 CPUs),
    ~3.2GHz
    Memory: 12288MB RAM

    Test results for one execution (with output rearranged):

    Manually Inlined Optimized Return Code : 2.136
    Manually Inlined Return Code : 2.143
    Inlineable Global Return Code : 2.124
    Inlineable Member Return Code : 2.128
    Virtual Return Code : 8.505
    Virtual Exception : 11.74
    Virtual Return Code Fake Try : 12.773

    I get similar results each time I run the test.

    Compile options:

    /Ox /Oi /Ot /Oy /GT /GL /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D
    "_UNICODE" /D "UNICODE" /FD /EHsc /MD /GS- /Gy /Za /Fo"x64\Release\\" /
    Fd"x64\Release\vc90.pdb" /W3 /nologo /c /Zi /TP /errorReport:prompt

    Link options:

    /OUT:"C:\Users\bkim\Desktop\test_solution\x64\Release
    \test_solution.exe" /INCREMENTAL:NO /NOLOGO /MANIFEST /
    MANIFESTFILE:"x64\Release\test_solution.exe.intermediate.manifest" /
    MANIFESTUAC:"level='asInvoker' uiAccess='false'" /DEBUG /PDB:"c:\Users
    \bkim\Desktop\test_solution\x64\Release\test_solution.pdb" /
    SUBSYSTEM:CONSOLE /OPT:REF /OPT:ICF /LTCG /DYNAMICBASE /NXCOMPAT /
    MACHINE:X64 /ERRORREPORT:pROMPT kernel32.lib user32.lib gdi32.lib
    winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib
    oleaut32.lib uuid.lib odbc32.lib odbccp32.lib

    ----

    Here's a linux test for comparison:

    psflor.informatica.com ~$ uname -a
    Linux <host-name> 2.6.18-128.el5 #1 SMP Wed Dec 17 11:41:38 EST 2008
    x86_64 x86_64 x86_64 GNU/Linux

    ~$ g++ -v
    Using built-in specs.
    Target: x86_64-redhat-linux
    Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --
    infodir=/usr/share/info --enable-shared --enable-threads=posix --
    enable-checking=release --with-system-zlib --enable-__cxa_atexit --
    disable-libunwind-exceptions --enable-libgcj-multifile --enable-
    languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --
    disable-dssi --enable-plugin --with-java-home=/usr/lib/jvm/java-1.4.2-
    gcj-1.4.2.0/jre --with-cpu=generic --host=x86_64-redhat-linux
    Thread model: posix
    gcc version 4.1.2 20080704 (Red Hat 4.1.2-44)

    psflor.informatica.com ~$ g++ -pthread -O3 foo.cpp

    Test results for one execution (with output rearranged):

    Manually Inlined Optimized Return Code : 5.21
    Manually Inlined Return Code : 5.21
    Inlineable Global Return Code : 5.21
    Inlineable Member Return Code : 5.21
    Virtual Return Code : 10.42
    Virtual Exception : 9.33
    Virtual Return Code Fake Try : 10.41

    I get similar results each time I run the test.

    ----

    I see no noticable difference between win 32 on x86 compared to win 64
    on AMD 64. Both have measurable overhead when working with exceptions,
    unlike modern gcc on Linux which has "basically" no measurable
    overhead with exception code on the not-thrown codepath.

    Moreover, the tests beautifully show the practical run time savings if
    you correctly use exceptions. The Virtual Exception test, which does
    not use a return code, runs measurably faster than both other Virtual
    tests which both do use return codes which are checked by the caller.
    The run time savings are because the caller doesn't have to execute a
    branch to test the return code. If the exception case is indeed
    "exceptional", then this can result in measurable speed improvements
    from less branches all over the place.

    However, sadly too many compiler writers and ABI writers missed this
    very important memo, which I think is core to the entire C++
    philosophy. C++ started initially with the additional of destructors
    to C - RAII, and consequently constructors. If you can't throw
    exceptions, then this quickly devolves down to noticably less "pretty"
    code, much more C-like, with error return codes all over the place,
    which I think greatly diminishes one of the best things about C++.

    So, you can still use exceptions and get the "pretty" code, but it
    comes at a cost. On some systems well written C++ code (which uses
    exceptions) will run /slightly/ faster than the C-style code with
    return codes, but on a lot of other systems, the situation is reversed
    - the well written C++code will run /a lot/ slower (relatively
    speaking).

    Overall, the "slightly faster" and "a lot slower" is quite a small
    portion of the overall runtime of a regular program. The test code
    below is perhaps the most contrived I could possibly make it, and even
    in this most contrived case I could only get a measurable difference
    of about 10% on windows 32 and AMD 64. For a real program, I expect
    that to be much much smaller, probably small enough to not worry about
    it except in the most extreme cases.

    PS: IIRC, on some crazy unix-like platform, I actually got the
    exception tests below to run 50% slower than the no-exception tests,
    which is impressively bad. Still, in a real program, I can't imagine
    even that horrible implementation would make the overall program run
    that much worse.

    ////
    //Start test

    #include <cstdlib>
    #include <ctime>
    #include <iomanip>
    #include <iostream>
    #include <sstream>
    #include <string>
    #include <vector>

    using namespace std;


    enum ReturnCodeT { Success = 1, Failure = 2 };

    class TestInterface
    {
    public:
    virtual void virtualCanThrow(int a, int b, int targetSum) = 0;

    virtual ReturnCodeT virtualReturnCode(int a, int b, int targetSum)
    = 0;

    ReturnCodeT inlineableReturnCode(int a, int b, int targetSum)
    { if (a + b == targetSum)
    return Failure;
    return Success;
    }
    };

    inline ReturnCodeT globalInlineableReturnCode(int a, int b, int
    targetSum)
    { if (a + b == targetSum)
    return Failure;
    return Success;
    }


    class TestImpl : public TestInterface
    {
    public:
    virtual void virtualCanThrow(int a, int b, int targetSum)
    { if (a + b == targetSum)
    throw 1;
    }
    virtual ReturnCodeT virtualReturnCode(int a, int b, int targetSum)
    { if (a + b == targetSum)
    return Failure;
    return Success;
    }
    };

    class TestImpl2 : public TestInterface
    {
    public:
    virtual void virtualCanThrow(int a, int b, int targetSum)
    { cout << "XXX" << endl;
    }
    virtual ReturnCodeT virtualReturnCode(int a, int b, int targetSum)
    { cout << "XXX" << endl;
    return Success;
    }
    };


    void testInlineableMemberReturnCode(TestInterface *& x, int
    loopIterations, int failIfEqualsNumber)
    { vector<int> vec;
    for (int i=0; i<loopIterations; ++i)
    { for (int j=0; j<loopIterations; ++j)
    { if (Failure == x->inlineableReturnCode(i, j,
    failIfEqualsNumber+3))
    { cout << "inlineable member return code, returned
    failure" << endl;
    x = new TestImpl2();
    }
    }
    }
    if (vec.size() && vec.back() == failIfEqualsNumber+3)
    { cout << "inlineable member return code, vector size
    comparison true" << endl;
    x = new TestImpl2();
    }
    }
    void testInlineableGlobalReturnCode(TestInterface *& x, int
    loopIterations, int failIfEqualsNumber)
    { vector<int> vec;
    for (int i=0; i<loopIterations; ++i)
    for (int j=0; j<loopIterations; ++j)
    { if (Failure == globalInlineableReturnCode(i, j,
    failIfEqualsNumber+4))
    cout << "inlineable global return code, returned failure"
    << endl;
    }
    if (vec.size() && vec.back() == failIfEqualsNumber+4)
    cout << "inlineable global return code, vector size
    comparison true" << endl;
    }
    void testVirtualReturnCodeFakeTry(TestInterface *& x, int
    loopIterations, int failIfEqualsNumber)
    { vector<int> vec;
    for (int i=0; i<loopIterations; ++i)
    { for (int j=0; j<loopIterations; ++j)
    { try
    { if (Failure == x->virtualReturnCode(i, j,
    failIfEqualsNumber+5))
    { cout << "virtual return code with fake try,
    returned failure" << endl;
    x = new TestImpl2();
    }
    } catch (...)
    { cout << "ERROR impossible exception caught" << endl;
    x = new TestImpl2();
    }
    }
    }
    if (vec.size() && vec.back() == failIfEqualsNumber+5)
    { cout << "virtual return code with fake try, vector size
    comparison true" << endl;
    x = new TestImpl2();
    }
    }
    void testVirtualReturnCode(TestInterface *& x, int loopIterations, int
    failIfEqualsNumber)
    { vector<int> vec;
    for (int i=0; i<loopIterations; ++i)
    { for (int j=0; j<loopIterations; ++j)
    { if (Failure == x->virtualReturnCode(i, j,
    failIfEqualsNumber+6))
    { cout << "virtual return code, returned failure" <<
    endl;
    x = new TestImpl2();
    }
    }
    }
    if (vec.size() && vec.back() == failIfEqualsNumber+6)
    { cout << "virtual return code, vector size comparison true" <<
    endl;
    x = new TestImpl2();
    }
    }
    void testVirtualException(TestInterface *& x, int loopIterations, int
    failIfEqualsNumber)
    { vector<int> vec;
    for (int i=0; i<loopIterations; ++i)
    { for (int j=0; j<loopIterations; ++j)
    { try
    { x->virtualCanThrow(i, j, failIfEqualsNumber+7);
    } catch (int & )
    { cout << "virtual exception, exception caught" << endl;
    x = new TestImpl2();
    }
    }
    }
    if (vec.size() && vec.back() == failIfEqualsNumber+7)
    { cout << "virtual exception, vector size comparison true" <<
    endl;
    x = new TestImpl2();
    }
    }
    void testManuallyInlinedReturnCode(TestInterface *& x, int
    loopIterations, int failIfEqualsNumber)
    { vector<int> vec;
    for (int i=0; i<loopIterations; ++i)
    { for (int j=0; j<loopIterations; ++j)
    { ReturnCodeT retVal;
    if (i + j == failIfEqualsNumber)
    retVal = Failure;
    else
    retVal = Success;
    if (retVal == Failure)
    { cout << "manually inlined return code, failure
    'returned'" << endl;
    x = new TestImpl2();
    }
    }
    }
    if (vec.size() && vec.back() == failIfEqualsNumber)
    { cout << "manually inlined return code, vector size comparison
    true" << endl;
    x = new TestImpl2();
    }
    }
    void testManuallyInlinedOptimizedReturnCode(TestInterface *& x, int
    loopIterations, int failIfEqualsNumber)
    { vector<int> vec;
    for (int i=0; i<loopIterations; ++i)
    for (int j=0; j<loopIterations; ++j)
    { if (i + j == failIfEqualsNumber+9)
    { cout << "manually inlined and optimized return code,
    failure 'returned'" << endl;
    x = new TestImpl2();
    }
    }
    if (vec.size() && vec.back() == failIfEqualsNumber+9)
    { cout << "manually inlined and optimized return code,
    vector size comparison true" << endl;
    x = new TestImpl2();
    }
    }

    int main(int argc, char ** argv)
    {
    if (argc != 3 && argc != 4)
    return 1;

    int loopIterations;
    if ( ! (stringstream(argv[1]) >> loopIterations))
    return 1;

    int failIfEqualsNumber;
    if ( ! (stringstream(argv[2]) >> failIfEqualsNumber))
    return 1;

    TestInterface * x;

    if (argc == 3)
    x = new TestImpl();
    else
    x = new TestImpl2();

    vector<clock_t> retVal;

    /* Executed in random order through function pointers because I
    noticed some bizarre optimizations on some platforms which heavily
    optimized the first function, but not the last functions, when the
    functions were called directly. I have no clue what was happening
    there. */

    typedef void (*TestFuncType)(TestInterface *&, int, int);
    vector<pair<string, TestFuncType> > remainingTests;

    remainingTests.push_back(make_pair(string("Inlineable Member
    Return Code"), & testInlineableMemberReturnCode));
    remainingTests.push_back(make_pair(string("Inlineable Global
    Return Code"), & testInlineableGlobalReturnCode));
    remainingTests.push_back(make_pair(string("Virtual Return Code
    Fake Try"), & testVirtualReturnCodeFakeTry));
    remainingTests.push_back(make_pair(string("Virtual Return Code"),
    & testVirtualReturnCode));
    remainingTests.push_back(make_pair(string("Virtual Exception"), &
    testVirtualException));
    remainingTests.push_back(make_pair(string("Manually Inlined Return
    Code"), & testManuallyInlinedReturnCode));
    remainingTests.push_back(make_pair(string("Manually Inlined
    Optimized Return Code"), & testManuallyInlinedOptimizedReturnCode));

    srand(time(0));

    while (remainingTests.size())
    {
    int index = rand() % remainingTests.size();
    pair<string, TestFuncType> thisTest = remainingTests[index];
    remainingTests.erase(remainingTests.begin() + index);

    clock_t t0 = clock();
    (*thisTest.second)(x, 1, -10);
    clock_t t1 = clock();
    (*thisTest.second)(x, loopIterations, failIfEqualsNumber);
    clock_t t2 = clock();

    cout << setw(40) << thisTest.first << " : " << double(t2 -
    t1) / double(CLOCKS_PER_SEC) << endl;
    }
    }
     
    Joshua Maurice, Feb 5, 2011
    #19
  20. Debajit Adhikary

    Miles Bader Guest

    Leigh Johnston <> writes:
    >> But remember that in typical C++ code you'll usually have many more
    >> _implicit_ try-catch blocks, inserted by the compiler to call
    >> the destructors of local objects.
    >>
    >> So you can't judge the cost simply by looking for "try" in your
    >> source code...

    >
    > I wasn't. IIRC there was no overhead in a function which contained a
    > throw statement if the throw was not invoked even taking RAII into
    > account.


    No, my point is that in a VC++ 32-bit style ABI there's overhead in the
    no-exception-thrown path for every use of RAII if an exception can
    _possibly_ occur, compared to a more modern ABI.

    Since use of RAII is of course very common in typical C++ code, this is
    can be an issue.

    > The path that needs to be fast is the most common path; the
    > most common path is the path in which an exception is not thrown


    Of course; but this path _is_ often affected, because of RAII (despite
    the lack of explicit "try" statements).

    -Miles

    --
    `Life is a boundless sea of bitterness'
     
    Miles Bader, Feb 5, 2011
    #20
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Kerri
    Replies:
    2
    Views:
    13,043
    Kevin Spencer
    Oct 27, 2003
  2. Replies:
    15
    Views:
    7,579
    Roedy Green
    Sep 8, 2005
  3. Pierre Rouleau
    Replies:
    3
    Views:
    6,785
    Siemel Naran
    Mar 4, 2005
  4. Mr. SweatyFinger
    Replies:
    2
    Views:
    2,030
    Smokey Grindel
    Dec 2, 2006
  5. Replies:
    11
    Views:
    1,212
    Default User
    Mar 5, 2007
Loading...

Share This Page