Russian Qt Forum
Ноябрь 27, 2024, 02:34 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1] 2   Вниз
  Печать  
Автор Тема: QTimer или QBasicTimer или QObject::startTimer ?  (Прочитано 12521 раз)
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« : Декабрь 08, 2010, 17:16 »

Доброго времени суток!

Возникла задача предварительно проанализировать: какой из типов таймеров использовать в приложении.
Суть в том, что в приложении (к примеру) будут использоваться некие объекты (назовем их "Коммуникационные ресурсы", наследованы от QIODevice), которые выполняют прием/передачу данных через сокеты и т.п...
Так вот, при обмене данными планирую использовать асинхронный режим.
Всё хорошо, но необходимо организовать обмен типа запрос/ответ, т.е. после записи данных в ресурс необходимо засекать время ожидания ответа из ресурса.
т.е. по истечении некоторого времени если ресурс не ответил, то формировать ошибку и т.п.

Самих объектов "ресурс" может быть сотни/тысячи и создавать столько же таймеров - абсурд.

Пока что единственный выход (ИМХО) - создать один многоканальный таймер в котором каналы представляли бы собой обычные счетчики, значения в которых инкрементировались каждый раз при поступлении сигнала от этого таймера.

Но что думают гуру? Улыбающийся Может что лучше предложат?


Записан

ArchLinux x86_64 / Win10 64 bit
asvil
Гость
« Ответ #1 : Декабрь 08, 2010, 17:39 »

Не могу назвать себя гуру. Но я бы реализовал то, что Вы предложили. Список счетчиков. После некоторой записи добавлять счетчик со значением таймаута. В таймере пройти по всем счетчикам, декрементировать каждый. В процессе декрементации нулевые выбрасывать ну и в куда-то генерировать таймаут.
Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #2 : Декабрь 08, 2010, 19:31 »

Цитировать
Не могу назвать себя гуру. Но я бы реализовал то, что Вы предложили. Список счетчиков
Да, но это все-таки какое-то кривоватенькое решение, хотя.. надо проверить.

В принципе, меня устроил бы таймер QObject-а, но оно что-то уж очень много кушает ресурсов.
Я тут на скорую руку сделал тестовый примерчик:
myobject.h
Код:
#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <QtCore/QTimerEvent>
#include <QtCore/QDebug>
#include <QtCore/QObject>

class MyObject : public QObject
{
    Q_OBJECT
public:
    explicit MyObject(QObject *parent = 0) :
        QObject(parent)
    {
        this->startTimer(1);
    }

protected:
     void timerEvent(QTimerEvent *event)
     {
         qDebug() << "Timer ID:" << event->timerId();
     }

};

#endif // MYOBJECT_H

main.cpp
Код:
#include <QtCore/QCoreApplication>

#include "myobject.h"
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    int counter = 10000;
    while (counter--)
        new MyObject();

    return a.exec();
}

в котором создается 10000 объектов, в каждом из которых инициализируется таймер с интервалом ~1мс...
При запуске всего этого, одно из двух ядер процессора загружается на ~70%, другое ~50%.
Записан

ArchLinux x86_64 / Win10 64 bit
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #3 : Декабрь 08, 2010, 22:17 »

Наспех соорудил класс MultiChannelTimer по этой идее:
Цитировать
Пока что единственный выход (ИМХО) - создать один многоканальный таймер в котором каналы представляли бы собой обычные счетчики, значения в которых инкрементировались каждый раз при поступлении сигнала от этого таймера.

При создании 10000 каналов оно также жрет ресурся ЦПУ, вдобавок еще и в консоль ничего не хочет выводить (но если создавать ~2000 каналов то выводит).

myobject.h
Код:
#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <QtCore/QTimerEvent>
#include <QtCore/QMap>
#include <QtCore/QDebug>
#include <QtCore/QObject>

class MultiChannelTimer : public QObject
{
    Q_OBJECT
signals:
    void timeout(int);

public:
    explicit MultiChannelTimer(QObject *parent = 0)
        : QObject(parent), timerId(0)
    {
        connect(this, SIGNAL(timeout(int)), this, SLOT(result(int)));
    }

    ~MultiChannelTimer()
    {
        if (this->timerId)
            this->killTimer(this->timerId);
        qDeleteAll(this->map);
    }

    int createChannel(bool autoReset = true)
    {
        TimerChannel *ch = new TimerChannel(this->freeId());
        ch->autoreset = autoReset;
        this->map.insert(ch->id, ch);
        return ch->id;
    }

