Расчет оставшегося времени в C ++ 11 - PullRequest
6 голосов
/ 03 марта 2012

Я пишу класс индикатора выполнения, который выводит обновленный индикатор выполнения каждые n тиков до std::ostream:

class progress_bar
{
public:
  progress_bar(uint64_t ticks)
    : _total_ticks(ticks), ticks_occured(0),
      _begin(std::chrono::steady_clock::now())
  ...
  void tick()
  {
    // test to see if enough progress has elapsed
    //  to warrant updating the progress bar
    //  that way we aren't wasting resources printing
    //  something that hasn't changed
    if (/* should we update */)
    {
      ...
    }
  }
private:
  std::uint64_t _total_ticks;
  std::uint64_t _ticks_occurred;
  std::chrono::steady_clock::time_point _begin;
  ...
}

Я бы также хотел вывести оставшееся время. Я нашел формулу в другой вопрос , в котором говорится, что оставшееся время (имена переменных изменены в соответствии с моим классом):

time_left = (time_taken / _total_ticks) * (_total_ticks - _ticks_occured)

Части, которые я хотел бы заполнить для своего класса, это time_left и time_taken, используя новый заголовок <chrono> в C ++ 11.

Я знаю, что мне нужно использовать std::chrono::steady_clock, но я не уверен, как интегрировать его в код. Я предполагаю, что лучший способ измерить время - это std::uint64_t в наносекундах.

Мои вопросы:

  1. Есть ли в <chrono> функция, которая преобразует наносекунды в std::string, скажем что-то вроде "3m12s"?
  2. Должен ли я использовать std::chrono::steady_clock::now() каждый раз, когда обновляю свой индикатор выполнения, и вычитать его из _begin, чтобы определить time_left?
  3. Есть ли лучший алгоритм для определения time_left

Ответы [ 2 ]

7 голосов
/ 03 марта 2012

Есть ли функция, которая преобразует наносекунды в std :: string, сказать что-то вроде "3m12s"?

Нет. Но я покажу вам, как вы можете легко это сделать ниже.

Должен ли я использовать std :: chrono :: stable_clock :: now () каждый раз, когда обновляюсь мой индикатор выполнения, и вычтите это из _begin, чтобы определить time_left?

Да.

Есть ли лучший алгоритм для определения таймфрейма

Да. Смотри ниже.

Редактировать

Я изначально неверно истолковал «тики» как «такты часов», когда на самом деле «тики» имеют единицы работы и _ticks_occurred/_total_ticks можно интерпретировать как% job_done. Поэтому я изменил предложенное значение progress_bar ниже соответственно.

Я считаю, что уравнение:

time_left = (time_taken / _total_ticks) * (_total_ticks - _ticks_occured)

неверно. Он не проходит проверку работоспособности: если _ticks_occured == 1 и _total_ticks велико, то time_left приблизительно равно (хорошо, немного меньше) time_taken. Это не имеет смысла.

Я переписываю вышеприведенное уравнение так:

time_left = time_taken * (1/percent_done - 1)

, где

percent_done = _ticks_occurred/_total_ticks

Теперь, когда percent_done приближается к нулю, time_left приближается к бесконечности, а когда percent_done приближается к 1, 'time_left приближается к 0. Когда percent_done равно 10%, time_left равно 9*time_taken. Это соответствует моим ожиданиям, принимая во внимание примерно линейную временную стоимость за такт работы.

class progress_bar
{
public:
  progress_bar(uint64_t ticks)
    : _total_ticks(ticks), _ticks_occurred(0),
      _begin(std::chrono::steady_clock::now())
//  ...
    {}
  void tick()
  {
    using namespace std::chrono;
    // test to see if enough progress has elapsed
    //  to warrant updating the progress bar
    //  that way we aren't wasting resources printing
    //  something that hasn't changed
    if (/* should we update */)
    {
        // somehow _ticks_occurred is updated here and is not zero
        duration time_taken = Clock::now() - _begin;
        float percent_done = (float)_ticks_occurred/_total_ticks;
        duration time_left = time_taken * static_cast<rep>(1/percent_done - 1);
        minutes minutes_left = duration_cast<minutes>(time_left);
        seconds seconds_left = duration_cast<seconds>(time_left - minutes_left);
    }
  }
private:
  typedef std::chrono::steady_clock Clock;
  typedef Clock::time_point time_point;
  typedef Clock::duration duration;
  typedef Clock::rep rep;
  std::uint64_t _total_ticks;
  std::uint64_t _ticks_occurred;
  time_point _begin;
  //...
};

