пятница, 9 декабря 2016 г.

Standard ML, Haskell parser benchmark

https://github.com/kni/sparcl

Redis protocol benchmark (seconds):
          String  Substring  Reader-String  Reader-Substring   Attoparsec
Haskell:          4.63                                         7.98
Mlton:    3.07    1.81       1.90           1.50
PolyML:   6.32    4.43       7.86           6.08


CSV benchmark (seconds):
          String  Substring  Reader-String  Reader-Substring   Attoparsec
Haskell:          7.85                                         14.41
Mlton:    6.91    5.61        3.51          3.10
PolyML:   9.31    9.29       10.56          9.14

P.S. Haskell ByteString - is Substring

But Substring has no apparent advantages over String in a real application (https://github.com/kni/redis-sharding-sml) :-)

четверг, 1 декабря 2016 г.

От Haskell к Standard ML

Переписал сервер с Haskell на Standard ML (MLton и Poly/ML).

Кода стало больше, скорость стала выше. Быстрей не только MLton версия, но и Poly/ML. И это, несмотря на то, что у PolyML медленный FFI.

Кода в Standard ML версии больше чем в Haskell потому, что в Haskell используются "зеленые треды" и менеджер IO, а в Standard ML - "голый" kevent (epoll) и функции обратного вызова. Да и синтаксис компактней в Haskell, но в Standard ML "модульней".

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

Если коротко, то в сравнении с Standard ML Haskell выигрывает в малом, но проигрывает в большом.

P.S.
Да, еще в GHC MIO есть баг, который лимитирует использование Haskell версии.

понедельник, 28 ноября 2016 г.

Haskell: ошибка в Mio...

Кажется, в Mio ("Scalable IO Manager") есть ошибка.
Mio - это IO менеджер для threaded режима в GHC 7.8 и выше.
В сервере, при превышении определенного размера данных, происходит зависание треда.
Mio считает, что писать в сокет еще нельзя, а на самом деле можно.
Проверил на GHC 7.8.[34] и 8.0.1.
В не-threaded режиме зависания нет, все работает.

А ведь считал, что основное преимущество Haskell - это "зеленые нитки" с многопроцессорностью при высокой скорости. А тут, оказывается...

вторник, 1 ноября 2016 г.

Whole program optimization

Не хватает в GHC whole program optimization.
Есть "data type" и две функции. Одна возвращает по входным данным этот "data type", другая его используется для разветвления логики.
Перенес определение data type и первую функции в другой файл - производительность сервера упала на 20%.

вторник, 13 сентября 2016 г.

Hans Boehm's GC benchmark - не только для GC

Hans Boehm написал бенчмарк для GC (работа с Tree).
Это бенчмарк есть и в "NoFib: Haskell Benchmark Suite"
https://github.com/jyp/nofib/blob/master/gc/gc_bench/gc_bench.hs

Немного модифицировал его, чтобы была возможность задавать kStretchTreeDepth.
А также сделал порт на Standard ML (один в один).

Запускаем его с большим размером кучи, чтобы GC не срабатывал, то есть используем как Tree бенчмарк.

GHC version 7.8.3 (ghc -O2 -rtsopts ...)
> time ./gc_bench_haskell 24 16 500000 16 22 +RTS -H250M
13.961u 0.220s 0:14.47 97.9% 947+248k 1+0io 27pf+0w

Poly/ML 5.6
> time ./gc_bench_polyml -H250 24 16 500000 16 22
5.388u 0.389s 0:05.58 103.2% 551+200k 0+0io 0pf+0w

MLton 20130715
> time /net/bsd/gc_bench_mlton 24 16 500000 16 22
2.593u 0.000s 0:02.61 99.2% 212+181k 0+0io 3pf+0w

multiMLton
> time ./gc_bench_multiMLton 24 16 500000 16 22
2.855u 0.000s 0:02.89 98.6% 273+197k 0+0io 0pf+0w


Выходит, что Haskell быстр при работе с числами, векторами, строками, а работа со сложными структурами данных не его конек?

P.S.
На http://benchmarksgame.alioth.debian.org в binary-trees бенчмарке Haskell обгоняет OCaml. Но там Haskell работает параллельно на 4 ядрах, а OCaml на одном.

Lazy ByteString или Haskell - кому сказать прощай?

Наверное вы уже знаете, что у меня есть 3 версии RedisSharding: Perl версия, Haskell версия с использованием Lazy ByteString и Strict Haskell версия с использованием attoparsec.

В старой заметке Redis Sharding: Lazy vs Strict (http://by-need.blogspot.com/2013/01/rs.html) утверждается, что Lazy и Strict версии примерно одинаковы по производительности.

Но, так как RedisSharding используется в основном для больших MGET команд, сравнение Lazy и Strict версии в pipeline режиме не производилось. А вот сейчас как-то случайно...

Оказалось, что Lazy версия в pipeline режиме имеет очень маленькую производительность.

cpuset -l 0 ./redis_sharding --port=8090 --nodes=127.0.0.1:8081,127.0.0.1:8082,127.0.0.1:8083,127.0.0.1:8084 +RTS -s -N1 -A10M -qa


Lasy версия.

> redis-benchmark -p 8090 -n 10000 -c 10 -q -t set,get,mset -P 1
SET: 20000.00 requests per second
GET: 19801.98 requests per second
MSET (10 keys): 9891.20 requests per second

> redis-benchmark -p 8090 -n 10000 -c 10 -q -t set,get,mset -P 2
SET: 195.46 requests per second
GET: 196.31 requests per second
MSET (10 keys): 196.29 requests per second

Strict версия.

> redis-benchmark -p 8090 -n 10000 -c 10 -q -t set,get,mset -P 1
SET: 16103.06 requests per second
GET: 15898.25 requests per second
MSET (10 keys): 9337.07 requests per second

> redis-benchmark -p 8090 -n 10000 -c 10 -q -t set,get,mset -P 2
SET: 25906.74 requests per second
GET: 25839.79 requests per second
MSET (10 keys): 12285.01 requests per second


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

Lasy версия
COST CENTRE                           MODULE                               %time %alloc
throwSocketErrorIfMinus1RetryMayBlock Network.Socket.Internal               29.8    0.7
MAIN                                  MAIN                                  11.9    0.8
server_responses                      RedisSharding                         11.2    0.9
client_reader                         RedisSharding                         10.1    5.6
send                                  Network.Socket.ByteString.Lazy.Posix   9.5    8.1
...
key2server                            RedisSharding                          2.3    0.4

Strict версия
COST CENTRE                           MODULE                              %time %alloc
endOfLine                             Data.Attoparsec.ByteString.Internal  13.0    7.5
decimal                               Data.Attoparsec.ByteString.Char8     11.0    8.2
servers_sender                        RedisSharding                        10.6   13.1
throwSocketErrorIfMinus1RetryMayBlock Network.Socket.Internal              10.4    0.5
client_reader                         RedisSharding                         9.8    5.5
...
key2server                            RedisSharding                         1.7    1.2

Но где тут 20 кратное замедление? Возьмем для ориентира чистую функцию key2server, которотая под ключу определяет на какою ноду отправлять данные. В обоих версиях время выполнения ее в процентной отношении соизмеримо. Значит профайлер не показывает обнаруженное 20 кратное замедление. О чем это может говорить? Неужели, причина кроется где то глубоко в Haskell RTS? Что, выходит, Харпер прав?

P.S. Для не threaded режима работы RTS Haskell, цифры аналогичны.

P.S.S. Нужно для объективности еще провести исследования.