суббота, 2 августа 2014 г.

[prog.sobjectizer] Подробнее о SObjectizer-5.4.0

Этот большой пост рассказывает о том, чем версия 5.4.0 отличается от предыдущих версий. Его цель -- это краткая фиксация данной информации в одном месте на русском языке. А уже отталкиваясь от этого текста будут созданы соответствующие описания на английском в Wiki на SourceForge.

четверг, 31 июля 2014 г.

[prog.sobjectizer] Работы над overload control в версии 5.4.0 свернуты

Сегодня принято решение, что в новой версии 5.4.0 не будет никаких механизмов обеспечения overload control. Главная причина -- это отсутствие живого проекта на SObjectizer, где бы этот механизм был бы востребован здесь и сейчас. Посему работы над ним выглядят как реализация возможности ради возможности, а не как воплощение в жизнь актуально необходимой штуки. Но на данный момент я не могу позволить себя такую роскошь, как "возможность ради возможности", тем более, что это выливается в ощутимые затраты на тестирование и документирование. И еще в непонятно какие затраты на сопровождение в последствии.

В общем, ситуация получилась не самая приятная: побежал куда-то, но выяснилось, что зря побежал. С другой стороны, вовремя остановился. Ну и на ошибках же учатся. Так что какая-то польза все равно есть.

То, что удалось придумать к сегодняшнему дню, зафиксировано здесь.

Дальнейшие действия в отношении SObjectizer-а будут такими. Сегодня-завтра реализация еще одной актуальной штуки, которая всплыла недавно и которую полезно включить в версию 5.4.0. После чего документирование того, что получилось. А так же расширение набора документации на SourceForge. Этому я постараюсь уделить максимум усилий. После чего подготовка и релиз сборки 201408-00. На ближайшие 2-3 недели работы выше крыши :)

Потом в планах старт какого-нибудь публичного демо-проекта. И, параллельно с ним, начало работ над 5.4.1 (wish-list уже открыт). Если в демо-проекте потребуется overload control, то он будет включен в скоуп работ для 5.4.1.

Что это будет за демо-проект -- пока вопрос открытый. Лично я думал над какой-то штукой в области MQTT и/или CoAP. Либо же это может быть интеграция с какой-нибудь MQ-системой (типа AMQP-клиента).

Но тут многое зависит от интереса сторонних наблюдателей. Если кто-то проявит интерес к SObjectizer в какой-то другой предметной области, то я обязательно очень тщательно изучу такие варианты.

[life.cinema] Очередной кинообзор (2014/07)

Кратко о просмотренных в июле фильмах. Как обычно, в начале хорошие, по моему мнению фильмы, в конце -- отстойные. "Неуловимых-3" в списке нет, т.к. официально они еще не вышли (но можно глянуть сюда).

Еще перед обзором хотелось бы поделиться вопросом, который меня не отпускает после просмотра "Повара на колесах". Почему в СНГ никто ничего подобного не снимает? Простая история про своеобразного чувака, увлеченного своим делом, про его взаимоотношения с сыном, с банальным хеппи-эндом, но при этом все сдержанно оптимистично без ухода в сопли или пафос. Можно было бы попробовать допустить, что у 2/3 жителей РБ и РФ жизнь совсем невеселая, приходится каждый день бороться за выживание, некогда отвлекаться на простые житейские радости и д.т. и т.п. Но что-то когда я хожу по улицам своего города, покупаю продукты на рынке или еду в общественном транспорте, я не вижу тотального уныния, озлобленности и безнадеги и отчаяния в глазах каждого встречного. Проблемы, наверняка есть, но не в таком количестве, чтобы в обществе была гнетущая атмосфера, принуждающая "творцов" снимать "Остров", "Иван Грозный" или, простите мне этот запрещенный удар, "Бумажного человека". По-моему, в консерватории что-то не так, если на угребищный высер под названием "Сталинград" находятся и инвесторы, режисеры, и сценаристы, и актеры и пр. А вот для чего-то в стиле "Повара на колесах" -- нет.

Прошу прощения у читателей за этот эмоциональный выплеск, ну а теперь, собственно, к слайдам.


Все и сразу. Хороший фильм, мне очень понравился.

Превосходство. Хорошая фантастика, давно ничего подобного не попадалось. Но в финале каких-то соплей накрутили, чем несколько подпортили впечатление от фильма.

Мерзлая земля. Хороший, добротный фильм, я бы сказал, снятый в старом духе. Но можно было бы сделать его минут на 10 покороче.

Повар на колесах. Добрый, жизнеутверждающий довольно предсказуемый, но очень здорово сделанный семейный фильм.

Оборотень. На удивление крепкий фильм-середнячок на тему оборотней.

Need for Speed: Жажда скорости. Сюжета нет, актерская игра так себе. Но вот съемки гонок на все деньги и это видно.

Саботаж. Корявенько. Практически весь фильм было к чему придраться. Ну а уж финал вообще все испортил. Хотя видеть играющего Анри все-таки приятно, хотя бы из ностальгических соображений.

Кровавая месть. Первые две трети фильма были вполне себе смотрибельными. Да и рукопашные схватки с участием Джины Карано оставляли хорошее впечатление. А вот потом, похоже, деньги закончились. Как на сценарий, так и на режиссуру, и на операторскую работу.

Пластик. Незамысловато и предсказуемо. В то, что снято по реальным события не верится ни разу. Но в качестве развлекательного фильма сойдет.

Перейти черту. Просто удивительно, как при таких хороших актерах, шикарной операторской работе и нормальном сценарии получилась такая тягомотная хрень.

Зной. Мог бы быть неплохим атмосферным боевичком, что-то вроде средней руки вестернов, но перенесенных в современность. Но обилие клюквы и соплей все убило. Хотя ван-Даммыч на вторых ролях смотрелся вполне себе ничего.

Путь нечестивых. Абсолютный шлак. Упоминаю лишь потому, что жалко видеть, что уникально фактурный Винни Джонс не смог стать характерным актером и занять свою нишу харизматических маньяков в хороших фильмах. Все-таки "Карты, деньги, два ствола" задрали планку так высоко, что дотянуться до нее уже не получается.

Новый человек-паук 2: высокое напряжение. По степени маразма и идиотии этот фильм находится далеко за гранью моего понимания того, как же это можно смотреть. Даже ради спецэффектов.

среда, 30 июля 2014 г.

[management] Это просто шедеврально (детали закрытия Adobe-ом своего Flash-а)

Нашел на просторах Интернета замечательный пост от 2011 года, цитата оттуда (курсив мой, это самое шикарное):

Коротко.
Flash is no more.
Продукт переводят в Индию, на весь процесс отводится полгода.
Плеер для мобильных устройств закрыт.
Офис в Оттаве и в Техасе закрыт. О других не знаю.
Уволено 750 человек. В том числе - программисты самого высокого уровня. Уволен руководитель продукта, все менеджеры и все сотрудники(за очень малым исключением, у нас оставили одного человека из большой группы).

Сегодня нам прислали письмо, в котором ситуация описывается словами
"еще никогда в нашей компании дела не шли столь успешно, поэтому мы открыаем новые замечательные проекты на основе нашего замечательного продукта! И увольняем 750 человек, профессионалов высокого класса. С тяжелым сердцем мы совершаем этот шаг, но уверенно глядим в будущее."

[life.cinema] Про третьих Неудержимых

Не припомню, чтобы в последние несколько лет фильм в приличном качестве ушел в Интернет до его официальной мировой премьеры. А тут внезапно. Да еще и не абы какой фильм, а, потенциально, главный блокбастер августа.

Однако, по факту. Имхо, этот проект Сталонныча исчерпал себя уже на второй части. Смысла делать третью не было. Снято не хуже и не лучше предыдущих. Но те хотя бы брали своей ностальгией по олд-скульным боевикам с героями минувших дней в главных ролях (чего только стоит первое появление Чака Норриса во вторых Неудержимых). В этом уже ничего подобного нет.

вторник, 29 июля 2014 г.

[life.photo] И эту фотографию выкладывают в качестве образца? Ппц!!!

Имхо, фотография больше подходит под категорию "Жанр", чем "Свадебный альбом". Остается надеяться, что к заказчику она не попала (хотя, скорее всего, попала):

Цинк.

[prog.thoughts] О производительности вообще, показателях микробенчмарков в частности, ну и о C++

Начну, как водится, издалека. Тем, кому не интересна вступительная часть про SObjectizer, можно начать читать отсюда.

Для C++ сейчас есть три живых OpenSource фреймворка, дающих возможность разрабатывать софт с использованием Actor Model. Это -- SObjectizer, C++ Actor Framework (CAF, ранее libcppa) и Theron. Два последних, в отличии от SObjectizer, в своих материалах уделяют внимание своей высокой производительности. Мол, в таких-то тестах мы показываем столько-то миллионов сообщений в секунду, а в таких-то тестах -- столько-то. SObjectizer же похвастаться большими цифрами в микробенчмарках никогда не мог. Более того, оптимизация его производительности никогда не была приоритетом №1, особенно на ранних стадиях разработки SO-4 и SO-5. Более того, в том же SO-4 микробенчмарки появились лишь спустя несколько лет после начала активного использования SObjectizer в продакшене.

Причина тому проста: SObjectizer создавался как рабочий инструмент, и скорость его работы была всего лишь одним из его качеств, причем далеко не самых важных. В подавляющем большинстве случаев, для решения прикладных задач производительности SObjectizer хватало. Припоминается лишь два случая, в которых пришлось серьезно засесть за оптимизацию производительности. В первом случае это был не сам SObjectizer, а библиотека MBAPI-3, построенная над ним, в которой были более сложные правила определения получателей сообщений и рероутинга перехваченных и частично обработанных перехватчиками сообщений. Второй случай -- это мой косяк в реализации транспортного слоя с использованием ACE_Reactor-ов (не вкурил в должной степени руководство по взаимодействию ACE_Reactor-ов, ACE_Event_Handler-ов и ACE_SOCK_Stream), из-за чего в определенных ситуациях данные из сокетов вычитывались гораздо медленнее, чем это должно было происходить. Производительность же основной функциональности (т.е. обслуживание агентов и их коопераций, доставка и обработка сообщений) практически всегда была good enough.

Вопросы производительности стали выходить на передний план после того, как мы стали пытаться выпустить SObjectizer "в свет". Тут уж ничего не поделать, это одна из маркетинговых фишек. Когда конкуренты трубят, что у них пропускная способность в 20M сообщений в секунду, очень хочется смело заявить, что у нас 25M. Или, хотя бы, у нас 18.5M, но зато каждый покупатель получает 3 года бесплатного сервисного обслуживания и подарок -- фирменную чашку с логотипом нашей компании :)

Из-за этого меня несколько раз настигало что-то вроде лихорадочного стремления получить высокие показатели в микробенчмарках. В последний раз это произошло буквально несколько недель назад, во время работы над версией 5.4.0. Излечение же было несколько болезненным, но быстрым :)

Сначала плотно задуматься о том, что "где-то что-то не так", заставили результаты экспериментов с lock-free очередями. В процессе которых вспомнилось то, о чем я когда-то читал, но забыл. А именно: для выжимания максимальной производительности из современных процессоров не нужно создавать большое количество нитей и уж тем более часто передавать информацию между ними. Напротив, нужно свести количество рабочих потоков к минимуму, постараться делать всю работу в рамках одного потока, стараться как можно меньше передавать информацию между потоками, а так же попробовать устранить модификацию общих данных в ОП из нескольких потоков. Т.е., работая на процессорах с 4-8-16 и более ядрами, нужно отказаться от соблазна распределить 50 мелких задачек между всеми имеющимися ядрами. Вместо этого лучше разбить их всего на два-три потока, а то и вообще попробовать выполнить их все последовательно в одной нити, работающей на одном ядре.

Самое смешное, что окончательно от "лихорадки микробенчмарков" я избавился вчера вечером, когда читал большую статью "Why mobile web apps are slow". Статья очень большая, не новая (опубликована чуть больше года назад), да и уже попадалась мне на глаза ранее. Но тогда я только мельком ее просмотрел, а вчера прочитал полностью. Статья классная. Заставляет смотреть на вопросы производительности по-новому, даже если ты и не имеешь отношения к разработке мобильных приложений на JavaScript-е.