    void deleteChannel(int channelId)
    {
        TimerChannel *ch = this->map.take(channelId);
        if (ch)
            delete ch;
    }

    void startChannel(int channelId, int msecs)
    {
        if (!this->timerId)
            this->timerId = this->startTimer(10);
        if (!this->timerId)
            return;
        TimerChannel *ch = this->channel(channelId);
        if (ch) {
            ch->currCounterValue = msecs;
            ch->constCounterValue = msecs;
            ch->started = true;
        }
    }

    void stopChannel(int channelId)
    {
        TimerChannel *ch = this->channel(channelId);
        if (ch)
            ch->started = false;
    }

    bool isStarted(int channelId) const
    {
        TimerChannel *ch = this->channel(channelId);
        return (ch) ? ch->started : false;
    }

protected:
    void timerEvent(QTimerEvent *event)
    {
        if (event->timerId() == this->timerId) {
            foreach (TimerChannel *ch, this->map) {
                if (ch && ch->started) {
                    if ((ch->currCounterValue--) < 0) {
                        if (ch->autoreset)
                            ch->currCounterValue = ch->constCounterValue;
                        emit timeout(ch->id);
                    }
                }
            }
        }
    }

private:
    class TimerChannel
    {
    public:
        TimerChannel(int id)
            : id(id)
            , currCounterValue(0)
            , constCounterValue(0)
            , autoreset(false)
            , started(false)
        {}
        int id;
        int currCounterValue;
        int constCounterValue;
        bool autoreset;
        bool started;
    };

    TimerChannel *channel(int channelId) const
    {
        return this->map.value(channelId, 0);
    }

    int freeId() const
    {
        int id = 0;
        QList<int> list = this->map.keys();
        while (list.contains(id)) {
            ++id;
        }
        return id;
    }

    int timerId;
    QMap<int, TimerChannel *> map;

private slots:
    void result(int id)
    {
        qDebug() << "Id: " << id;
    }
};
#endif // MYOBJECT_H

main.cpp
Код:
#include <QtCore/QCoreApplication>

#include "myobject.h"
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    MultiChannelTimer mct;
    int id = 0;
    int count = 10000;

    while (count--) {
        id = mct.createChannel();
        mct.startChannel(id, 5);
    }
    return a.exec();
}

Тестовый проектик прилагаю в аттач.

Подскажите более правильное решение. Грустный
Записан

ArchLinux x86_64 / Win10 64 bit
asvil
Гость
« Ответ #4 : Декабрь 09, 2010, 00:05 »

Код:
// GTK+ слот для обработки таймаута:)
typedef void (*timeout)(quint64);

// односвязный список таймеров
typedef struct timers {
  quint64 id;
  int timeout;
  struct timers* next;
}

//точность таймера
#define precision 10
class Channel : public QObject
{
  Q_OBJECT
  
  quint64 idgenerator; // переменная для генерации идентификаторов таймеров
  timers *first; // указатель на односвязный список таймеров
  timeout callback;
public:
  Channel(QObject* parent = 0) {
     idgenerator = 0;
     first = 0;
     startTimer(precision); // Ваша точность 10 msec
  }
  virtual ~Channel();

  // return timer id
  quint64 addTimer(int timeout) {
    timers* that = malloc(sizeof(struct timers));
    that->id = idgenerator++;
    that->timeout = timeout;
    that->next = first;
    first = that;
    return that->id;
  }
  void setTimeoutSlot(timeout slot) {
    callback = slot;
  }
protected:
  void timerEvent(QTimerEvent *event) {
    timers *next = first;
    timers *previous = 0;
    while (next) {
      --next->timeout;
      if (!next->timeout) {
        if (previous) {
          previous->next = next->next;
        } else {
          first = next->next;
        }
        callback(next->id);
        free(next);
      }
      previous = next;
      next = next->next;
    }
  }
};

void fancytimeout(quint64 id)
{
  qDebug() << id << "timeout";
}

void main(int argc, char** argv) {
  QCoreApplication app(argc, argv);
  Channel channel;
  channel.setTimeoutSlot(fancytimeout);
  // Наполняем таймерами
  for (int i = 0; i < MAX_INT; ++i)
    channel.addTimer(qRand()/precision); // qRand() - кол-во msec
    
  app.exec();
}
Примерно то же, что и у Вас, только чуть-чуть на c.
« Последнее редактирование: Декабрь 09, 2010, 00:18 от Филоненко Михаил » Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #5 : Декабрь 09, 2010, 08:36 »

