2
\$\begingroup\$

I've written a small C++ utility that is intended for measuring the execution time of blocks of code, and I'd appreciate your feedback on it. I'm an experienced programmer, but a novice C++ programmer.

The code is written with performance in mind, and uses a libcuckoo hash table to support multiple concurrent calling threads.

The timerexample.cc file contains a simple usage example.

Hopefully the documentation is clear, but if you need any more information, please let me know.

codetimer.h:

#ifndef TEST_TIMER_H
#define TEST_TIMER_H

#include <chrono>
#include "libcuckoo/cuckoohash_map.hh"

struct RecordedValue;

/**
 * Permits measuring of timings in C++ code. Call the \p record function to record a timing, and the
 * \p printStats function to print recorded timings.
 */
class CodeTimer {
private:
    static cuckoohash_map<std::string, std::unique_ptr<RecordedValue>> timerMap;
public:
    /**
     * Record a timing, with the specified name and start time. The end time is (approximately) the time at which
     * this method is called. Timings with the same name are aggregated.
     * @param name The name of the timing to be recorded.
     * @param start The time that we started recording.
     */
    static void record(std::string name, std::chrono::high_resolution_clock::time_point start);
    /**
     * Prints previously recorded timings to stdout. A line is printed for each unique name that was passed to the
     * \p record function, along with the total time and number of occurrences for that name. Timings are printed
     * in descending order, according to the total time.
     */
    static void printStats();
};

#endif //TEST_TIMER_H

codetimer.cc:

#include <iostream>
#include <chrono>
#include <vector>
#include <algorithm>
#include "codetimer.h"

using namespace std::chrono;

struct RecordedValue {
public:
    double totalSecs {0};
    long occurrences {0};
    std::string name;
};

void CodeTimer::record(std::string name, std::chrono::high_resolution_clock::time_point start) {
    auto stop = high_resolution_clock::now();
    auto diff = duration_cast<duration<double>>(stop - start);
    auto updateFunction = [&diff](std::unique_ptr<RecordedValue>& existingValue) {
        existingValue->occurrences++;
        existingValue->totalSecs += diff.count();
    };
    // We try a regular update before calling upsert here. We assume that updates will be much
    // more common that insertions, and this avoids creating unnecessary RecordedValue instances
    // when performing updates. Unnecessary RecordedValue instances will only be created when
    // there are race conditions between threads, which is likely to be rare.
    if (!CodeTimer::timerMap.update_fn(name, updateFunction)) {
        RecordedValue *newValue = new RecordedValue();
        newValue->totalSecs = diff.count();
        newValue->occurrences = 1;
        newValue->name = name;
        CodeTimer::timerMap.upsert(name, updateFunction, std::unique_ptr<RecordedValue>(newValue));
    }
}

void CodeTimer::printStats() {
    std::vector<RecordedValue*> vals;
    vals.reserve(timerMap.size());
    for (const auto &item: CodeTimer::timerMap.lock_table()) {
        vals.push_back(item.second.get());
    }
    std::sort(begin(vals), end(vals), [](RecordedValue* t1, RecordedValue* t2) {
        return t1->totalSecs > t2->totalSecs;
    });
    for (auto val : vals) {
        std::cout << val->name << ": total=" << val->totalSecs << "; occurrences=" << val->occurrences;
        std::cout << std::endl;
    }
}

cuckoohash_map<std::string, std::unique_ptr<RecordedValue>> CodeTimer::timerMap = {};

timerexample.cc:

#include <chrono>
#include <iostream>
#include <thread>
#include "codetimer.h"

void sleepMillis(long milliseconds) {
    std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
}

int main () {

    auto start = std::chrono::high_resolution_clock::now();
    sleepMillis(100);
    CodeTimer::record("key1", start);

    start = std::chrono::high_resolution_clock::now();
    sleepMillis(200);
    CodeTimer::record("key2", start);

    start = std::chrono::high_resolution_clock::now();
    sleepMillis(300);
    CodeTimer::record("key1", start);

    CodeTimer::printStats();

    return 0;
}
\$\endgroup\$
  • \$\begingroup\$ There is probably some use for this, but if you're interested in timing a program for optimizing needs, try a profiler! \$\endgroup\$ – jacwah Apr 12 '16 at 17:19
  • \$\begingroup\$ I think that profilers have their uses, but I've found this method of instrumentation to be very effective, particularly in production environments where it's often not practical to use a profiler. \$\endgroup\$ – Neil Apr 12 '16 at 18:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.