Pull to refresh

Comments 66

Нагребатор(tm) и разгребатор(tm) — это сильно!
Главное корень слов правильно понять и не путать как некоторые переводчики англоязычных фильмов.
даёшь правильный перевод парадигмы producer-consumer
Поставщик — потребитель?
Это когда producer лениво и неторопливо генерирует 10 сообщений в минуту — тогда да, тогда он производитель. А когда 10 000 в секунду — тогда какой же он производитель, разве можно что-то с такой скоростью производить? — Он нагребатор, как есть нагребатор.

В данном примере нагребатор/разгребатор гораздо более яркая и говорящая метафора. И не только в данном, как показывает практика )
Нагрузочный тест — нагибатор…
Резонно к этим всем качествам добавить ещё и свойство garbage less.

нужны только самые актуальные — то хорошо бы хранить пары ключ-значение в некотором ассоциативном массиве, перетирая старые значения новыми

А это как тогда друг с другом коррелирует?

А вообще, в статье слишком уж сжато начинается с Card Mark'а, неговоря уже про отсылки к D., которые понять можно только побывав (или почитав) на докладе у Руслана. Если будет время/желания, для увеличения читаемости можете поподробнее раскрыть эти моменты.
Card Mark — это ухищрение для второго пунтка общего дизайна (бежать по ассоциативному массиву, забирать самые свежие значения и отдавать в обработчик).

Т.е без card mark обработка задач происходила бы как
for(int i = 0; i < values.length; i++){
  V value = values[i].getAndSet(null);
  if (value != null){
    processor.handle(value);
  }
}


Что как-то нелепо — зачем сканировать область массива, которая никак не изменялась. Но сам по себе card mark не связан с Disruptor.

Пожалуй, ключевая отсылка к D. это идея waiting strategy.
Т.е без card mark обработка задач происходила бы как

Ага, теперь яснее. Спасибо.
Ну этот CardMark — это не dirty memory regions card marking, который используется GC, и про который я мог рассказывать (правда я сам уже не помню, когда и где я про него рассказывал). Но да, здесь та же самая идея, только в собственноручном исполнении
UFO just landed and posted this here
wait strategy с busy-spin-ом и сотней попыток выбирался исходя из оценочного количества входящих сообщений в реальной системе.

согласен, что на холодных нитках latency будут далеко не такими хорошими, как в приведённых графиках — можно придумать какого-нибудь мутанта, который после нескольких неудачных попыток захватить по busy-spin вообще переставал так делать (аналогия между thin/fat locks), но это уже отдельный вопрос
UFO just landed and posted this here
Верное замечание.

Проблема в том, что я не знаю, что есть такое «обычное приложение».
В случае еле-еле идущего потока может быть нужна другая реализация (использующая обычный wait/notify) wait strategy или как вариант вообще обрабатывать сообщения на нитке нагребатора.

Словом, это не универсальное решение типа кухонного комбайна.
UFO just landed and posted this here
Мне кажется, разница упрется в реализацию waitStrategy. От оптимизации структур данных, избавления от блокировок и аллокаций на fast-path вряд ли кому станет хуже, хотя кто-то, конечно, может и не заметить разницы.

Тут есть еще ценность в самом интерфейсе, в API — я в своей жизни видел дюжину реализаций подобного троллинга, и там обычно было месиво разных примитивов синхронизации с неочевидным API и неочевидным же контрактом. Здесь как раз можно сформулировать вменяемое компактное апи с компактным же контрактом (правда, про апи-то Володя как раз не написал)
Я изыскивыаю возможности открыть код, так, чтобы он не противоречил условиям контракта. Трудового. Моего.
UFO just landed and posted this here
Почему же это каждом велосипеде — мы как раз обточивая конкретное решение хотим сократить количество других велосипедов одним.

Конечно, ни я, ни Руслан не знаем внутренний мир реализации JVM как знаете его Вы и какие финты и оптимизации может сделать JVM. Это кстати один из пунктов, который сильно удивил меня в случае реализации на AtomicArrayRefs + BinaryHeapCardMark — ожидания говорили одно, а на деле вышло совсем иное.

Опять же — это ни в коем случае не пропадаганда common case для работы на миллионах разных машин с разными архитектурами. И именно поэтому вынесли на суд общественности — здравая критика приветсвуется, только не ногами по голове.
UFO just landed and posted this here
Это что, вызов на дуэль по боям в грязи?

Если честно, я не большой фанат «жестких» стандартов. На мой взгляд демонстрировать способы решения задач полезнее, чем давать готовые решения. Но я понимаю, что это подходит не для всех
А что там ты ожидаешь с холодными нитками — я пока не понял идеи?

Мое личное мнение, честно говоря, что в данном случае метрика «время от входа до выхода» вообще не является репрезентативной, потому что на нее, начиная с какой-то степени оптимизированности, гораздо больше влияют тонкости thread scheduling-а, чем уже наши оптимизации. То есть мы больше измеряем взаимодействие waitStrategy с thread scheduler-ом, чем например, оптимальность раскладки наших данных в памяти.

С моей точки зрения подходящие метрики здесь это
а) GC-less — количество создаваемого мусора
б) время .put() — задержки, которые мы вносим в потоки-нагребаторы
в) время сканирования в разгребаторах — тут возникает несколько метрик, типа относительного объема «холостых» операций по отношению к полезным, и отдельно стоимость холостого сканирования и стоимость сканирования с реально обнаруженными изменениями
UFO just landed and posted this here
Давайте определимся: постановка задачи предполагает высоконагруженную систему и в силу этого скорее интересует the best и average case.

В случае же с теми решениями, которые были ранее — лучше ровного распредления от 0 до 300 мкс даже на самых разогретых нитках не получалось.
Ну возможно. Я и говорю — от входа до выхода это скорее синтетический тест всей системы (включая планировщик задач). А вот микробенчмарки отдельных ее частей были бы, строго говоря, более репрезентативны — но только для тех, кто сможет их в уме скомбинировать ) С точки зрения краткого описания синтетический бенчмарк более понятно выглядит — хотя комментарии насчет «у нас особые условия, не пытайтесь повторить их без участия профессионалов» вполне уместны )
Интересно узнать про магию AtomicReferenceUpdater'а, в чём она проявляется в данном случае?
Это для красного словца — никакой магии сверх той, что описана в спецификации AtomicXXXUpdater (т.е. возможность делать CAS/lazySet через reflection на волатильных полях без создания объектов AtomicXXX) здесь не используется.
и фактически она нужна только в реализации, где используется atomic ref array.
UFO just landed and posted this here
+1 за «нагибатор» вместо «нагребатора»

И ещё предлагаю на пару с ThrottlingExecutor завести Троллинг Экзекьютор!
Троллинг экзекьютор используется как вопрос на 6.0 на интервью. И надо сказать, что есть такие люди, которые осилили его… Хотя какие они после этого люди.
И у меня похожий велосипед, только разгребаторам выделяются таски по ряду дополнительных критериев.

image
Интересно. И как вы решаете данную проблему? Какие у вас ограничения?
В данном случае реализация на NodeJS. Соответственно проблема многопоточности отпадает в связи с архитектурой платформы (workflow асинхронный, но все выполняется в одном потоке, многопоточность симулируется). Временной интервал для задач варьируется в интервале от нескольких секунд до нескольких минут.
Критерии для обработчиков — типы задач (множество) и интервал приоритетов.
В моем случае потери на скорости распределения ничтожны по сравнению с длительностью выполнения задач, дополнительная оптимизация не требовалась, более важным фактором была гибкость в настройке / диапазон возможных критериев и понимание пользователями принцыпа работы.
Кстати, пообщаться на эту и другие темы со мной и другими троллями, такими как Руслан и Алексей можно будет на 5го апреля на конференции javapoint в Питере.
UFO just landed and posted this here
Хочется и твой доклад послушать, и доклад Руслана — а судя по всему вы будите в параллель — может быть кто из организаторов javapoint сможет уточнить этот момент. И так же то — будет ли видеозапись докладов или нет?

И разве кто спорит, что написание бенчмарков и вообще проведение измерений и единственно верная их трактовка это не искусство?
Да, про видеозапись поддержу. Можно даже как на javazone: материалы только для участников.
ну на счет " материалы только для участников." я бы хотел задать встречный вопрос: а как же быть тем кто не в питере?

С одной стороны интересная конференция, с другой в апреле-мае пройдут java one, javaee, jpoint, где доклады частично пересекаются, на все не съездишь если ты не в питере-москве живешь, а так хоть видео можно было бы посмотреть тех докладов которые не видел.
ну на счет " материалы только для участников." я бы хотел задать встречный вопрос: а как же быть тем кто не в питере?

