NOTE:
These are a little sketchy...I'll fill in details 'soon'.
ctime has a function called time() which takes the single argument nullptr. (This argument never needs to change, but C had a poor concept of default arguments so we're stuck with typing it.) time(nullptr) returns the number of seconds that have elapsed since midnight (on January 1st of 1970 Greenwich Mean Time -- 6 hours ahead of Central Mean Time).
To take a timing, therefore, you can call it once to find out when you started, perform your activity, then call time again to find out when you got done:
time_t start, end; start = time(nullptr); // do something you want timed end = time(nullptr); cout << "That took " << (end-start) << " seconds." << endl;
This will give you the number of seconds elapsed between the two calls. Timing things like an end-user's typing can be accomplished easily with this pattern. Of course, unless the action is fairly slow (like the user), it may not even take a whole second...
Of course, many events that happen inside the computer happen SO fast that this difference will be 0. To alleviate this problem, you can perform your action multiple times and take an average timing:
time_t start, end; start = time(nullptr); for (i = 0; i != LOTS; i++) { // do something you want timed } end = time(nullptr); cout << "That took " << ((end-start)/static_cast<double>(LOTS)) << " seconds." << endl;
One thing to watch out for, of course, is making the timed event in the loop consistent. For example, if you were trying to time a sorting algorithm with a cutoff condition (one which takes only one loop when the data is already sorted), timing this sort directly 10000 times would really only time the whole algorithm once and the single cutoff loop 9999 times. A single loop through an vector isn't very slow -- nearly instantaneous for most vectors. So your timing is likely to still be 0.
To adjust for this circumstance, you can do one of two things: replace the original vector contents between sorts or randomly shuffle the data between sorts. Although it may not seem like it, either of these approaches will give you comparable timings. The problem is, this copying or shuffling must be done inside the timing loop and so your timing will reflect not only the sorting done, but the vector re-organization as well. To compensate for this, simply time the re-organization separately and subtract this from your 'sort' timing:
time_t start, end; double reorg_time; start = time(nullptr); for (i = 0; i != LOTS; i++) { // re-organize vector } end = time(nullptr); reorg_time = (end-start)/static_cast<double>(LOTS); start = time(nullptr); for (i = 0; i != LOTS; i++) { // re-organize vector // sort vector } end = time(nullptr); cout << "That took " << ((end-start)/static_cast<double>(LOTS) - reorg_time) << " seconds." << endl;
Even if you are comparison-timing many sorts, the re-organization timing only needs to be done once.
You can also improve your timing by making the data larger -- not the actual values, but the number of data elements. If the thing you are timing is vector processing, for instance, you can increase the number of elements in the vector that are being processed.
This method of timing program execution also uses the time(nullptr) function from the ctime library, but it takes a different approach. In the previous method, we ran our code many, many times and calculated how many seconds this took -- on average. Another way to look at this idea is that we aren't sure how many times it will take running our code to make a second, so we've instead just run our code long enough to make many seconds and divided out how many times we ran to get an average:
seconds --------- = sec/run runs
However, since we expect our code to run multiple times each second, we could simply run it enough times so that the current second changes. Then, by counting how many runs that took (i.e. how many runs we did in a second), we can calculate:
1 ------------- = sec/run runs -------- second
That's just what we wanted and it only took us a second instead of the several/many seconds the previous method took!
Well, sort-of... In fact, because of all the things computers are always doing, we'll not likely get an accurate reading all the time. To fix this, we should take several such readings and then find the median number of times the code ran in a second. (See the appropriate lab for information about taking a median of a set of data.) Even so, this method can still be done in a consistent number of seconds (say 9-10) instead of the more arbitrary time that the previous timing method took (sometimes less, but more than likely more).
We should still take account of re-organization/setup time for this method, but we can use this method to time both the setup and actual code running. *smile*
So, what might this look like? Something like this, perhaps:
vector<unsigned long> counts; double reorg_time; unsigned long count; time_t start; for (short rep = 0; rep != 9; rep++) { count = 0; start = time(nullptr); do { // re-organize/setup count++; } while (time(nullptr) == start); counts.push_back(count); } // sort counts vector reorg_time = 1.0/counts[4]; // 9/2 == 4 with integer division counts.clear(); for (short rep = 0; rep != 9; rep++) { count = 0; start = time(nullptr); do { // re-organize/setup // do code to be timed count++; } while (time(nullptr) == start); counts.push_back(count); } // sort counts vector cout << "That took " << (1.0/counts[4] - reorg_time) << " seconds." << endl;
You'll have to fill in the sorting code yourself, of course. *grin*
(The following material is advanced and is an introduction to some CSC122 material. Read at your own risk.)
Once we study pointers, you'll come to realize that nullptr is the guaranteed invalid address. That should lead you to the realization that time actually is taking a pointer to something as its argument. It is, in fact, taking a pointer to a time_t value. (time_t is typically a long integer.) If provided, time would place the number of seconds in the address specified in addition to returning it. You can do a man time command at the terminal ($-prompt) in unix to learn more.
Historical note: This is sort of the way C did default arguments. Having no such mechanism, they'd simply pass a pointer to a value instead of a value itself. If the caller provided nullptr, you went with the default. If the caller provided a valid address, you could use their value. A bit cheesy, but it worked. (Of course, C++'s default argument mechanism works much better and so you should use it instead ...when coding in C++.)