Re: Are throwing default constructors bad style, and if so, why?
On Sep 26, 7:52 pm, Andre Kaufmann <akfmn...@t-online.de> wrote:
JoshuaMaur...@gmail.com wrote:
[...]
There are 3 golden rules regarding code performance one should follow:
a) Measure
b) Measure
c) Measure
I disagree. That's going too far. For example, if I need an algorithm
to sort data, and I have no guarantees on the order of the incoming
data, I'm going to use std::map. It may not be the fastest thing
available, but I do know that it will be faster that using a sorted
std::vector for any non-trivial case.
Don't get me wrong - measuring can't replace good design and choosing
the right algorithm for a specific task.
It should only replace assumptions:
E.g.:
a) I assumed that cout isn't slower than printf - rather the contrary
b) I assumed that iostreams aren't that slower than C file functions
You cannot measure everything in the real world. Now, this is science,
so everything is independently verifiable, but you don't verify the
wheel every time you design a car.
I've heard this often. Some implementations do implement and compile
the iostreams poorly. Now, technically, with a crazy awesome compiler,
as the types are statically available, the compiler could do better
with iostreams. However, from what little I've read, this isn't true
in the real world in general. The reasons include (but are not limited
to) printf has to parse the format string at runtime, iostreams have
some virtual call overhead to deal with in their underlying buffer,
iostream relies upon good lining of the operator<< and >>, etc. This
is a topic unto itself on which I'm ill prepared to speak, so I'll let
it be with just that.
You can't measure everything for sure - but how often is an obvious
faster algorithm measured to be really faster ?
And sometimes the measures are done only once for a small example and
not for the released code.
Either way, there is no general consensus that yes, compilers do
implement iostreams as well as printf, so your counter-example to my
argument is somewhat flawed.
I'm talking about basic asymptotic analysis and other equivalently
simply analysis, like don't compute the same value twice in a row. If
it takes no more programmer time to do it with the known faster
algorithm, and it's just as simple, modular, changeable, etc., then
use the faster algorithm.
On the other side it shouldn't be exaggerated. A developer should have a
basic knowledge regarding performance of algorithms and code/cpu and
then he should concentrate on the hot spots and not waste too much time
to measure the performance of code which isn't relevant.
By the same token, I know that if I add a bunch of unused code to the
end of my executable, I know it won't affect runtime performance in a
system with virtual memory. The unused pages of the executable will
stay paged out to disk, requiring no runtime cost at all.
I must apologize as I was unable to replicate my "minimal" testing
which showed visual studios 2005 handled exceptions properly, that is
with the table method and ~0 runtime overhead if you don't throw.
Googling for information has been tedious. The best source of
information I can find is this year old blog from MS
http://blogs.msdn.com/vcblog/archive/2007/08/10/the-future-of-the-c-language.aspx
In it they explain they still use the "code method" to implement
exceptions, though on x64 platforms they use the table approach. Maybe
I did my previous testing on an x64 machine. My recent testing was on
my 32 bit laptop.
O.k. let's try to measure a small sample:
###################
#### sample1: #####
class error : public std::exception
{
public: error(const char* whatInit) : what(whatInit) {}
std::string what;
};
int value = 100;
int calc = 0;
void foo()
{
calc+= value; // only to reduce optimizer impact
if (value <= 1000) return;
if (value > 10000) throw error("value larger 10000");
if (value > 1000) throw error("value larger 1000");
}
void foomain() { foo(); }
###################
#### sample2: #####
int fox()
{
calc+= value;
if (value <= 1000) return 0;
if (value > 10000 ) return 2;
if (value > 1000) return 1;
}
bool foxmain()
{
int result = fox();
switch (result)
{
case 0: return true;
case 1: { cout << "value larger 1000" << endl; break; }
case 2: { cout << "value larger 10000" << endl; break; }
}
return false;
}
Sample1 should be slightly faster, because basically the code is the
same, but sample 1 uses exceptions and returns no value to be evaluated.
And additionally the exception code isn't executed and should have no
impact.
Results (calling the functions foomain, foxmain 1000000 times)
for cpp compiler (Windows) a:
Sample1: 2291 ms
Sample2: 801 ms
for cpp compiler (Windows) b:
Sample1: 800 ms
Sample2: 601 ms
Now replacing the lines in sample1:
if (value > 10000) throw error("value larger 10000");
if (value > 1000) throw error("value larger 1000");
by
if (value > 10000) throw "value larger 10000";
if (value > 1000) throw "value larger 1000";
gives the following result for compiler a):
Sample1: 823 ms
Sample2: 801 ms
Obviously the exception code had an impact.
It might be a lame example and not a proof for more complex programs - I
know. But what about the general assumption exceptions to be at least
not slower - will that be generally true for complex programs too, if
it's not (generally) true for a small example ?
I would love to see what happens when the loop is moved inside the
program. I would suspect you're measuring not what you want, that
you're measuring startup and teardown time of the process, not the
difference between return codes and exceptions.
Moreover, I think there's another problem to your example. In both
cases you have an if check, one if to determine if you're going to
throw an exception, the other if you're going to return an error code.
In the return code case, there's another if statement to check the
returned value. I'd expect that the return code case would have one of
those two if statements optimized away with decent inlining. Thus it's
probably not a good example of exceptions vs error return codes in the
real world.
I'm guessing that a virtual function call would inhibit inlining in
this case. I'll do a quick test to see if my inlining suspicion is
correct, and how exceptions fare.
(20 min later)
Confirmed and confirmed. I got on a company Linux 64 box with g++
3.4.3. (Yeah I know my company needs to upgrade.) Here's my source:
// **** **** **** **** ****
#include <iostream>
#include <ctime>
#include <sstream>
using namespace std;
class TestInterface
{
public:
enum ReturnCodeT { Success = 1, Failure = 2 };
virtual void testExceptions(int a, int b, int targetSum) = 0;
virtual ReturnCodeT testReturnCodes(int a, int b, int targetSum) =
0;
ReturnCodeT testReturnCodes_2(int a, int b, int targetSum)
{ if (a + b == targetSum)
return Failure;
return Success;
}
};
class TestImpl : public TestInterface
{
public:
virtual void testExceptions(int a, int b, int targetSum)
{ if (a + b == targetSum)
throw 1;
}
virtual ReturnCodeT testReturnCodes(int a, int b, int targetSum)
{ if (a + b == targetSum)
return Failure;
return Success;
}
};
int main(int argc, char ** argv)
{
if (argc != 3)
return 1;
int arg1;
if (! ( stringstream(argv[1]) >> arg1))
return 1;
int arg2;
if (! ( stringstream(argv[2]) >> arg2))
return 1;
TestInterface * x = new TestImpl();
clock_t t0 = clock();
for (int i=0; i<arg1; ++i)
for (int j=0; j<arg1; ++j)
if (TestInterface::Failure == x->testReturnCodes(i, j, arg2))
cout << "Failure returned (1)" << endl;
clock_t t1 = clock();
for (int i=0; i<arg1; ++i)
for (int j=0; j<arg1; ++j)
{ try
{ x->testExceptions(i, j, arg2);
} catch (int & )
{ cout << "Exception caught" << endl;
}
}
clock_t t2 = clock();
for (int i=0; i<arg1; ++i)
for (int j=0; j<arg1; ++j)
if (TestInterface::Failure == x->testReturnCodes_2(i, j, arg2))
cout << "Failure returned (2)" << endl;
clock_t t3 = clock();
cout << "Not-inlined return codes "
<< double(t1 - t0) / double(CLOCKS_PER_SEC)
<< endl;
cout << "Exceptions "
<< double(t2 - t1) / double(CLOCKS_PER_SEC)
<< endl;
cout << "Inlineable return codes "
<< double(t3 - t2) / double(CLOCKS_PER_SEC)
<< endl;
}
// **** **** **** **** ****
[prompt]$ g++ foo.cpp -O3 -fomit-frame-pointer
[prompt]$ ./a.out 60000 -1
Not-inlined return codes 9.5
Exceptions 8.69
Inlineable return codes 4.07
I also swapped the order of the tests to help make sure that the tests
weren't otherwise measuring some sort of startup time, and the results
were the same.
Now, I admit this is a somewhat trivial example. I never claimed that
exceptions would make your program noticeably faster in a majority of
cases. I will claim that properly used with a non-POS compiler (e.g.
not visual studios), they can make your program faster, and if
properly used, they will almost never make your program slower.
The "measure, measure, measure" rule is often misapplied. You should
always use the correct data structures and algorithms the first time.
Don't just use a linear search and say "Oh, it's ok. We'll measure it
later to see how bad it is."
It seems to be often obvious. But a linear search might be faster than a
binary search for small lists. For an experience developer this might be
obvious too. An unordered map should be faster than a normal map too.
But should it generally be used ?
The distinction is implementing the code with the binary search is
just as easy, flexible, correct, etc., than writing it with the linear
search, so prefer the better algorithm.
The better algorithm has some downsides, or we wouldn't have and use
std::list ? Would we ?
When is it better to use an vector due to cache locality than using a map ?
Your example of list vs vector vs map shows that you do not understand
the basics of algorithms, or that you are purposely misconstruing my
statements. There is no single best. Best is defined in terms of the
operations you're going to do on the data.
For example, if you need to sort input data of any nontrivial size
given no information on its starting order, then you should use
std::map instead of std::vector. If you're just going to store some
input data, perform some operations on each individually, then dump it
somewhere, then use a std::vector. If you're going to do a lot of
inserts and removes from the middle, or splicing, etc., then use
std::list.
By a (not very) similar token, I like exceptions over error return
codes for stuff like "out of memory". It simplifies my life as a
programmer, it allows me to write less code, and it gives me
(slightly) faster code.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]