Конфа платная, соответственно если вы купили билет — вам и запись. Нет билета — нет записи. Если записи раздавать всем после конфы — зачем покупать билет тогда, а тогда конфы не будет по финансовым соображениям.
всегда считал конференции в первую очередь пиаром, но уж никак не способом заработка
для того и спонсоры чтобы покрыть разницу в бюджете между билетами проданными и стоимостью организации

вопрос в том, что для некоторых с учетом билетов и жилья стоимость конференций получается отнють не такой какая она стоит на сайте, а можно смело суммарные траты умножать на 5 от стоимости билета и брать отпуск/отгулы на 2-3 дня минимум

покупать записи за стоимость участия это вообще больше попахивает бредом, а со скидкой следуя вашей логике продавать тоже не стоит
Кстати, Владимир, на прошлой или позапрошлой J1 я у вас спрашивал после доклада о GC free логгере, связывались ли вы с ребятами из Logback по поводу полученных результатов.
Я смотрю, вы снова выступаете с тем же докладом — как за год/два прогресс у Logback в плане перфоманса и у вас в плане коммуникаций с девелоперами? )
А не лучше, если приходит задание типа А, а в вашем хеш-хранилище актуальных заданий по типам было пусто для типа A (сделать какой-нибудь getAndSet), то тип положить в очередь тип A. Пусть разгребатор просто берет типы из готовы очереди. Очередь, видимо, лучше сделать на базе кольцевого массива с типичной стратегией удвоения как вы все-равно делаете для хеш-таблицы.
А можно даже это хеш-хранилище объединить с очередью, получается что-то вроде кастомного LinkedHashMap.
Намного чаще встречается задачка, когда нельзя в момент провисаний по загрузке нельзя просто схлопывать по ключу. А когда нужно иногда схлопывать, а иногда чтото более умное делать. Вот на эту тему написал я недавне штуку под названием ActiveThrottlingQueue. Работает по принципу очереди событий, когда парсинг событий делится на 2 части, быстрая загрузка событий в локальный структуры и мапы, а затем груповая работа по обработке изменений.
>нагибатор/нагребатор
У меня дежавю или я с ума схожу — какое-то время назад как будто уже читал эту статью… и главное как будто даже комментарии по поводу терминологии такие же были
Я тоже немного занимался задачей троттлинга (мы из одного домена, если вы меня не знаете :) ). Реальное использование выявило такой кейс: таска, которая кидается в executor может блокироваться, а апдейты на данное значение продолжают сыпаться. Есть какие-нибудь идеи насчет API для этого кейса? Я выкрутился тем, что обернул каждое сообщение в провайдер, который по запросу элемента возвращает самое актуально значение.
Не совсем понял ваш случай — мы после того как вынули значение и передали его на выполнение уже никак не можем прервать её выполнение. И пока она выполняется, конечно же, другие всё новые и новые значения могут приходить, только выполнены они будут в следующую итерацию.
> только выполнены они будут в следующую итерацию.

Ага, вот как раз надо было придумать как от этого избавиться. Проблема в том, что заблокированный метод может довольно долго ждать.
Сгребатор от LMAX: The Coalescing Ring Buffer.

Структура данных типична для Disruptor: кольцевой буффер.Из отличительных особенностей:

— доступны исходники уже сейчас
— сохранение порядка обратки сообщения в зависимости от его прихода
— только один нагребатор (тм) и один разгребатор (тм)
— нет wait-strategy для разгребатора
— есть маленькая ненулевая вероятность, что разгребатор увидит дупликаты

p.s. у меня нет выбора, чтобы не открыть свои исходники
Прочитал статью и комменты, и не понимаю — это какая-то специальная Java для посвященных? И если не знаешь, кто такой Руслан или D., то вход в элитный отряд закрыт?

В целом, пока вижу сферического коня в вакууме. Что дает измерение задержки executor-а? Каким образом оно меряется, в каких точках? Производительность нельзя рассматривать в отрыве от задачи. Что важнее — количество обработанных событий в секунду/день/год, или минимальная пиковая задержка? А если есть ограничение по задержке — скажем, опоздали на 5 секунд и событие уже не актуально, более того, можно потерять деньги, если его обработать?

Выгоднее ли потратить свое время на велосипед и на его поддержку, чем взять простой HashMap + BlockingQueue + купить 4 сервера?
>купить 4 сервера?

