0

Consider the loop below. This is a simplified example of a problem I am trying to solve. I want to limit the number of times doSomething function is called in each second. Since the loop works very fast, I thought I could use a rate limiter. Let's assume that I have found an appropriate value by running it with different x numbers.

unsigned int incrementionRate = x;
unsigned int counter == 0;

while (true) {

    double seconds = getElapsedSeconds();
    print(seconds);

    counter = (counter + 1) % incrementionRate;
    if (counter == 0) {
        doSomething();
    }
}

I wonder if the number of calls to doSomething function would be less if I was working on a lower clock rate. In that case, I would like to limit the number of calls to doSomething function to once for each second. The second loop I have written is below.

float epsilon = 0.0001;
while (true) {

    double seconds = getElapsedSeconds();
    print(seconds);

    if (abs(seconds - floor(seconds)) <= epsilon) {
        doSomething();
    }
}

Would that do the trick for different clock cycles or are there still problems? Also, I would like to know if there is a better way of doing this. I have never worked with clock rates before and trying to understand how concerns differ when working with limited resources.

Note: Using sleep is not an option.

Can Bayar
  • 497
  • 4
  • 16
  • I'd rather divide the second by number of times to get a concrete offset and then [sleep until](https://en.cppreference.com/w/cpp/thread/sleep_until) next period elapsed. – Aconcagua Jun 06 '21 at 20:10
  • what is `getElapsedSeconds` and `print` ? I don't understand how either of the codes calls `doSomething` once per second. In the first there seems to be no (accurate) relation between passed time and the calls to `doSomething` as it only counts the loop iterations – 463035818_is_not_an_ai Jun 06 '21 at 20:13
  • Is the rest of this `while` loop supposed to keep looping and only call `doSomething()` once a second - or would sleeping be ok? – Ted Lyngmo Jun 06 '21 at 20:13
  • Oh, I forgot to note that using sleep is not an option, my bad. – Can Bayar Jun 06 '21 at 20:20
  • So the rest of the function will still be looping? – Ted Lyngmo Jun 06 '21 at 20:24
  • Yes, let's say it is. – Can Bayar Jun 06 '21 at 20:29
  • 2
    The second loop would only work on a baremetal /real time OS, and if you were in such a case, you could use timer interrupts. On the generic case, you're not guaranteed that getElapsedSeconds() will be called inside the "espsilon" tiemspan, so you'd miss a frame. There is no platform agnostic solution to this. – xvan Jun 06 '21 at 20:30
  • I see, that's what I was worried about epsilon check, indeed. – Can Bayar Jun 06 '21 at 20:32
  • 1
    Could you give details about the platform you'll be using? Why sleep is not an option? – xvan Jun 06 '21 at 20:33

3 Answers3

4

If I understand the issue proberly, you could use a std::chrono::steady_clock that you just add a second to every time a second has passed.

Example:

#include <chrono>

auto end_time = std::chrono::steady_clock::now();

while (true) {
    // only call doSomething once a second
    if(end_time < std::chrono::steady_clock::now()) {
        doSomething();
        // set a new end time a second after the previous one
        end_time += std::chrono::seconds(1);
    }

    // do something else
}

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
2

Ted's answer is fine if you are really doing something else in the loop; if not, though, this results in a busy wait which is just consuming up your CPU for nothing.

In such a case you should rather prefer letting your thread sleep:

    std::chrono::milliseconds offset(200);
    auto next = std::chrono::steady_clock::now();
    for(;;)
    {
        doSomething();
        next += offset;
        std::this_thread::sleep_until(next);
    }

You'll need to include chrono and thread headers for.

Aconcagua
  • 24,880
  • 4
  • 34
  • 59
  • This has far too much faith in the precision of sleep until; if you care about precision at all, sleep until 10s of ms before the event, then spin. However, I like its lack of error accumulation. – Yakk - Adam Nevraumont Jun 06 '21 at 21:11
  • @Yakk-AdamNevraumont I did not assume such high precision requirements, as not mentioned in question either. With typical multi-threaded OS and time slices of 10 ms or similar we likely won't be able to achieve it anyway even with 100 us time buffer. Micro controller with high precision oscillator and timer interrupt might be better choice then. But as question got edited excluding this solution, the answer got obsolete anyway... It might still be useful for other readers, so I won't delete it. – Aconcagua Jun 07 '21 at 04:22
0

I decided to go with a much more simple approach at the end. Used an adjustable time interval and just stored the latest update time, without introducing any new mechanism. Honestly, now I don't know why I couldn't think of it at first. Overthinking is a problem. :)

double lastUpdateTimestamp = 0;
const double updateInterval = 1.0;

while (true) {

    double seconds = getElapsedSeconds();
    print(seconds);

    if ((elapsedSeconds - lastUpdateTimestamp) >= updateInterval) {
        doSomething();
        lastUpdateTimestamp = elapsedSeconds;
    }
}
Can Bayar
  • 497
  • 4
  • 16