среда, 4 февраля 2015 г.

[prog.sobjectizer] На пути к 50M msg/sec... (Akka нам не страшна?)

Некоторое время назад наткнулся на пост "50 million messages per second - on a single machine". Речь там идет о замере пропускной способности Akka 2.0 посредством небольшого микробенчмарка. Не смотря на то, что результаты замеров там приведены более чем трехлетней давности, интересно было посмотреть, что в этом плане может показать SObjectizer.

Прежде всего пришлось разобраться, что именно делает обсуждаемый микробенчмарк. Для этого был изучен его исходный текст. Оказалось, что речь идет вот о чем: создается N пар акторов, обменивающихся друг с другом сообщениями (вроде пинг-понга). Тест завершается, когда каждая пара обменяется M сообщениями. При этом актор-инициатор в первый раз отсылает не одно сообщение, а пачку. Т.е. актор-инициатор шлет K сообщений актору-получателю, тот на каждое полученное отсылает ответное сообщение. Актор-инициатор получив ответное сообщение шлет новое и т.д., пока не будет отослано M сообщений.

Почему я отмечаю момент, что вначале отсылается не одно, а K сообщений актору-получателю? Потому, что в данном бенчмарке это очень важно. Пропускная способность в 50M msg/sec получается при выставлении большого значения throughput. Это значение указывает, сколько сообщений из очереди актора будет обработано диспетчером прежде, чем диспетчер перейдет к обслуживанию следующего актора. Соответственно, если throughtput=200, а в очереди актора всего одно сообщение, то диспетчер будет часто переключаться между акторами. А вот если в очереди актора 200 сообщений, то диспетчер сначала их все обработает, и только потом возьмется за следующего актора.

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

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

Аналогом параметра throughput в SObjectizer-овском thread_pool-диспетчере является параметр max-demands-at-once. Т.е. сколько сообщений из очереди будет обработано подряд прежде, чем рабочая нить перейдет к обслуживанию других очередей.

Насколько я понимаю, в Akka у каждого актора своя очередь сообщений. В SObjectizer чуть посложнее. Во-первых, это зависит от используемого диспетчера. Во-вторых, т.к. в бенчмарке задействуется thread_pool-диспетчер, то чтобы у каждого агента была своя очередь, выставляется параметр fifo=individual (если fifo выставить в cooperation, то очередь будет общей для всех агентов одной кооперации).

Единственная модификация, которая была сделана в SObjectizer-овском бенчмарке -- это добавлен параметр командной строки --messages-at-start. С его помощью можно задать количество сообщений, которые агент отошлет сам себе при старте. Однако выяснилось, что чем меньше этот параметр, тем больше итоговая пропускная способность. Полагаю, дело здесь в том, что чем меньше размер очереди, тем эффективнее происходит ее обработка (меньше промахов мимо кэшей).

Итого, если запускать тест с одной кооперацией из 4-х агентов, каждый из которых отсылает себе по 10M сообщений, то итоговая пропускная способность у меня оказывается порядка 9M сообщений в секунду. Если две кооперации по 4-е агента, то чуть меньше 11M сообщений в секунду:

_vc_12_0_x64/release/_test.bench.so_5.thread_pool_disp.exe -c 2 -a 4 -m 10000000 -d 200 -S 1 -i

coops: 2, agents in coop: 4, msg per agent: 10000000 (at start: 1), total msgs: 80000016

dispatcher: thread_pool

*** demands_at_once: 200
*** threads in pool: default (4)
*** FIFO: individual
creating cooperations: 0s
messages: 80000016, total_time: 7.368s
price: 9.209998158e-008s
throughtput: 10857765.47 messages/s
shutdown: 0s

Так вот, я запускаю тесты на 4-х ядерном Core i7 2.4GHz, поэтому в thread_pool-е четыре рабочих нити и по четыре агента в кооперации.

А в том тесте Akka 2.0, где была достигнута пропускная способность в 50M msg/sec использовалась машина с 48-ью ядрами:

  • Processor: 48 core AMD Opteron (4 dual-socket with 6 core AMD® Opteron™ 6172 2.1 GHz Processors)
  • Memory: 128 GB ECC DDR3 1333 MHz memory, 16 DIMMs
  • OS: Ubuntu 11.10
  • JVM: OpenJDK 7, version “1.7.0_147-icedtea”, (IcedTea7 2.0) (7~b147-2.0-0ubuntu0.11.10.1)
  • JVM settings: -server -XX:+UseNUMA -XX:+UseCondCardMark -XX:-UseBiasedLocking -Xms1024M -Xmx2048M -Xss1M -XX:MaxPermSize=128m -XX:+UseParallelGC
  • Akka version: 2.0
  • Dispatcher configuration other than default
    • parallelism 48 of fork-join-exector
    • throughput as described

Фиг знает, как бы повел себя SObjectizer на такой технике, никогда не имел ничего подобного в своем распоряжении, и насколько бы смаштабировались результаты. Но, подозреваю, получилось бы не хуже, чем у Akka :)

Важное примечание. Я сам микробенчмарк для Akka не запускал, цифры взяты из упомянутого поста. И я не знаю, 50M msg/sec там -- это именно 50M сообщений (т.е. каждый запрос и каждый ответ считается отдельно). Или же 50M -- это запросов в секунду, т.е. запрос+ответ считается однократно, а потому мелких сообщений должно быть в два раза больше. Если речь идет про запросы в секунду, то показатели теста SObjectizer-а нужно делить пополам. Но даже в этом случае при масштабировании с 4-х до 48-ми ядер должно получиться порядка 50M req/sec.

PS. В готовящейся к релизу версии 5.5.3 сделаны некоторые нововведения, позволяющие разработчику настраивать производительность обработки сообщений агентами для разных сценариев. И работы по вылизыванию производительности ядра SObjectizer будут продолжаться в следующих версиях.

Комментариев нет: