вторник, 26 февраля 2013 г.

Haskell - энергичный лентяй

Какая то ленивость у Haskell не правильная, слишком энергичная. :-)
Да, ленивость - это дело не простое, тут уметь надо.

Возьмем для примера функцию foldl. Ну какой лентяй будет как сумасшедший создавать столько чанков, что стек переполняется. На это способна только бешеная лисица, которой сотня километров - не крюк, на и Haskell.

Вы можете возразить, что вот ленивые списки - это окно в бесконечность. Да любая комбинация из функций map свалила бы Haskell в эту бесконечность, если бы не тщательно прописанные правила преобразования различных комбинаций функций. Да и вместо длинных списков предпочтительней использовать Stream Fusion.

Ну что это за ленивый язык, которому надо говорить, как правильно лениться?

Возьмем ленивые строки. Ну какие они ленивые!

Разве ленивая строка при добавлении данных в конец будет как бешеная лисица пробегать по всем чанкам? Нет, она сохранит данные в некотором буфере и добавит лишь когда эти данные понадобятся.

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

Что тут можно сказать? Лень - это не просто. Правильно лениться - уметь надо!

среда, 6 февраля 2013 г.

RedisShardnig и GHC threaded

В последней версии RedisShardnig (http://github.com/kni/redis-sharding-hs-strict) буферизация данных и отправка их в сокеты происходит в отдельных user-lavel threads (forkIO), которым данные передаются посредством MVar переменных.

Разумеется, использование MVar имеет накладные расходы, но какие? Может, не смотря на некоторое усложнение кода, выгодней буфера таскать с собой?
Набросал простенький тестик, и кроме MVar добавил также RefIO. Тест показал, что и MVar и RefIO очень быстры и их можно использовать без опаски замедлить скорость выполнения программ.

Но тут я заметил, что компилировал без включения threaded ражима. А этот режим нужен, чтобы задействовать kqueue и epoll.
Скомпилировал я тесты в threaded режиме, запустил и увидел, что следует минимизировать их использование.

Но каждый тест является синтетическими, поэтому я решил вязать реальное приложение (RedisShardnig) и сделать версию с минимальным использованием MVar. В этой версии для буферизации не будут использоваться отдельные user-lavel threaded, с которыми общение происходит посредством MVar, а все буферизация будет происходить в рабочих threads и буыера будет таскаться с собой. Конечно множество MVar все равно останется, ведь при каждой операции записи чтения данных из сокетов используются MVar.

Текущая версия RedisShardnig (в таблице обозначена как mvar) и новая версия (в таблице обозначена как self) были собраны без и с поддержкой threaded режима и протестированы на производительность при помощи redis-benchmark (в 10 потоков). За RedisShardnig находилось 4 Redis node. Процессор имел 8 ядер. В таблице приведены тысячи SET запросов в секунду.

                 -P 1  -P 100
mvar             19      83
mvar threaded    10      93
self             23     116
self threaded    13     107

mvar -N2         21     166
self -N2         26     208

mvar -N4         23     232
self -N4         25     263


-P 100 - обозначает режим Pipeline с 100 команд в одном пакете (смотри redis-benchmark). То есть в этом режиме в 100 раз снижено количество отправки и получения данных из сокета.

Из таблицы видно, что для приложений с интенсивной сетевой нагрузкой включение threaded режима снижает производительность в 2 раза.
Впрочем и для приложений с другим характером нагрузки также заметно снижение производительность при использование threaded режима.
А не использовать threaded режим нельзя, так как для сетевых приложений необходим kqueue и epoll.
Кстати, разработчики GHC игнорируют это упущение.