Ну наверное действительно, если вам задача позволяет просто поставить больше серверов, то наверное статья не для вас. А если интересоваться темой оптимизаций в java, то ИМХО не знать что такое D. довольно странно. Ну и лекторов по данной тематике не знать тоже.
Прошу меня простить, темой я интересуюсь, но лекторов или D. не знаю. В Питер ездить на конференции не имею возможности. Но вы опубликовали статью на хабре, значит — для всех, а не только для избранных. Отсюда и вопросы.

Я так понимаю, вы же реальные данные от третьих источников обрабатываете? Насколько я помню, в DB пишут трейдинговую систему, и данные идут из NY? Если я правильно вас погуглил.

Что дает измерение задержки executor-а на маленьких тасках? Надежней мерять не маленькие интервалы и суммировать, а нагрузить приложение реальными данными (или похожими на реальные) и измерять количество данных в час/день/неделю — эта величина коррелирует с latency. Плюс нужен тест на простой реализации, для сравнения. Ну или для смелых — сравнить прибыль в $$$ :)
Прочитал статью и комменты, и не понимаю — это какая-то специальная Java для посвященных? И если не знаешь, кто такой Руслан или D., то вход в элитный отряд закрыт?

Нет, это просто Java. Просто без EE.

Насколько я помню, в DB пишут трейдинговую систему, и данные идут из NY? Если я правильно вас погуглил.

