Здравствуйте, Engler, Вы писали:
A>>я ещё раз повторюсь, представьте что у вас запустился producer и поместил миллион объектов в очередь очень быстро, скажем за 0 секунд, E>А так не получится. Во-первых, что значит за 0 секунд? Мы же только перед отправкой сообщения получаем timestamp текущий с точностью до наносекунд ( да-да, про погрешность системы знаю, не real time).
пардон, но я уже устал повторять. просто смоделируйте у себя в голове ситуацию, когда consumer стартует на N миллисекунд позже. При этом скорость с которой producer кладёт элементы и consumer достаёт — одинакова, или (я надеялся что вам лучше будет понятно в экстремуме) бесконечно велика (это значит что отработают за 0 секунд).
E>всегда дадут разницу в N наносекунд (хотя бы из за самого вызова ).
да, и это может быть объяснено в разнице между времени старта producer'а и consumer'а. При этом даже если они отрабатывают бесконечно быстро, ваш sum получит гигантские значения.
E>Даже если consumer стартанет раньше, то читать будет нечего. И соответсвенно t2 ( т.е время когда мы получили сообщение ) будет ВСЕГДА больеш t1 (т.е время когда мы записали сообщение).
то что оно всегда больше это очевидно.
попробую аналогией:
Аналогия 1:
100 человек пришло на почту отправить письмо, но почта открылась только через 30 мин. Обслужила 100 человек за 100 секунд ( по секунде на человека ). Для каждого человека разница между его приходом на почту и уходом составляет ~31 минуту ( 30мин + 1 секунда для первого, 30мин + 2 секунды для второго, и т.д., в среднем ~31 минута ). Вы зачем-то взяли это число и умножили на 100, получили какое-то большое число и ужаснулись. Хотя по факту ужасаться нечему, т.к. скорость обслуживания была 1 человек в секунду, что очень быстро.
Аналогия 2:
100 человек пришло на почту отправить письмо, почта открылась сразу. Обслужила 100 человек за 100 секунд ( по секунде на человека ). Для каждого человека разница между его приходом на почту и уходом составляет в среднем 50 секунд ( 1 секунда для первого, 2 секунды для второго, и т.д., в среднем 50 секунд ). Вы зачем-то взяли и просуммировали разницу для каждого человека, получили какое-то большое число ( 1 + 2 + 3 ... + 99 = (1+99) + (2+98) ... = 100 * 50 = 5000 секунд ) и ужаснулись, хотя по факту очередь была обслужена за 100 секунд и скорость обслуживания была 1 человек в секунду, что очень быстро.
я ещё раз повторюсь, представьте что у вас запустился producer и поместил миллион объектов в очередь очень быстро, скажем за 0 секунд, через полсекунды (условно, мало ли как поток запланировался операционкой) запустился consumer, выгреб миллион объектов опять же очень быстро, за 0 секунд. Что вы в итоге насчитаете? У вас "latency" будет полсекунды для каждого элемента в очереди, что, очевидно, бессмысленная информация, т.к. в данном случае может лишь свидетельствовать о более позднем старте consumer'а по отношению к producer'у.
Здравствуйте, Engler, Вы писали:
U>>эта сумма не обязана как-то быть похожей на время выполнения всего приложения, т.к. у вас не пинг-понг (то есть producer не ждет когда же consumer обработает объект). E>Так вот именно что пинг-понг. Т.е схема такая:
Нет, твоя совсем не похожа на пинг-понг. Вот если бы максимальный размер очереди был бы ограничен единицей, то о схожести можно было бы говорить, а так отличия слишком велики, и как следствие, поведение совсем другое.
U>>физический смысл переменной sum — сумма всех задержек по всем вброшенным объектам. E>Физический смысле sum именно такой. Просто получается что приложение перегнало N сообщений, за 1.6 секунды, а sum говорит что суммарно все передалось за 67 ( шесьдесят семь ) . Вот не понимаю как это?
sum — это не время передачи всех сообщений. Это именно сумма всех задержек при передачи каждого сообщения.
В программе значение sum возрастает даже когда сообщение не передаётся, а просто лежит в очереди и ничего не делает. А так как в очереди у тебя может находится одновременно много сообщений, то и sum будет увеличиваться со скоростью пропорциональной их числу.
Рассмотри, например, такую ситуацию: у тебя программа запустилась и доработала уже где-то до середины цикла. В очереди накопилось 100 сообщений (из 128 максимальных), и приходит прерывание. Процессор прекращает выполнение твоей программы, обрабатывает прерывание за одну наносекунду, и продолжает выполнять твою программу. На сколько возрастёт значение sum по сравнению с ситуацией, когда прерывания не было? На 100 наносекунд! А не на одну наносекунду, которую потратил процессор в реальности. А всё из-за того, что каждое из сотни ожидающих сообщений добавило в сумму по одной наносекунде.
Так что sum не отображает время работы всей программы.
А что в варианте с lock время сходится — так это из-за того, что в среднем в очереди находится всего одно сообщение — медленные производители из-за блокировок не успевают очередь наполнять достаточно быстро. Поэтому и время в sum попадает в среднем со множителем 1.
Здравствуйте, Vain, Вы писали V>Я не сразу понял висит ли оно на pop'ах если очередь пуста. Если не висит, то получается оно в холостую цикл под условием с переменной done будет крутить пока данные не появятся, что как бы холостая нагрузка — не есть хорошо.
Если хочешь маленькие задержки, то приходится идти на такое. См. Disruptor pattern
V>Это всё костыли. В правильной реализации кидается сообщение-терминатор и не надо костылей с отслеживанием выхода из диспатчера.
Оба подхода рабочие и прямые имхо, для терминатора нужно еще класс сообщений расширять, что не всегда удобно
V>Не понял, что за нагрузка такая?
Предположу, что это нагрузка на очередь: механизм доставки сообщений наверняка сложнее и требует больше ресурсов, нежели переключение булевского флага done
Здравствуйте, Engler, Вы писали:
E>Есть у кого какие-нибудь идеи?
физический смысл переменной sum — сумма всех задержек по всем вброшенным объектам. эта сумма не обязана как-то быть похожей на время выполнения всего приложения, т.к. у вас не пинг-понг (то есть producer не ждет когда же consumer обработает объект). sum / consumer_count должно быть меньше времени работы всего приложения (в вашем "странном случае это 33 микросекунды меньше 1.6 сек, все окей)
sum / consumer_count — это среднее время задержки обработки одного объекта (интересующее вас число). для того, чтобы оно было точнее нужно сначала поднять потоки, а потом уже прогонять данные через систему. для этого рекомендую создать barrier на (producer_thread_count + consumer_thread_count) потоков и в функции consumer\producer в самом начале вызовать wait(), чтобы дождаться запуска всех потоков. это позволит избежать искажения собранной статистики из-за доп. времени старта каждого потока.
зы: выход из wait тоже недешев и может исказить результаты, но, надеюсь, вы уже при таком подходе получите приемлемые результаты
успехов
Есть есть boost lock free очередь. 2 producer и 1 consumer.
(Код основан на примере из буста. Только тип сообщения из очереди не int , а мой кастомный ). Пытаюсь замерить среднее время сообщения в очереди.
Соответсвено перед тем как послать сообщение, получаем текущее время.
Записываем в сообщение. На принимающей стороне вычитываем сообщение.
Получаем текущее время. Переводим в наносекунды. Суммируем.
В конце делим получившееся время на кол-во сообщений.
Но у меня получаются старнные результаты.
Если ставить lock все работает как и ожидалось, нормально (имеется ввиду producerLock в методe produce). Результат будет дальше.
Если лок снять, то происходят странности.
Total time = это сколько суммарно мы насчитали нано секунд
(1с = 1,000,000,000 нано секунд ).
Программку запускал под утилитой time, что бы проверить результаты.
Вариант, с Lock работате "как надо", т.е суммарно Total time ~= user time (из утилиты time)
Результат с раскомментированным Lock
boost::lockfree::queue is lockfree
produced 2000000 objects.
consumed 2000000 objects.
Total time 2748765264 ns.
Messages 1374 ns per msg
real 0m1.374s
user 0m2.868s
sys 0m0.880s
Без Lock результаты не совпадают. Причем очень сильно.
Результат с закомментированным Lock
boost::lockfree::queue is lockfree
produced 2000000 objects.
consumed 2000000 objects.
Total time 67803937295 ns.
Messages 33901 ns per msg
real 0m0.541s
user 0m1.603s
sys 0m0.002s
Подробнее о системе
Centos 7: 3.10.0-123.el7.x86_64
g++ (GCC) 4.8.3 20140911 (Red Hat 4.8.3-9)
boost 1.58_0: system and thread
Буст собирал сам с этим же компилятором.
Очень внимательно изучил документацию. Не один раз.
Ограничения на тип выполнены:
Requirements:
•T must have a copy constructor
•T must have a trivial assignment operator
•T must have a trivial destructor
Что пробовал:
Заменял DataType на DataType* ( т.е передовать не сообщение а указатель )
Линковать 32 и 64 битные релизные версии библиотеки.
(В случае с 32 бибтной версией, естесвенно компилировалал с ключом -m32 )
Различные версии gcc ( 4.8.2 / 4.8.3 / 4.9.1 / еще что то там ).
Различные версии linux: Alt / Ubuntu 14.04 LTS / Centos 7
Игрался с выравниванием струткруты.
Ииспользовал метод push_bounded
Использовал очередь с динамическим выделением объектов
Пробовал создавать соощение в динамической памяти, а не на стеке.
Пробовал создавать каждый раз новое сообщение, и слать его.
....
Здравствуйте, Engler, Вы писали:
E>void consumer(void)
А что консумер какой-то странный, с двумя циклами и флажком? Разве там в очередь не надо какой-либо quit или eof вставлять (с boost queue не знаком)?
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, Engler, Вы писали:
E>Но у меня получаются старнные результаты.
потому что вы считаете странное. Что хотели посчитать то? Например, если consumer начнёт работать на полсекунды позже чем producer, то результат будет ещё более феерический, даже если producer и consumer отработают за 0 секунд.
E>Если ставить lock все работает как и ожидалось, нормально (имеется ввиду producerLock в методe produce). Результат будет дальше. E>Если лок снять, то происходят странности.
как раз таки наоборот, real time уменьшился фактически на величину sys time, а сам sys time ушёл в ноль, что логично. В чём странности?
Здравствуйте, Vain, Вы писали: E>>void consumer(void) V>А что консумер какой-то странный, с двумя циклами и флажком? Разве там в очередь не надо какой-либо quit или eof вставлять (с boost queue не знаком)?
Консьюмер нормальный.
Флажок нужен для того, что бы знать, когда выходить из общего цилка чтения. Т.е либо читать N сообщений из очереди и закругляться. Либо ждать пока тебе скажут, что все отослали. Тут просто второй вариант.
Второй цикл нужен в ситуации, когда нам сказали что пора выходить, мы вышли из главного цилка, но в очереди остались сообщения. Вот их мы и дочитываем.
P.S: можно конечно eof в сообщение встроить, но это нагрузка на payload, да и принципиально не меняет суть вопроса.
A>потому что вы считаете странное. Что хотели посчитать то? E> Пытаюсь замерить среднее время сообщения в очереди.
Если говорить другими словами, то Latency.
A> Например, если consumer начнёт работать на полсекунды позже чем producer, то результат будет ещё более феерический
Если consumner начнет работать на полсекунды позже, то должно получится что сообщение пролежит в очередит на пол секунды дольше, и всего то.
A>, даже если producer и consumer отработают за 0 секунд.
Я наверное, не совсем понимаю, что вы имеете ввиду. Допутсим.
Запустили Producer. Он сгенерировал сообщение. Записали в него, что оно сгенерировалось во время t1.
Запустили Consumer (путь даже не секунду позже ). Вычитали из очереди сообщение. Получили текущее время t2.
t2 — t1 = время, сколько соощение провело в очереди.
A>как раз таки наоборот, real time уменьшился фактически на величину sys time, а сам sys time ушёл в ноль, что логично. В чём странности?
Смотрите, что я имею ввиду:
boost::lockfree::queue is lockfree
produced 2000000 objects.
consumed 2000000 objects.
Total time 67803937295 ns.
Messages 33901 ns per msg
real 0m0.541s user 0m1.603s
sys 0m0.002s
Total time это то что я насуммировал счетчиком.
67,803,937,295 наносекунд = 67.80 секунд. Т.е получается, что время считается где-то неправильно.
А само приложение отработало по time за 1.6 секунды.
И так считается БЕЗ locka.
а если Lock ставить, то суммируемое время и время утилитой совпадают. Вот как раз в этом странность и заключается.
Здравствуйте, Engler, Вы писали:
E>Здравствуйте, Vain, Вы писали: E>>>void consumer(void) V>>А что консумер какой-то странный, с двумя циклами и флажком? Разве там в очередь не надо какой-либо quit или eof вставлять (с boost queue не знаком)? E>Консьюмер нормальный.
Я не сразу понял висит ли оно на pop'ах если очередь пуста. Если не висит, то получается оно в холостую цикл под условием с переменной done будет крутить пока данные не появятся, что как бы холостая нагрузка — не есть хорошо.
E>Второй цикл нужен в ситуации, когда нам сказали что пора выходить, мы вышли из главного цилка, но в очереди остались сообщения. Вот их мы и дочитываем.
Это всё костыли. В правильной реализации кидается сообщение-терминатор и не надо костылей с отслеживанием выхода из диспатчера.
E>P.S: можно конечно eof в сообщение встроить, но это нагрузка на payload, да и принципиально не меняет суть вопроса.
Не понял, что за нагрузка такая?
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, uzhas, Вы писали:
V>>Не понял, что за нагрузка такая? U>Предположу, что это нагрузка на очередь: механизм доставки сообщений наверняка сложнее и требует больше ресурсов, нежели переключение булевского флага done
Можно пример про "больше ресурсов"? А то я себе не представляю, как можно обрабатывая лишь одно сообщение затребовать ресурсы большие, чем уже затребованы на обработку всех остальных сообщений.
U>Зы: пиши что-нибудь интересное, а не цепляйся )
я не цепляюсь, а задаю вопросы
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, uzhas, Вы писали:
U>Здравствуйте, Engler, Вы писали:
E>>Есть у кого какие-нибудь идеи?
U>физический смысл переменной sum — сумма всех задержек по всем вброшенным объектам. эта сумма не обязана как-то быть похожей на время выполнения всего приложения, т.к. у вас не пинг-понг (то есть producer не ждет когда же consumer обработает объект).
Так вот именно что пинг-понг. Т.е схема такая:
В данном случае очередь 128 сообщений.
запустился consumer и пытается вычитать данные ( да, гоняя холостой цикл, если очередь пуста ).
Producer записывает в очередь сообщения, если не может писать, гонят цикл пока не запишет, т.е сообщения мы не теряем.
Физический смысле sum именно такой. Просто получается что приложение перегнало N сообщений, за 1.6 секунды, а sum говорит что суммарно все передалось за 67 ( шесьдесят семь ) . Вот не понимаю как это?
Остальные советы, надо будет глянуть, спасибо, но пока задача, что бы это "взлетело".
Здравствуйте, antropolog, Вы писали:
A>Здравствуйте, Engler, Вы писали:
A>я ещё раз повторюсь, представьте что у вас запустился producer и поместил миллион объектов в очередь очень быстро, скажем за 0 секунд,
А так не получится. Во-первых, что значит за 0 секунд? Мы же только перед отправкой сообщения получаем timestamp текущий с точностью до наносекунд ( да-да, про погрешность системы знаю, не real time).
A> через полсекунды (условно, мало ли как поток запланировался операционкой) запустился consumer, выгреб миллион объектов опять же очень быстро, за 0 секунд. Что вы в итоге насчитаете? У вас "latency" будет полсекунды для каждого элемента в очереди, что, очевидно, бессмысленная информация, т.к. в данном случае может лишь свидетельствовать о более позднем старте consumer'а по отношению к producer'у.
В итоге, должно все правильно посчитаться. Т.е мы вычитали сообщение ( а вычитать мы можем только в том случа если его producer УЖЕ положил ). И тут же берем у системы текущее время. Т.е груго говоря, два вызова подряд:
clock_gettime( CLOCK_REALTIME, &time1 ); // это в procuer потоке
clock_gettime( CLOCK_REALTIME, &time2 ); // это в consumer потоке
всегда дадут разницу в N наносекунд (хотя бы из за самого вызова ).
В данном случае очередь 128 элеметнов. Сообщения не теряются, т.е это значит, что если очередь забита producer будет пытаться до тех пор, пока в очереди не появится место.
Даже если consumer стартанет раньше, то читать будет нечего. И соответсвенно t2 ( т.е время когда мы получили сообщение ) будет ВСЕГДА больеш t1 (т.е время когда мы записали сообщение).
V>Я не сразу понял висит ли оно на pop'ах если очередь пуста. Если не висит, то получается оно в холостую цикл под условием с переменной done будет крутить пока данные не появятся, что как бы холостая нагрузка — не есть хорошо.
Нет, не висит.
V>Это всё костыли. В правильной реализации кидается сообщение-терминатор и не надо костылей с отслеживанием выхода из диспатчера.
У нас N producer и 1 consumer.
А как тогда обрабатывать ситуацию, когда у нас N producer-ов?
Пришло из одного producer потока флаг eof и что останавливать consumer?
Т.е даже если мы знаем что у нас N producer-ов, то надо считать количество eof.
А если мы не знаем количесвто producer-ов? На каком eof убивать consumer?
E>>P.S: можно конечно eof в сообщение встроить, но это нагрузка на payload, да и принципиально не меняет суть вопроса. V>Не понял, что за нагрузка такая?
Уже, ответили, но имелось ввиду дополнительное поле в структуре.
Здравствуйте, Engler, Вы писали:
V>>Это всё костыли. В правильной реализации кидается сообщение-терминатор и не надо костылей с отслеживанием выхода из диспатчера. E>У нас N producer и 1 consumer. E>А как тогда обрабатывать ситуацию, когда у нас N producer-ов? E>Пришло из одного producer потока флаг eof и что останавливать consumer?
А в чём проблема? Ты же как правило не знаешь сколько msg тебе должно прийти, но если кто-то кинул quit значит пора закругляться и не важно что кто-то ещё что-то кидает. Это нормально.
E>Т.е даже если мы знаем что у нас N producer-ов, то надо считать количество eof. E>А если мы не знаем количесвто producer-ов? На каком eof убивать consumer?
Ну на eof останавливается вся очередь, не консамеры по отдельности. Только непонятно зачем нам в реальной среде надо ждать продюсера. Как правило продюсеры продьюсят до бесконечности пока очередь не будет остановлена.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
A>я ещё раз повторюсь, представьте что у вас запустился producer и поместил миллион объектов в очередь очень быстро, скажем за 0 секунд, через полсекунды (условно, мало ли как поток запланировался операционкой) запустился consumer, выгреб миллион объектов опять же очень быстро, за 0 секунд. Что вы в итоге насчитаете? У вас "latency" будет полсекунды для каждого элемента в очереди, что, очевидно, бессмысленная информация, т.к. в данном случае может лишь свидетельствовать о более позднем старте consumer'а по отношению к producer'у.
Теперь я действительно понял, что вы имелли ввиду.
Но если я правильно понимаю, то суть latency сообщения, то как раз в этом и заключается.
Т.е посчитать разницу во времени, включая все накладные расходы на пересылку, т.е context switching, планирование потоков и.т.д
Случай, если consumer запущен "значинтельно позже", то да latency будет большой, я этот момент сразу не учел.
В моем случае ( если я правильно понимаю ), это может повлиять только на 128 первых сообщений (т.е на длину очереди).
Но, т.к мы передаем большое количество сообщений ( 20+ миллионов ), мне казалось, что эта большая разница должна была быть усреднена остальными сообщениями.
W>Нет, твоя совсем не похожа на пинг-понг. Вот если бы максимальный размер очереди был бы ограничен единицей, то о схожести можно было бы говорить, а так отличия слишком велики, и как следствие, поведение совсем другое.
Да, согласен.
W>sum — это не время передачи всех сообщений. Это именно сумма всех задержек при передачи каждого сообщения. W>В программе значение sum возрастает даже когда сообщение не передаётся, а просто лежит в очереди и ничего не делает. А так как в очереди у тебя может находится одновременно много сообщений, то и sum будет увеличиваться со скоростью пропорциональной их числу.
Да, но вроде это логично, т.е именно так и должно быть? ( вроде как, хотя я уже начинаю сомневаться )
Т.е допустим, у нас очерень из 4 сообщений.
Соообщение
Время записи
M1
T1
M2
T2
M3
T3
M4
T4
Прошло 2 секунды. ( Т.е эти 2 секунды добавились ко всем сообщениям ).
Но если мы через 2 секунды только достали сообщение из очереди, то соответсвенно latency для этого сообщения:
Для первого T1 + 2 секунды.
Для вторго T2 + 3 секунды ( если мы его через 3 секунды достали )
W>Рассмотри, например, такую ситуацию: у тебя программа запустилась и доработала уже где-то до середины цикла. В очереди накопилось 100 сообщений (из 128 максимальных), и приходит прерывание. Процессор прекращает выполнение твоей программы, обрабатывает прерывание за одну наносекунду, и продолжает выполнять твою программу. На сколько возрастёт значение sum по сравнению с ситуацией, когда прерывания не было? На 100 наносекунд! А не на одну наносекунду, которую потратил процессор в реальности. А всё из-за того, что каждое из сотни ожидающих сообщений добавило в сумму по одной наносекунде. W>Так что sum не отображает время работы всей программы.
Классное объяснение, спасибо!
Мне такое поведение кажется правильным. Мне просто цифры пугают...
W>А что в варианте с lock время сходится — так это из-за того, что в среднем в очереди находится всего одно сообщение — медленные производители из-за блокировок не успевают очередь наполнять достаточно быстро. Поэтому и время в sum попадает в среднем со множителем 1.
Хм, логично.