Так что теперь я смотрю на показатели микробенчмарков различных фреймворков для обмена сообщениями совсем иначе :) Вы заявляете, что помогаете выжать максимум из многоядерного железа и при этом говорите о пропускной способности в 20M сообщений в секунду? Но если кто-то выжимает максимум из многоядерного железа, то зачем ему 20M раз в секунду пересылать информацию между потоками? Даже если информацию нужно пересылать 1M раз -- это уже выглядит подозрительным. И даже если 100K раз. Какое отношение количество обменов сообщениями между сущностями имеет к производительности? Для производительности-то как раз нужно, чтобы этих обменов не было вообще. Ну или чтобы количество этих обменов исчислялось единицами, максимум десятками раз. Batch processing, знаете ли, рулит не только при работе с СУБД. Вместо пересылки 100K мелких сообщений лучше было бы собрать 5 пакетов по 20K штук и переслать ссылки на эти пакеты (да еще так, чтобы после их обработки выделенную под пакеты память освободила та нить, которая их собирала, или же вообще скопировать все содержимое пакета в принадлежащий другой нити буфер). А то и вовсе не передавать эти пакеты никуда, а сделать всю обработку в рамках старого-доброго однопоточного приложения: одна процедура нагенерит 100K объектов, вторая процедура их следом обработает. И все.

Но статья "Why mobile web apps are slow" заставила задуматься еще о C++ вообще. О том, какой смысл писать сейчас что-то новое на C++. И о том, почему на C++ не пишут что-то новое.

Есть у меня подозрение, что современный C++, тот, который C++11, это крайне недооцененный язык. ИМХО, репутация С++ была слишком сильно подмочена длительным принятием стандарта C++98, затем еще более длительным появлением компиляторов, хотя бы более-менее нормально подерживающих С++98/03. Фактически, если говорить про платформу Windows, что-то более-менее приличное с поддержкой C++98 появилось только с выходом VisualStudio 2003, то, что до этого было в VisualStudio 98 (т.е. Visual C++ 6.0) -- это ужас и кошмар. Ну а еще более комфортной работа с C++98/03 стала при появлении VisualStudio 2007 (там и скорость компилятора была выше, и генерируемый код быстрее, и поддержка стандарта строже). Т.е. с момента оформления стандарта до появления действительно хорошего компилятора для самой популярной платформы прошло почти 10 лет. На которые пришелся еще и бурный расцвет web-программирования, оттянувшего на себя значительную долю разработчиков с десктопа.

К чему это все привело? Да к тому, что старых C++разработчиков, начинавших в во времена до C++98, осталось совсем мало. Многие просто перешли в другие предметные области и, соответственно, на новые языки/технологии. Кто-то вообще отошел от программирования. Т.е. старых, матерых, C++ников, помнящих эволюцию C++ хотя бы с 1991-го и по нынешние дни, осталось совсем-совсем мало. Не знаю, как много новичков, которые пришли в C++ после 2003-го, подозреваю, что немного. К тому времени у C++ уже прочно сложилась карма чрезвычайно сложного, маргинального инструмента, на котором невозможно написать что-то более-менее серьезное, без большого количества глюков внутри. Такая репутация небезосновательна, но она была заслужена задолго до того, как на C++ научились программировать нормально, проложив мостики между граблями, о которых так любят говорить в форумных войнах.

Но, самое важное, все это было до C++11. А C++11 -- это уже совсем другой C++, намного более безопасный и значительно более быстрый, чем C++98/03. Всего лишь несколько новых вещей, таких как rvalue references и move semantics, lambda-функции и variadic templates, shared_ptr и unique_ptr в стандартной библиотеке, и язык стал совсем другим. Писать быстрый и надежный код на C++11 намного, очень намного проще, чем на C++98/03. Правда, многословность C++ все равно остается, что правда, то правда, но тут уж вряд ли что-то изменишь.

Только вот вряд ли к C++11 потянутся толпы желающих писать быстрые программы. По многим причинам. Начиная от безнадежно испорченной кармы C++ и заканчивая модой на функциональщину (ведь изучать Haskell -- это же гораздо круче, чем изучать C++11, моднее, да и сразу переводит неофита в категорию крутых спецов). А так же тем, что за производительность программ (пока?) не платят.