Нет, не верно, данные идут с локальных бирж (или локальных точек присутствия бирж и это не только из Нью Йорка, это прежде всего Лондон, Токио, Сингапур.

Что дает измерение задержки executor-а на маленьких тасках

Время прохождения через executor, т.е. какова величина накладных расходов, которые даёт сам executor. Разумеется, что мы измеряем производительность системы целиком, но в то же время меряем и производительность отдельных компонент, в т.ч. и используя синтетические тесты.

Выгоднее ли потратить свое время на велосипед и на его поддержку, чем взять простой HashMap + BlockingQueue + купить 4 сервера?

Нет, задача не масштабирования уровня обработать 10000 и 1 запрос, когда каждый из них будет обрабатываться 100 мс — вопрос в том, что каждый запрос должен быть обработан как можно быстрее, в так называемом sub millisecond latency.
Время прохождения через executor, т.е. какова величина накладных расходов, которые даёт сам executor

А, теперь понятна цель. Не ясно, каким образом это измеряется. Перед помещением задачи в executor вызывается System.nanoTime(), это значение передается в таск, а в нем опять вызывается nanoTime() и считается разница? А результат в отдельный поток?

Это я к тому, что по количеству обработанных задач в некий промежуток времени можно оценить накладные расходы более точно, чем считать время в каждом таске.

Нет, задача не масштабирования уровня обработать 10000 и 1 запрос, когда каждый из них будет обрабатываться 100 мс — вопрос в том, что каждый запрос должен быть обработан как можно быстрее,

Понял.
Как показал мой опыт (тут ожидаю появление джина) nanoTime не самый лучший способ измерения времени между потоками, мы используем некоторый свой jni вызов, который возвращает системное время в нс — немного об этом у меня в блоге.

Результат измерения latency складывается в массив на потоке разгребатора, где уже по окончании уже он забирается целиком.

Если сильно упрощённо, то
final long[] times = new long[5000];

// .....
final long diffUs = nowUs() - event.timeUs;
times[diffUs]++;
//
Да, помню статью про RDTSC на хабре. А зачем брать CLOCK_REALTIME, для измерения задержки достаточно ведь MONOTONIC? Там, конечно, полно всякого кода, но CLOCK_REALTIME его же и вызывает, плюс еще и wall clock читает. Могу ошибаться, исходники ядра давно ковырял.
Решение используется не только для оценки задержки внутри одной JVM, но и для оценки latency между компонентами на разных серверах — всё равно jni библиотеку делать и подключать, а разница между CLOCK_REALTIME и MONOTONIC не столь критичная. Хотя, можно вынести как отдельный метод для специальных случаев.

Конечно же, в этом случае, мы передаём мкс, а не нс.
>Это я к тому, что по количеству обработанных задач в некий промежуток времени можно оценить накладные расходы более точно, чем считать время в каждом таске.

Какие именно накладные расходы можно оценить «более точно»? Чтобы говорить то, что вы говорите, нужно иметь в голове некую модель того, что происходит в системе, и что именно мы хотим измерить. Измеряя количество задач в единицу времени вы тестируете систему в режиме насыщения — в этом режиме вы можете упираться в совершенно другие узкие места, чем упирается время ответа в ненасыщенном режиме.

Например, в ненасыщенном режиме у вас «стоимость» CAS-а (внутри put)будет примерно постоянной, потому что contention будет низким, и вероятность неуспешного CAS-а тоже будет низкой. Зато будет важна эффективность работы CardMarking-а — потому что в среднем таблица будет едва-едва заполненной, и сканировать ее целиком каждый раз будет не эффективно, и CardMarking тут очень к месту.

А в режиме насыщения CardMarking будет только мешаться, потому что таблица скорее всего будет заполнена, и толку от CardMarking будет мало, а накладные расходы он вносит. С другой стороны, стоимость CAS-а вырастет (поскольку вырастет contention), и начнет заметно зависеть от всяких деталей компоновки объектов в памяти, и количество неуспешных CAS-ов тоже вырастет, и поэтому будет важна реализация backoff после неудавшихся CAS-ов.

Это так, навскидку описание. Все может быть иначе, и вообще дракон из монитора вылезет и откусит экспериментатору голову.

Для нашей конкретной задачи нужно минимальное время доставки до обработчика каждого конкретного таска. Предельная пропускная способность нас интересует гораздо меньше — у нас система даже близко не подходит к режиму насыщения. Поэтому эксперимент поставлен так, как поставлен. «Плюнь тому в глаза, кто скажет, что можно обнять необъятное!»
Для нашей конкретной задачи нужно минимальное время доставки до обработчика каждого конкретного таска.

Да, теперь понятно, спасибо
>Надежней мерять не маленькие интервалы и суммировать, а нагрузить приложение реальными данными (или похожими на реальные) и измерять количество данных в час/день/неделю — эта величина коррелирует с latency.

Надежнее всего, на мой взгляд, не думать, что есть единственно правильный метод измерять что либо. Есть безусловно место и для синтетических бенчмарков всего приложения в целом. Но если вы не представляете что происходит в вашей системе на уровне отдельных ее компонент, вы не сможете правильно интерпретировать эти синтетические бенчмарки. Что толку знать, что у вас полное время ответа 100мс, когда надо 20, а вы не представляете как можно хотя бы один из этапов обработки сделать быстрее 50мс, потому что складывая пасьянс из стандартных примитивов быстрее не сделать?

>Что дает измерение задержки executor-а на маленьких тасках?

Интерпретация _любых_ бенчмарков требует аккуратности и опыта. Ложные выводы можно сделать как из результатов синтетических тестов «в $$$», так и из результатов микробенчмарков в микросекундах.
— взять простой HashMap + BlockingQueue

Можно, только в этом случае очередь будет расти. Более того, все последующие ключи в очереди по-сути уже будут не нужны — т.к. самое актуальное значение будет удалено из HashMap-ы при первом же обращении по ключу.

Второй момент — это доп. расходы на поддержание обеих структур — расход по памяти, (cache locality), атомарность, видимость значений.

И третий момент — 4 сервера исходя из каких соображений? Из тех, что пишем код как получится, а после закидываем шапками?
Можно, только в этом случае очередь будет расти

Можно сделать так:
previousValue = concurrentHashMap.put(key, newValue)
if (value!=null) {
    queue.add(...);
}

Ну и забирать через put(key, null)

4 сервера были взяты с потолка.

В целом, ваша задача понятна. Я вообще начал этот тред т.к. зацепился за фразу «типичная проблема в большом классе задач». В процессе обсуждения оказалось, что задача специфическая, с известными рамками.
Фраза звучит примерно так:

«типичная проблема в большом классе задач в алчном нашем мире борьбы за то кто кого быстрее обманет, продавая валюту»

Можно сделать так: previousValue = concurrentHashMap.put(key, newValue) if (previousValue != null) { queue.add(...); } Ну и забирать через put(key, null)

Хорошо, это вы решаете проблему нерастущей очереди.

Один из самых больших выигрышей (в нашем случае, и быть может в случае других алчных задач) даёт как раз использование wait strategy, в сравнении с использованием wait/notify. Это ответ почему не BlockingQueue.

И в финале решили объединить только самое необходимое из двух структур в одно.
Да, теперь понятно. Я решаю немного другие задачи, нужно обработать как можно больше данных за как можно меньшее время, но в пределах секунд, не мили (тем более микро) секунд. Для меня действительно может быть лучше купить еще сервер, чем потратить неделю на тюнинг и возможные баги.
Sign up to leave a comment.

Articles

Change theme settings