Михаил , ну и какая у Вас получается загрузка  CPU при (к примеру) 10000 каналов?
Записан

ArchLinux x86_64 / Win10 64 bit
asvil
Гость
« Ответ #6 : Декабрь 09, 2010, 10:19 »

Привожу полный код программы:
Код:
#include <QtCore>
#include <QCoreApplication>

// GTK+ слот для обработки таймаута:)
typedef void (*timeout)(quint64, int);

// односвязный список таймеров
struct timers {
  quint64 id;
  int timeout;
  int elapsed;
  struct timers* next;
};

//точность таймера
#define precision 10
class Channel : public QObject
{
  quint64 idgenerator; // переменная для генерации идентификаторов таймеров
  timers *first; // указатель на односвязный список таймеров
  timeout callback;
public:
  Channel(QObject* parent = 0)
  :QObject(parent){
     idgenerator = 0;
     first = 0;
     startTimer(precision); // Ваша точность 10 msec
  }
  virtual ~Channel(){

  }

  // return timer id
  quint64 addTimer(int timeout) {
    timers* that = (timers*)malloc(sizeof(struct timers));
    that->id = idgenerator++;
    that->timeout = timeout;
    that->elapsed = timeout;
    that->next = first;
    first = that;
    return that->id;
  }
  void setTimeoutSlot(timeout slot) {
    callback = slot;
  }
protected:
  void timerEvent(QTimerEvent *event) {
    timers *next = first;
    timers *previous = 0;
    while (next) {
      --next->timeout;
      if (!next->timeout) {
        if (previous) {
          previous->next = next->next;
        } else {
          first = next->next;
        }
        callback(next->id, next->elapsed);
        free(next);
      }
      previous = next;
      next = next->next;
    }
  }
};

void fancytimeout(quint64 id, int elapsed)
{
  qDebug() << "id" << id << "timeout" << elapsed;
}

int main(int argc, char** argv) {
  QCoreApplication app(argc, argv);
  Channel channel;
  channel.setTimeoutSlot(fancytimeout);
  sleep(5);
  // Наполняем таймерами
  for (int i = 0; i < 1000000; ++i)
    channel.addTimer(qrand()/precision); // qRand() - кол-во msec

  sleep(5);
  return app.exec();
}
Количество таймеров: 1000000
Загрузка процесссора: 88%
Занимаемое количество памяти: 30mb
ubuntu linux
amd athlon 2 x4 635
Я привел параметры из gnome-system-monitor
Вывод, которого я дождался:
Код:
id 996477 timeout 153 
id 891698 timeout 507
id 500001 timeout 528
id 99268 timeout 1406
id 583055 timeout 1519
id 562457 timeout 1638
id 216986 timeout 1836
id 411259 timeout 2229
id 892895 timeout 2341
id 16990 timeout 2669
id 297043 timeout 2823
id 176393 timeout 2850
id 12546 timeout 3165
id 734402 timeout 3341
id 252614 timeout 3394
id 976756 timeout 3445
id 487864 timeout 3767
id 309831 timeout 3933
id 589093 timeout 3955
id 321155 timeout 4210
id 174007 timeout 4342
id 673380 timeout 4609
дельта меджу таймерами ~ 200 милисекунд. Если я все правильно представляю при меньшей дельте загрузка процессора должна возрасти.
Ну и слот, который будет не только qDebug делать, а еще много полезных вещей, также съест свою долю процессорного времени.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #7 : Декабрь 09, 2010, 10:47 »

А почему нельзя обойтись одним таймером: складывать запросы в сортированный (по времени) контейнер (напр QSet) и если обработка должна начаться скорее - подрезать интервал таймера
Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #8 : Декабрь 09, 2010, 11:21 »

А почему нельзя обойтись одним таймером: складывать запросы в сортированный (по времени) контейнер (напр QSet) и если обработка должна начаться скорее - подрезать интервал таймера
Код в студию.
Записан

ArchLinux x86_64 / Win10 64 bit
asvil
Гость
« Ответ #9 : Декабрь 09, 2010, 11:32 »

Хм, у меня один таймер используется. Скажите какие у вас средние таймауты?
Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #10 : Декабрь 09, 2010, 12:55 »