Хотя, у меня после прочтения "Why mobile web apps are slow" сложилось впечатление, что это пока. Там ведь говорится о вещах, которые касаются не только разработки под мобильные устройства. Там много о накладных расходах, налагаемых сборщиком мусора, например. А так же, вскользь, о том, что базовые принципы, заложенные при разработке языков с VM или промежуточным байт-кодом, ставят во главу угла безопасность, а не производительность. Так что пока это может быть актуально для портативных устройств (но ведь и этот рынок постоянно растет, причем его рост опережает рост аппаратных мощностей мобильных устройств). Хотя, не исключено, что и на рынке серверных и десктопных приложений скоро начнут считать деньги по-другому. И предпочитать решения, которые требуют 10 серверов вместо 20 (кстати, почему сейчас Facebook вкладывает силы в разгон своего PHP-кода, а Twitter переписывал свои куски с Ruby на Scala?).

Но это все банальности о которых я, да и не только я, говорили неоднократно. Думаю, что сейчас важнее другой момент, на который меня подтолкнула статья "Why mobile web apps are slow": а чем ваши продукты будут отличаться от продуктов конкурентов? Если конкурирующие продукты сделаны такими же посредственными хорошими программистами, как и ваши, на тех же самых инструментах и технологиях, с такими же требованиями к железу и такой же ресурсоемкостью?

Т.е., если вы выпускаете какой-то новый, революционный продукт, не имеющий аналогов и захватывающий рынок, то без разницы, на чем он написан -- на Haskell, Erlang, Scala, Java, C#, Python или Ruby. Но когда к вам подтянутся конкуренты, когда рынок будет поделен и рулить продажами будут маркетологи, устраивающие распродажи или привлекающие внимание новых пользователей удачными рекламными роликами? За счет чего снижать издержки, уменьшать стоимость транзакции и увеличивать прибыль? Ваша Java на ваших серверах будет тормозить так же, как такая же Java на серверах конкурентов. Какую-то часть забот возьмет на себя техподдержка, грамотные люди оттуда будут вовремя заменять старые диски на новые, производить апгрейд железа, оптимизировать распределение ресурсов, перекраивать лимиты расхода ресурсов и т.д. Но это мало касается разработчиков. Чем разработчики смогут помочь в такой конкурентной борьбе?

Насколько много тут возможностей? Имхо, не так уж и много.

Во-первых, это люди. Чем более толковых разработчиков вы сможете заарканить, заинтересовать и удержать, тем лучше. Новые идеи и тщательное отношение к работе дадут вам такое преимущество над командами разработчиков у конкурентов, какое вам и не снилось, даже если они будут использовать одинаковые инструменты и технологии. Проблема, правда, в том, что когда главными людьми в компании становятся маркетологи, никому нет дела до конкретных людей, занимающихся разработкой -- это обычное следствие преобразования компании из инновационной адхократии к механистической бюрократии.

Во-вторых, уникальные инструменты. Так, если и вы, и конкуренты, используете Java, то при более-менее равных командах скорость воплощения новых фич и количество ошибок на 1000 строк кода и у вас, и у конкурентов будет одинаковой. Скорость работы и требования к ресурсам, скорее всего, тоже. Однако, если вы возьмете другой инструмент, ситуация изменится. Например, у Haskell-я есть репутация языка, пропускающего гораздо меньше ошибок в успешно скомпилированный код, чем Java и C#. Это офигенное конкурентное преимущество для команды разработки. Ведь, если у вас уходит три дня на реализацию фичи и один день на тестирование, то это позволяет выводить новую функциональность на рынок быстрее, чем когда на реализацию нужно два дня и затем еще три дня на тестирование.