Трафик в std :: chrono :: длительностей, когда вы можете. Таким образом <chrono> сделает все преобразования за вас. typedefs может облегчить ввод с длинными именами. И разбить время на минуты и секунды так же просто, как показано выше.

Как отмечает bames53 в своем ответе, если вы хотите использовать мое <chrono_io> средство, это тоже круто. Ваши потребности могут быть достаточно простыми, что вы не хотите. Это суждение. Ответ bames53 - хороший ответ. Я подумал, что эти дополнительные детали тоже могут быть полезны.

Редактировать

Я случайно оставил ошибку в коде выше. И вместо того, чтобы просто пропатчить код выше, я подумал, что было бы неплохо указать на ошибку и показать, как использовать <chrono> для ее исправления.

Ошибка здесь:

duration time_left = time_taken * static_cast<rep>(1/percent_done - 1);

и здесь:

typedef Clock::duration duration;

На практике steady_clock::duration обычно основывается на целочисленном типе. <chrono> называет это rep (сокращение от представление ). И когда percent_done больше 50%, коэффициент, умноженный на time_taken, будет меньше 1. А когда rep является целым числом, это приводит к 0. Так что progress_bar ведет себя хорошо только первые 50% и прогнозируют 0 времени, оставшегося в течение последних 50%.

Ключом к исправлению этого является трафик в duration s, основанный на числах с плавающей запятой, а не на целых числах. И <chrono> делает это очень легко.

typedef std::chrono::steady_clock Clock;
typedef Clock::time_point time_point;
typedef Clock::period period;
typedef std::chrono::duration<float, period> duration;

duration теперь имеет тот же период тика, что и steady_clock::duration, но использует float для представления. И теперь вычисление для time_left может не включать static_cast:

duration time_left = time_taken * (1/percent_done - 1);

Вот снова весь пакет с этими исправлениями:

class progress_bar
{
public:
  progress_bar(uint64_t ticks)
    : _total_ticks(ticks), _ticks_occurred(0),
      _begin(std::chrono::steady_clock::now())
//  ...
    {}
  void tick()
  {
    using namespace std::chrono;
    // test to see if enough progress has elapsed
    //  to warrant updating the progress bar
    //  that way we aren't wasting resources printing
    //  something that hasn't changed
    if (/* should we update */)
    {
        // somehow _ticks_occurred is updated here and is not zero
        duration time_taken = Clock::now() - _begin;
        float percent_done = (float)_ticks_occurred/_total_ticks;
        duration time_left = time_taken * (1/percent_done - 1);
        minutes minutes_left = duration_cast<minutes>(time_left);
        seconds seconds_left = duration_cast<seconds>(time_left - minutes_left);
        std::cout << minutes_left.count() << "m " << seconds_left.count() << "s\n";
    }
  }
private:
  typedef std::chrono::steady_clock Clock;
  typedef Clock::time_point time_point;
  typedef Clock::period period;
  typedef std::chrono::duration<float, period> duration;
  std::uint64_t _total_ticks;
  std::uint64_t _ticks_occurred;
  time_point _begin;
  //...
};

Ничего подобного небольшому тестированию ...; -)

3 голосов
/ 03 марта 2012

Библиотека хронографов включает типы для представления длительностей. Вы не должны преобразовывать это в плоское целое число некоторой «известной» единицы. Когда вы хотите известный юнит, просто используйте типы хронографов, например, 'std :: chrono :: наносекунд' и duration_cast. Или создайте свой собственный тип продолжительности, используя представление с плавающей запятой и одно из соотношений SI. Например. std::chrono::duration<double,std::nano>. Без duration_cast или с округлением длительности с плавающей запятой запрещено во время компиляции.

Средства ввода-вывода для chrono не попали в C ++ 11, но вы можете получить исходный код из здесь . Используя это, вы можете просто игнорировать тип продолжительности, и он будет печатать правильные единицы. Я не думаю, что там есть что-то, что будет показывать время в минутах, секундах и т. Д., Но такую ​​вещь не должно быть слишком сложно написать.

Я не знаю, что слишком много причин беспокоиться о том, чтобы часто звонить steady_clock::now(), если вы об этом. Я ожидаю, что на большинстве платформ будет достаточно быстрый таймер для подобных вещей. Хотя это зависит от реализации. Очевидно, что это вызывает у вас проблему, поэтому, возможно, вы могли бы звонить только steady_clock::now() внутри блока if (/* should we update */), что должно установить разумное ограничение частоты вызовов.

Очевидно, есть другие способы оценить оставшееся время. Например, вместо того, чтобы брать среднее по прогрессу (что и делает формула, которую вы показываете), вы можете взять среднее из последних N тиков. Или сделайте оба и возьмите средневзвешенное значение двух оценок.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...