В тему:
О ужас, посмотрел только что  (бегло) исходники связанные с таймером под винду.
И углядел, что на самом деле создается системный таймер с помощью Win32 API функции (MSDN):
Цитировать
UINT_PTR WINAPI SetTimer(
  __in_opt  HWND hWnd,
  __in      UINT_PTR nIDEvent,
  __in      UINT uElapse,
  __in_opt  TIMERPROC lpTimerFunc
);
При этом, чтобы создать таймер, функции необходим такой параметр как HWND hWnd, который является по сути дескриптором окна.
Это еще цветочки, ягодки вперде: для того чтобы создать к примеру 1*10e6 таймеров, необходимо создать столько же ОКОН! О_о
Воистину винда рулезз!

« Последнее редактирование: Декабрь 09, 2010, 13:07 от kuzulis » Записан

ArchLinux x86_64 / Win10 64 bit
BRE
Гость
« Ответ #11 : Декабрь 09, 2010, 13:02 »

Венда это конечно жесть, но сейчас ты по моему ошибаешься.  Подмигивающий
Одно окошко создается.
Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #12 : Декабрь 09, 2010, 13:06 »

Упс, действительно одно.  Строит глазки

Записан

ArchLinux x86_64 / Win10 64 bit
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #13 : Декабрь 09, 2010, 13:34 »

Еще по теме.
Интересно, а почему тролли/нокиа в исходниках Qt4 (по крайней мере 4.5.3) не используют для винды waitable-таймеры,
для которых используются ф-ции:
Цитировать
CreateWaitableTimer
SetWaitableTimer
...

Это же было бы по идее лучше?!

---

Думаю: может замутить нечто (класс-таймер) с использованием этих таймеров? Тем более, что при этом можно ловить события через QWinEventNotifier.  Строит глазки
И проверить нагрузку на CPU.
« Последнее редактирование: Декабрь 09, 2010, 13:37 от kuzulis » Записан

ArchLinux x86_64 / Win10 64 bit
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #14 : Декабрь 09, 2010, 14:27 »

Вот, сварганил по быстрому класс (для экспериментов):

myobject.h
Код:
#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <QtCore/QObject>
#include <QtCore/private/qwineventnotifier_p.h>
#include <QtCore/QEvent>
#include <QtCore/QDebug>

#include <qt_windows.h>


class WaitableTimer : public QWinEventNotifier
{
    Q_OBJECT
signals:
    void timeout();

public:
    explicit WaitableTimer(QObject *parent = 0)
        : QWinEventNotifier(parent)
    {
        this->hTimer = ::CreateWaitableTimer(0, 0, 0);
    }

    ~WaitableTimer()
    {
        this->setEnabled(false);
        if (this->stop())
            ::CloseHandle(this->hTimer);
    }

    bool start(int msecs)
    {
        ::LARGE_INTEGER liDueTime;
        memset(&liDueTime, 0, sizeof(::LARGE_INTEGER));
        bool ret = (0 != ::SetWaitableTimer(this->hTimer, &liDueTime, ::LONG(msecs), 0, 0, 0));
        if (ret) {
            this->setHandle(this->hTimer);
            this->setEnabled(true);
        }
        return ret;
    }

    bool stop() { return (0 != ::CancelWaitableTimer(this->hTimer)); }

protected:
    bool event(QEvent *e)
    {
        bool ret = false;
        if (e->type() == QEvent::WinEventAct)
            qDebug() << "TimerHandle: " << this->hTimer;
        else
            ret = QWinEventNotifier::event(e);
        return ret;
    }

private:
    ::HANDLE hTimer;
};
#endif // MYOBJECT_H


main.cpp
Код:
#include <QtCore/QCoreApplication>

#include "myobject.h"
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    int count = 10000;
    while (count--) {
        WaitableTimer *wt = new WaitableTimer;
        wt->start(10);
    }
    return a.exec();
}
Компилябельный проект в аттаче.

В этом случае тоже запускаю ~10000 таймеров, при этом, нагрузка на процессор падает в ~5 раз, т.е. в данный момент в диспетчере задач около 10-12%.

ЗЫ: Только при запуске оно ругается так:
Цитировать
...
QWinEventNotifier: Cannot have more than 62 enabled at one time
...

К чему бы это?
--
Ответ: это ограничение заложено в заголовках компилятора, а именно в winbase.h:
Код:
...
#define MAXIMUM_WAIT_OBJECTS 64
...
т.е. с использованием Wait-функций особо не разгонишься, не создашь более 64 работоспособных таймеров (да и вообще любых объектов). Грустный ..


« Последнее редактирование: Декабрь 09, 2010, 16:44 от kuzulis » Записан

ArchLinux x86_64 / Win10 64 bit
Страниц: [1] 2   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.077 секунд. Запросов: 23.