Home > Blockchain >  How to save bounded-length strings as quickly as possible for a timing mechanism?
How to save bounded-length strings as quickly as possible for a timing mechanism?

Time:07-15

I have a thin wrapper around rdtsc that I use as a timer. It supports "stepping" and all timestamps are saved in a std::array (you specify when creating the timer how many steps you will make). That way, you can do something like,

void funcToTime() {
    timer<2> t;
    ...
    t.start();
    ...
    t.step();
    ...
    t.step();
    ...
    t.end();
}

And the timer will write the rdtsc at the start, each of the two steps, and the end, into a file for you to look at.

What I want to do is extend this to be able to add tags to the timer,

void funcToTime() {
    // second template param is the max tag length
    timer<2, 10> t;
    ...
    t.start();
    ...
    // "hi" is a tag
    t.step("hi");
    ...
    t.step("bye");
    ...
    t.end();
}

And now in the file, I will be able to see the tags next to the steps (rdtsc timestamps).

Of course this must be as fast as possible, or else the timer is inaccurate and useless. I've been wracking my brain for how to do this but can't think of how to do it quickly.

My only idea is to maintain a std::array<MaxTagLen, NumSteps> that I write the tags into, but each step will now incur a string copy which is super slow, even when the strings are small enough to be in the SSO buffer. I don't think there is any templating magic I can do because these tags may not be available at compile-time, even if I know their max length at compile-time.

Any ideas?

CodePudding user response:

A string literal evaluates to the address at which that literal is stored in memory. As such, there's no need to copy the string in response to each step call.

template <class N>
class Timer { 

    struct TimeRecord {
        unsigned long long timestamp;
        char const *tag;
    };

    std::array<TimeRecord, N> data;
    unsigned int current = 0;

    std::ostream &output;

public:

    Timer(std::ostream &os) : output(os) {}

    void step(unsigned long long const &ts, char const *tag) {
        data[current  ] = TimeRecord{ts, tag};
    }

    void end() { 
        for (unsigned i=0; i<current; i  )
            output << data[i].timestamp << "\t" << data[i].tag << "\n";
    }
};

This does impose a limitation on what you pass as the tag. The pointer you pass has to remain valid as until after end() finishes execution. As long as you pass string literals, that's no problem at all--they have static storage duration. But if you define a Timer in one function, then it calls another function, and somebody passes the address of an array of char defined in that function, like this:

void foo(Timer &t) { 
    // note use of arrays here:
    char tagS[] = "start foo";
    char tagE[] = "stop foo";
    
    t.step(tagS);

    // do foo stuff

    t.step(tagE);
}

Then you'd have a problem. But as long as you stick to string literals:

    char const *tagS = "start foo";
    char const *tagE = "stop foo";

// or:

    t.step(a, "some tag");

...you have no problem at all. Also note that since you're only storing the address, this eliminates any requirement to store the maximum tag length either.

  • Related