Меня интересует, как сигналы связываются со слотами. Как я понимаю, есть некое хранилище даных связей, единое для всего приложения, типа map/multimap, в котором при возникновении сигнала ищутся слоты, которые к нему присоединены. У меня возникает вопрос — если приложение большое, содержит много GUI, использует множество сигналов, то по идее оно должно тормозить, и чем больше будет разрастаться гуй, тем сильнее будут тормоза. Или все устроено несколько более оптимально и быстродействие сигнало не зависит от размера гуя?
Здравствуйте, Marty, Вы писали:
M> Здравствуте, коллеги!
M> Меня интересует, как сигналы связываются со слотами. Как я понимаю, есть некое хранилище даных связей, единое для всего приложения, типа map/multimap, в котором при возникновении сигнала ищутся слоты, которые к нему присоединены. У меня возникает вопрос — если приложение большое, содержит много GUI, использует множество сигналов, то по идее оно должно тормозить, и чем больше будет разрастаться гуй, тем сильнее будут тормоза. Или все устроено несколько более оптимально и быстродействие сигнало не зависит от размера гуя?
Расширение языка C++ реализовано с помощью специального препроцессора MOC (Meta Object Compiler). Он анализирует классы на наличие макроса Q_OBJECT и создает дополнительные файлы с кодом на "чистом" C++, содержащие необходимую функциональность. Так как эта работа выполняется полностью автоматически, то вспоминать об этом механизме вам не придется. Если у вас все же возникла необходимость углубиться в детали реализации,то вы всегда можете просмотреть файлы, сгенерированные метаобъектным компилятором.
Недостатки использования механизма сигнал-слот:
* Необходимость наследования класса QObject.
* Сигналы и слоты несколько медленнее, чем вызовы функций при использовании механизма функций обратного вызова. Однако производительность механизма сигнал-слот все же весьма высока. Так, по информации Trolltech, на i586-500 за одну секунду может быть послано около 2000000 сигналов с одним получателем, либо около 1200000 с двумя.
* В процессе компиляции не ведется проверка совместимости сигнала со слотом или наличия данного сигнала или слота в данном классе. Сообщение об ошибке будет получено на консоль во время исполнения программы.
O> * Необходимость наследования класса QObject.
O> * Сигналы и слоты несколько медленнее, чем вызовы функций при использовании механизма функций обратного вызова. Однако производительность механизма сигнал-слот все же весьма высока. Так, по информации Trolltech, на i586-500 за одну секунду может быть послано около 2000000 сигналов с одним получателем, либо около 1200000 с двумя.
O> * В процессе компиляции не ведется проверка совместимости сигнала со слотом или наличия данного сигнала или слота в данном классе. Сообщение об ошибке будет получено на консоль во время исполнения программы.
O>Препроцессором механизм реализуется.
Было бы интересно, если бы указывались не абсолютные цифры, а процент замедления по сравнению с callbacks(хотя бы для случая с 1 получателем).
Здравствуйте, blackhearted, Вы писали:
b> Было бы интересно, если бы указывались не абсолютные цифры, а процент замедления по сравнению с callbacks(хотя бы для случая с 1 получателем).
Я бы начал с просмотра сгенерированных исходников moc'ом (что и рекомендуется по ссылке, кстати). Думаю, это бы дало наиболее верное представление о том, что происходит внутри. Сам таким вопросом не задавался пока, быстродействием монстрообразных qt4-программ доволен
Здравствуйте, ocaml, Вы писали:
O>Здравствуйте, blackhearted, Вы писали:
b>> Было бы интересно, если бы указывались не абсолютные цифры, а процент замедления по сравнению с callbacks(хотя бы для случая с 1 получателем).
O>Я бы начал с просмотра сгенерированных исходников moc'ом (что и рекомендуется по ссылке, кстати). Думаю, это бы дало наиболее верное представление о том, что происходит внутри. Сам таким вопросом не задавался пока, быстродействием монстрообразных qt4-программ доволен
Я не спорю, но хотелось бы относительных цифр. Исходники почитать — это неплохо, но бенчмарков не заменит.
Общие сведения я читал, хотелось бы узнать, хотя бы в общих чертах, как устроено связывание сигналов и слотов.
O>Препроцессором механизм реализуется.
Это в стиле магистра Йоды?
На самом деле, если бы препроцессором все ограничивалось, то в рантайме нельзя было бы связывать сигналы и слоты, а это не так. Значит, есть рантайм поддержка этого механизма.
Здравствуйте, Marty, Вы писали:
M> O>Препроцессором механизм реализуется.
M> Это в стиле магистра Йоды?
Нет.
M> На самом деле, если бы препроцессором все ограничивалось, то в рантайме нельзя было бы связывать сигналы и слоты, а это не так. Значит, есть рантайм поддержка этого механизма.
Здравствуйте, Marty, Вы писали:
M> Здравствуте, коллеги!
M> Меня интересует, как сигналы связываются со слотами. Как я понимаю, есть некое хранилище даных связей, единое для всего приложения, типа map/multimap, в котором при возникновении сигнала ищутся слоты, которые к нему присоединены. У меня возникает вопрос — если приложение большое, содержит много GUI, использует множество сигналов, то по идее оно должно тормозить, и чем больше будет разрастаться гуй, тем сильнее будут тормоза. Или все устроено несколько более оптимально и быстродействие сигнало не зависит от размера гуя?
Связи (сигнал<->слот) хранятся не в общей для приложения карте, а в каждом из QObject-ов (и во владельце сигнала, и во владельце слота).
В деструкторе каждого из вледельцев связи вызывается disconnect для связи.
Поиск нужного члена (при соединении) производится по строковому имени сигнала/слота. Поэтому время подключения будет рости с ростом числа сигналов/слотов в нем.
После того как соединение установлено — хранится только номер сигнала/слота. В момент вызова, функция из номера получается с помощью оператора switch, т.е. достаточно быстро.
Связь хранится в виде числа (номер сигнала/слота) + указатель на владельца. Вызов реализован в виде switch. Не думаю, что это медленнее чем хранение указателя на функцию в boost::signal. Трудозатраты в связи с упаковкой аргументов вызова в виде виде void **_a и последующей распаковкой каждого из аргументов через " *reinterpret_cast< int(*)>(_a[1]) " возможно, даже меньше чем в случае других видов сигналов (в boost::signals аргументы копируются несколько раз, пока дойдут до получателя).
Где handmake — наиболее близкий аналог для случая прямого вызова. Когда указатель на вызываемую функцию известен на этапе компиляции, в сигнале запоминаются только указатели на обьект-получатель.
Реализация сигналов Qt вполне конкурентноспособна, в сравнениями с другими реализациями.
Но в критических (по производительности) ситуациях, когда обработчик сигнала очень мал по размеру, идеому сигналов лучше не использовать (лучшие реализации работают в 20 раз медленнее, чем просто вызов известной функции).
Здравствуйте, Marty, Вы писали:
M> ...У меня возникает вопрос — если приложение большое, содержит много GUI, использует множество сигналов, то по идее оно должно тормозить, и чем больше будет разрастаться гуй, тем сильнее будут тормоза. Или все устроено несколько более оптимально и быстродействие сигнало не зависит от размера гуя?
Да, может и тормозить.
К счастью — не только слоты/сигналы.
В книжке, в главе 7 описан механизм событий.
Отмечено, что, как правило, сигналы полезны при использовании виджета, в то время как события полезны при реализации виджета.
На самом деле, если бы препроцессором все ограничивалось, то в рантайме нельзя было бы связывать сигналы и слоты, а это не так. Значит, есть рантайм поддержка этого механизма.
Препроцессор генерирует код для поддержки connect() и emit в рантайме.
In general, emitting a signal that is connected to some slots, is approximately ten times slower than calling the receivers directly, with non-virtual function calls. This is the overhead required to locate the connection object, to safely iterate over all connections (i.e. checking that subsequent receivers have not been destroyed during the emission), and to marshall any parameters in a generic fashion. While ten non-virtual function calls may sound like a lot, it's much less overhead than any new or delete operation, for example. As soon as you perform a string, vector or list operation that behind the scene requires new or delete, the signals and slots overhead is only responsible for a very small proportion of the complete function call costs.
The same is true whenever you do a system call in a slot; or indirectly call more than ten functions. On an i586-500, you can emit around 2,000,000 signals per second connected to one receiver, or around 1,200,000 per second connected to two receivers. The simplicity and flexibility of the signals and slots mechanism is well worth the overhead, which your users won't even notice.