Re: Why does std::stack::pop() not throw an exception if the stack is empty?
On Feb 4, 9:49 am, Andre Kaufmann <akfmn...@t-online.de> wrote:
On 04.02.2011 07:49, Joshua Maurice wrote:
On Feb 3, 10:21 pm, Andre Kaufmann<akfmn...@t-online.de> 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;
}
}