мультиплексирование
Oct. 27th, 2012 11:03 pmЕго ещё нужно уметь тестировать, оказывается.
Клиент написан на NIO, бросается тривиальными запросами, идёт спать с помощью Thread.sleep, чтобы гарантировать нужный arrival rate, и пользуется Selector.select.

Удивительно здесь то, что Response Time не зависит от arrival rate, и то, что другой клиент, написанный полностью на blocking IO и с 6x потоков, добивается более низкого Response Time (на графике не указан). Но если у нас 24 ядра, то что будет делать этот клиент с 75 потоками? Думаю, здесь начинаются какие-то трюки ОС, когда оно пытается не снимать с ядра потоки, даже если они в blocking IO. Тогда даже хотя потоков и 75, но запросов в каждый момент времени-то всего 24, а потому только вот эти 24 сокета и могут стать read-ready, и потому клиент с blocking IO кажется выигрывает. Но это не честный тест, он biases сокеты, и не умеет задавать arrival rate.
Ещё один confounding factor - вы видите, что throughput застрял на 70+К и дальше не смог пройти? Это примерно тот же throughput, что и у blocking IO клиента. Это не показалось бы странным, если бы не следующий эксперимент.
Тот же NIO клиент, но не спит: все Thread.sleep заменены на пустой цикл, проверяющий текущее время, а Selector.select заменено на Selector.trySelect в цикле. Всё направлено на то, чтобы потоки никогда с ядра не слезали.

Вот теперь мы получаем разные Response time для разных arrival rate (ура!) и Response time для 70+К запросов получается такой же, как и для клиента с blocking IO. И мы, оказывается, можем повышать throughput до 200К, а может, и больше.
Остаётся объяснить, что это за разноцветные точки. Это два разных способа мультиплексировать входящие соединения на сервере. Теория говорит, что красные должны победить, но график нам кагбы намекает.
А вот как выглядит картинка, когда тот же неблокирующий бессонный NIO клиент распределяет arrivals по Пуассону.

Нужно из этого извлечь какой-нибудь урок.
Клиент написан на NIO, бросается тривиальными запросами, идёт спать с помощью Thread.sleep, чтобы гарантировать нужный arrival rate, и пользуется Selector.select.

Удивительно здесь то, что Response Time не зависит от arrival rate, и то, что другой клиент, написанный полностью на blocking IO и с 6x потоков, добивается более низкого Response Time (на графике не указан). Но если у нас 24 ядра, то что будет делать этот клиент с 75 потоками? Думаю, здесь начинаются какие-то трюки ОС, когда оно пытается не снимать с ядра потоки, даже если они в blocking IO. Тогда даже хотя потоков и 75, но запросов в каждый момент времени-то всего 24, а потому только вот эти 24 сокета и могут стать read-ready, и потому клиент с blocking IO кажется выигрывает. Но это не честный тест, он biases сокеты, и не умеет задавать arrival rate.
Ещё один confounding factor - вы видите, что throughput застрял на 70+К и дальше не смог пройти? Это примерно тот же throughput, что и у blocking IO клиента. Это не показалось бы странным, если бы не следующий эксперимент.
Тот же NIO клиент, но не спит: все Thread.sleep заменены на пустой цикл, проверяющий текущее время, а Selector.select заменено на Selector.trySelect в цикле. Всё направлено на то, чтобы потоки никогда с ядра не слезали.

Вот теперь мы получаем разные Response time для разных arrival rate (ура!) и Response time для 70+К запросов получается такой же, как и для клиента с blocking IO. И мы, оказывается, можем повышать throughput до 200К, а может, и больше.
Остаётся объяснить, что это за разноцветные точки. Это два разных способа мультиплексировать входящие соединения на сервере. Теория говорит, что красные должны победить, но график нам кагбы намекает.
А вот как выглядит картинка, когда тот же неблокирующий бессонный NIO клиент распределяет arrivals по Пуассону.

Нужно из этого извлечь какой-нибудь урок.
no subject
Date: 2012-10-28 09:40 am (UTC)Меня удивило три вещи:
1. blocking IO клиент смог быстрее, чем NIO клиент. Хотя клиент и считает время с учётом client-side inefficiencies, но почему blocking IO изначально даёт 150 микросекунд, а NIO клиент даёт 400 микросекунд, пока не сделать всё на клиенте неблокирующим.
2. теория говорит, одного селектора должно быть достаточно, но это не правда. (а у Гризли сколько, интересно?) Здесь, видимо, какая-то беда в реализации таблицы read-ready дескрипторов.
3. насколько регулярный arrival rate отличается от Пуассоновского!
А в чём сложность с Пуассоном? Сложность убедиться, что получается именно он и подправить в нужную сторону? (Потому что генерировать "примерно Пуассоновское" распределение вроде не сложно)
Ну и дисклеймер, что если строго говоря у меня и найдутся косяки с распределением, то ровно Пуассона мне и не нужно - то есть, усложнять генерацию событий пока не вижу причины - мне нужно какой-то jitter, чтобы не было clock work прибытия событий, как в первом случае.
no subject
Date: 2012-10-28 09:49 am (UTC)2. Селектора одного может и достаточно, а вот обработчика событий с этого селектора -- нет. В Grizzly кучка SelectorRunner'ов работает, если что.
А чем ты собрался приближать Пуассоновское распределение? Нормальным? Его тоже придётся итеративно считать (по крайней мере, как это делает Кнут и java.util.Random вслед за ним).
no subject
Date: 2012-10-28 10:12 am (UTC)2. о, это-то понятно, куча обработчиков конечно нужна.
Сначала был вариант с один селектором, кучей обработчиков событий, но глупой фишкой в этом обработчике. Четыре года назад переписано на кучу селекторов и обнаружено улучшение. Тогда же у меня по прикидкам получалось, что один селектор должен быть лучше, they just didn't do it right, но этот blocking IO client не показал разницы, и в продакшен пошёл вариант с кучей селекторов. Вот сейчас разобрался, что есть проблема с клиентом и способом измерения.
В моём варианте можно варьировать количество селекторов. Конфигурация с одним селектором однозначно проигрывает.
Как чем приближать Пуассоновское? Разбросать время до следующего прибытия сообщения из одного сокета экспоненциально = количество сообщений в единицу времени распределено по Пуассону. Я не ту книжку читал?
no subject
Date: 2012-10-28 10:24 am (UTC)А про Пуассона я однозначно ступил. Это мы тоже пробовали, но воткнулись в горячий логарифм. Нелегко, короче, генерить кучу случайных величин, если их распределение нетривиально.
no subject
Date: 2012-10-28 05:51 pm (UTC)Дык... чему там лагать-то? BlockingQueue, и все дела. Если консюмеры не спят (ещё не прибежали или спинят в take), стоимость равна одному CAS на N read-ready сокетов. Если консюмеры спят (спин кончился), то ещё разбудить нужно. И что, можно сделать быстрее, чем это?
Далее, разница между одним селектором и несколькими селекторами не влияет на очередь. Разница в пропускной способности только за счёт количества селекторов.
Я где-то верю. Selector.select должен пробежаться по какой-то таблице FD, чтобы найти read-ready сокеты. У этой операции должна быть линейная стоимость. Нет?..
А про горячий лошарифм хочу послушать.
no subject
Date: 2012-10-28 05:52 pm (UTC)