Аналогичный эффект могут давать и языки, способные производить очень быстрый и потребляющий мало ресурсов код. В частности, C++. Ведь если ваше решение дает время отклика на 15% быстрее конкурента, при этом требуя в два раза меньше железа, то это так же преимущество. И, если оно есть (причем оно объективно есть, уже неоднократно показывалось, что языки вроде Java/C# способны показывать сравнимую с C++ производительность только при задействовании гораздо больших ресурсов, в частности, нужны объемы памяти от 4-х до 6-ти раз большие), то почему бы его не использовать?

Для этого, правда, нужно отойти от стереотипов о том, что C++ -- это старое и глючное говно мамонта. Таковым он, может быть, и был года до 2007-го. Но сейчас, с появлением нормальной поддержки C++11 в новых C++ компиляторах, это точно не так.

[prog] Интересные ссылки на тему overload control

В продолжение вчерашней темы. Вот несколько ссылок на интересные материалы, которые попались в Интернете на эту тему. Если кто-то сможет поделиться еще чем-то, буду очень признателен.

Adaptive Overload Control for Busy Internet Servers, 2003 год. PDF-ка на 14 страниц от разработчиков фреймворка SEDA. Читать оттуда можно лишь несколько страниц, где описываются особенности трех механизмов контроля за нагрузкой на сервисы. Ценность этой статьи в том, что она явно показывает, что хороший механизм защиты от перегрузки должен использовать максимум знаний о предметной области, а само прикладное решение должно изначально строится с оглядкой на действие механизма защиты от перегрузки.

Следующие три ссылки -- это статьи Макса Лапшина, стоящего за Erlyvideo/Flussonic:

Overload Protection 1 и Overload Protection 2 -- две первых части из серии статей о том, как в Erlyvideo справлялись с пиковыми нагрузками (в каждой статье есть ссылки на следующие статьи серии, так что легко при желании прочитать их все, оно того стоит, ИМХО).

Про эрланг vs XXX: интроспекция для отладки. Очень интересная заметка. Не столько про защиту от перегрузок, сколько о том, что активный Elrang-юзер считает реально ценным и полезным в такой мощном инструменте, как Erlang.


Лично для меня напрашивается следующий практический вывод: базовый фреймворк должен предоставлять лишь базовые средства. Например, лимиты на максимальные размеры очередей, диагностика и простейшая реакция на их превышение. Возможно, еще одну штуку нужно дать пользователю на уровне базового фреймворка -- это получить текущую длину очереди сообщений агента. Собственно и все.

Более сложные и изощренные механизмы должны предоставлять заточенные под конкретные задачи прикладные библиотеки/фреймворки, построенные на основе базового фреймворка.

понедельник, 28 июля 2014 г.

[life.question] Spyderco Tri-Angle Sharpmaker

А не подскажет ли кто-нибудь, возможно ли купить subj у на в РБ? Да еще так, чтобы оплата была по факту получения, чтобы сначала убедиться можно было, что все в наличии, а потом уже деньги платить?

Или же только под заказ со 100% предоплатой? И если это так, то можно ли покупать на официальном сайте с доставкой в РБ?

[prog.sobjectizer] Цели, которые будут поставлены перед механизмом overload control.

Понимаю, что то, что я пишу в блоге про SObjectizer, интересует малую долю моих читателей. Однако, в данном случае нужно задействовать блог в качестве канала для привлечения желающих пообсуждать очень интересную и непростую тему. А именно: тему защиты агентной системы от перегрузки (overload control). Поэтому прошу прощения у тех, кому не интересно, всех же остальных приглашаю под кат.

Тема очень непростая. Помнится, лет семь назад мы очень плотно ее обсуждали с Дмитрием Вьюковым, тогда применительно к SObjectizer-4. Но до чего-то путного не договорились. Теперь же у меня есть буквально неделя-две, чтобы включить какой-то базовый механизм overload control в релиз версии 5.4.0. А конь, как говорится, еще не валялся. Так что задача, может, и безнадежная, но зато увлекательная.

Дело в том, что если мы работаем в рамках компонентной системы, в которой преобладают синхронные механизмы взаимодействия между компонентами, то сами эти механизмы выступают естественными регуляторами нагрузки. Например, представим себе классический Unix-овый подход, когда стандартный поток вывода одной программы подается на стандартный поток входа другой программы:

воскресенье, 27 июля 2014 г.

[life] Актуальное противопоставление "Солдат морской пехоты / Ветеран"

Актуальность навеяна многим. В том числе и осознанием того, что произнесенная вслух фраза "Я же старый больной человек", уже содержит лишь долю шутки...

Это два разных человека, а не один и тот же, но в разные годы своей жизни. Поскольку такова была задумка автора двух этих снимков, придумавший такое сочетание фотографий. Хотя, возможно, противопоставление фотографии ветерана в молодости и его нынешнего снимка, выглядело бы сильнее.

Источник: Созданные равными на AdMe.ru.

[life.work] Жизненно, блин...

Бернард Шоу:

Мир состоит из бездельников, которые хотят иметь деньги, не работая, и придурков, которые готовы работать, не богатея.

Источник: AdMe.ru

[prog.c++] Чего ждать от lock-free структур при активном использовании динамической памяти?

В последние несколько дней активно экспериментировал с lock-free и не lock-free очередями для передачи данных между нитями. Хотел выжать максимум возможного. Получил несколько обескураживающие результаты и сделал для себя соответствующие выводы. Полагаю, что для опытных в многопоточном программировании людей все нижесказанное будет очевидными и давно известными вещами. К сожалению, таковыми для меня они не оказались. Потому, что вижу, о том и пою, гуру от многопоточности этот текст могут не читать.

Итак, если вы активно используете в своем C++ приложении передачу динамически созданных объектов между нитями, да так, что одна нить создает объект, а вторая нить затем его разрушает, то вы не получите очень уж серьезного выигрыша от lock-free очередей (или других механизмов синхронизации передачи информации между потоками, построенных на низкоуровневых инструментах вроде атомиков).

Просто потому, что весь выигрыш, который дают вам lock-free структуры, будет без следа съеден стоимостью операции delete для объекта, аллоцированного в другом потоке. Т.е. если стоимость деаллокации объекта составляет 10мкс, то стоимость передачи указателя на него в другую нить в районе 1-2мкс -- это не о чем. Ваши потоки будут гораздо больше времени ждать на аллокации/деаллокации, чем на CAS-ах или даже тупых spinlock-ах.

Под катом полностью самодостаточный тест, который можно попробовать у себя. Он делает вот что:

Сначала создает N нитей (задается в командной строке или определяется автоматически). Эти нити в цикле делают две операции: создают несколько объектов demand_t и сохраняет указатели на них объекте to_fill. Затем ждут, пока появятся объекты в объекте to_clear. Как только объект to_clear оказывается заполненным, его содержимое (т.е. все объекты demand_t) удаляются.

Смысл в том, что нить может создавать объекты demand_t для удаления другой нитью. Тут все зависит от того, как именно заданы параметры to_fill и to_clear. Сначала тест запускается так, чтобы нить создавала demand_t в свой to_fill, а удаляла из чужого to_clear. Потом тест запускается так, чтобы и to_fill и to_clear были собственными объектами нити. В этом случае операции new/delete происходят на контексте "родной" нити и, потому, получаются гораздо эффективнее. Такое поведение теста контролируется параметром shift: значение 1 указывает, что to_fill будет "родным", а to_clear -- "чужим". Если же shift=0, то to_fill и to_clear -- это родные объекты нити.

У меня на 4-х аппаратных ядрах и четырех потоках под MSVC++2013 получаются следующие результаты:

$ _vc_12_0_x64/release/_experiments.combined_pass_between.exe 4
threads: 4
...
*** shift=1 ***
allocs: 4000000, total_time: 0.953s
price: 2.3825e-007s
throughtput: 4197271.773 allocs/s
assigns: 4000000, total_time: 0.65s
price: 1.625e-007s
throughtput: 6153846.154 assigns/s

*** shift=0 ***
allocs: 4000000, total_time: 0.168s
price: 4.2e-008s
throughtput: 23809523.81 allocs/s
assigns: 4000000, total_time: 0.015s
price: 3.75e-009s
throughtput: 266666666.7 assigns/s

Здесь при shift=1 пропускная способность для передачи динамически выделенных объектов между нитями получилась порядка 4.2M объектов в секунду (4_197_271). Тогда как при shift=0 нити смогли создавать и удалять объекты с суммарной пропускной способностью 24M объектов в секунду (23_809_523).

Отдельной строкой (помеченной как assigns) стоят результаты еще одной модификации этого теста: вместо реальной аллокации и деаллокации объектов demand_t между нитями передаются указатели на статические объекты. Т.е. вызовов new/delete нет, зато есть ожидание на атомиках. Имхо, такой вариант теста показывает максимальную скорость обмена значениями unsigned int между нитями. Т.е. в случае 4-х потоков, каждый из которых читает и пишет по два unsigned int для взаимодействия с другим потоком, суммарная пропускная способность передачи значений составила 6M в секунду. Для сравнения, когда эти unsigned int-ы просто изменяются одной и той же нитью, которая, к тому же не вызывает new/delete, "суммарная пропускная способность" составляет 267M (хотя в этом случае такой термин вряд ли применим вообще).

Цифры 4.2M (для allocs) и 6M (assigns) можно интерпретировать по-разному. При желании можно их удвоить, т.к. каждая нить выполняет операции чтения-ожидания-записи над двумя atomic-ами. С другой стороны, если нить пишет в очередь исходящие сообщения и принимает входящие, то как раз два atomic-а и потребуются. Поэтому я считаю именно так: 4.2M обменов сообщениями между нитями. Хотя, даже если цифры и удвоить, результаты получаются совсем не такие впечатляющие, как в случае с локальной работой каждой нити.

Под Linux и GCC 4.9.0 получается аналогичная картинка (хотя я пускаю тест под VirtualBox с эмуляцией двух процессорных ядер, т.ч. на реальном железе показатели могут отличаться в ту или иную сторону):

threads: 2
...
*** shift=1 ***
allocs: 2000000, total_time: 3.067s
price: 1.5335e-06s
throughtput: 652103.0323 allocs/s
assigns: 2000000, total_time: 2.941s
price: 1.4705e-06s
throughtput: 680040.8024 assigns/s

*** shift=0 ***
allocs: 2000000, total_time: 0.134s
price: 6.7e-08s
throughtput: 14925373.13 allocs/s
assigns: 2000000, total_time: 0.022s
price: 1.1e-08s
throughtput: 90909090.91 assigns/s

Какой практический вывод из всего этого напрашивается? Таковых несколько:

  • во-первых, использование динамической памяти желательно сводить к минимуму, если есть необходимость передавать мелкие объекты между сущностями, намного выгоднее это делать посредством копирования значений;
  • во-вторых, чем больше работы можно сделать на контексте одной нити, тем лучше. Задействовать многопоточность нужно в условиях, когда требуется действительно вытесняющая многозадачность и независимое выполнение каких-то операций (вроде синхронного ввода/вывода, обращения к внешним устройствам/службам, работа с СУБД и т.д.). Либо же в случае, если стоимость выполняемой на другой нити операции реально очень высока и выделение ее на отдельное аппаратное ядро принесет выигрыш (скажем, обработка больших объемов данных, которые можно раздробить на части и поручить обработку независимых частей разным ядрам);
  • в-третьих, при передаче информации между независимыми нитями нужно стараться как можно чаще копировать данные, и как можно реже передавать ответственность за освобождение динамической памяти другой нити.

Ну и, в продолжение практических выводов (кто про что, а вшивый про баню -- я про SObjectizer). В случае реализации модели акторов (агентов, активных объектов) передача сообщений между акторами посредством динамически-созданных объектов -- это губительно для масштабирования. По крайней мере в случае, если память освобождает не та нить, которая выделила память. Поэтому идеальный вариант очередей сообщений для акторов/агентов -- это очередь копий экземпляров сообщений, а не очередь указателей на динамически созданные экземпляры. (С другой стороны, тут еще нужно посмотреть, как оно будет, если у сообщения сразу несколько получателей, а размеры сообщений исчисляются десятками килобайт или же сообщения содержат в себе объекты с указателями внутри, как те же std::string-и или std::vector). Либо же, нужны какие-то механизмы очистки памяти (memory reclamation), чтобы заявка, сформированная нитью-отправителем, не уничтожалась нитью-получателем, а специальным образом помечалась. После чего нить-отправитель могла бы ее подчистить.

Тут, кстати говоря, на повестку дня вновь выходит вопрос о контроле за нагрузкой на акторов/агентов. Например, если можно указать, что для нормальной работы агенту нужно всего 300Kb, то для агента выделяется блок в 300Kb, внутри которого аллоцируется память под адресованные агенту сообщения. Получается такая циклическая арена, внутри которой хранятся сообщения агента и работа с которой происходит максимально быстро... Но это уже совсем другая история.

Под катом обещанный код теста. Красотой не блещет, но мне нужен был quick & dirty вариант для быстрых экспериментов, потому марафет не наводился. Тип demand_t имеет такой вид, чтобы быть более-менее похожим на описание заявки в SObjectizer-е.