Простейший HTTP сервер на Golang и Elixir. Сравнение производительности

    image
    Пару недель назад, я решил взять простейший пример HTTP сервера на Go и измерить его производительность. Потом я смело взял Phoenix, прогнал на тех же тестах, и расстроился. Результаты были не в пользу Elixir/Erlang (45133 RPS у Go и всего 3065 RPS у Phoenix). Но Phoenix — это тяжело. Надо что-то хотя бы примерно равное по простоте и логике разработки тому, что есть на Go: когда есть путь — "/" и handler для него. Логичной аналогией мне показалось решение cowboy + plug, где у нас есть Router, который так же ловит "/" и отвечает на него. Результаты убили — Elixir/Erlang опять оказался медленнее:


    Golang
    sea@sea:~/go$ wrk -t10 -c100 -d10s http://127.0.0.1:4000/
    ...
      452793 requests in 10.03s, 58.30MB read
    Requests/sec:  45133.28
    Transfer/sec:      5.81MB

    elixir cowboy plug
    sea@sea:~/http_test$ wrk -t10 -c100 -d10s http://127.0.0.1:4000/
    ...
      184703 requests in 10.02s, 28.57MB read
    Requests/sec:  18441.79
    Transfer/sec:      2.85MB

    Как жить дальше? Две недели я не спал и не ел (почти). Все, во что я верил все эти годы: совершенство vm erlang, ФП, зеленые процессы, было растоптано разорвано, сожжено и пущено по ветру. Немного отойдя от шока, успокоившись, и подтерев сопли я решил разобаться, в чем дело.


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

    И сервер и тестовая программа были запущены на одной виртуальной машине, внутри VirtualBOX, с выделенными двумя ядрами.


    Однако,

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

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


    Тестовый компьютер


    Как я тестировал. Я работаю на ноутбуке с Windows 7 x64, процессор i7, 8 Gb RAM, а Linux — в моем случае Ubuntu 16, я запускаю внутри VirtualBOX. Для нее я выделил 1 Gb RAM и 2 ядра. Внутри этой виртуальной машины я запустил HTTP сервер, и на этой же машине запускал тестирование ab и wrk. При таком раскладе, одна и та же машина получается нагружена и сервером и тестом; передача данных по сети не накладывает ограничений, потому что передачи по сети нет.


    В итоге мы получили полный разгром:


    go vs cowboy wrk -t10 -c100 -d10s http://127.0.0.1:4000/
    Go:
    sea@sea:~/go$ wrk -t10 -c100 -d10s http://127.0.0.1:4000/
    Running 10s test @ http://127.0.0.1:4000/
      10 threads and 100 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    65.18ms  109.44ms   1.05s    86.48%
        Req/Sec     4.60k     5.87k   25.40k    86.85%
      452793 requests in 10.03s, 58.30MB read
    Requests/sec:  45133.28
    Transfer/sec:      5.81MB
    
    Elixir cowboy:
    sea@sea:~/http_test$ wrk -t10 -c100 -d10s http://127.0.0.1:4000/
    Running 10s test @ http://127.0.0.1:4000/
      10 threads and 100 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency     8.94ms   11.38ms 123.57ms   86.53%
        Req/Sec     1.85k   669.61     4.99k    71.70%
      184703 requests in 10.02s, 28.57MB read
    Requests/sec:  18441.79
    Transfer/sec:      2.85MB
    go vs cowboy wrk -t10 -c1000 -d10s http://127.0.0.1:4000/
    Go:
    sea@sea:~/go$ wrk -t10 -c1000 -d10s http://127.0.0.1:4000/
    Running 10s test @ http://127.0.0.1:4000/
      10 threads and 1000 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    61.16ms  231.88ms   2.00s    92.97%
        Req/Sec     7.85k     8.65k   26.13k    79.49%
      474853 requests in 10.09s, 61.14MB read
      Socket errors: connect 0, read 0, write 0, timeout 1329
    Requests/sec:  47079.39
    Transfer/sec:      6.06MB
    
    Elixir cowboy:
    sea@sea:~/http_test$ wrk -t10 -c1000 -d10s http://127.0.0.1:4000/
    Running 10s test @ http://127.0.0.1:4000/
      10 threads and 1000 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency   123.00ms  303.25ms   1.94s    88.91%
        Req/Sec     2.06k     1.85k   11.26k    71.80%
      173220 requests in 10.09s, 26.79MB read
      Socket errors: connect 0, read 0, write 0, timeout 43
    Requests/sec:  17166.03
    Transfer/sec:      2.65MB

    Единственное, что можно было сказать в защиту Erlang/Elixir — это меньшее количество timeout'ов. Сборка приложение в HiPE не улучшило показателей. Но обо всем по порядку


    Тестовое окружение v.2


    Стало ясно, что тестировать лучше на отдельно стоящей системе, а не в виртуальной машине. Тестируемый http сервер было бы лучше разместить на более слабой машине, а тестирующая машина должна быть более мощной, чтобы она могла по полной завалить сервер, доведя его до предела технических возможностей. Желательно иметь быструю сеть, чтобы не упереться в ее ограничения.


    Поэтому, в качестве тестирующей машины я решил оставить свой ноутбук на i7. А в качестве сервера решил помучать Orange PI One. Я предположил, что я скорее "упрусь" в ее производительность, чем в ограничение скорости обмена по сети. Orange PI One подключена к роутеру по UTP со скоростью 100 Мбит/с.


    image


    На сайте производителя указано, что на Orange PI One установлен процессор A7 Quad Core с частотой 1200 МГц. Но из за ошибок разработчиков, вся система страдает от алертов ядра по перегреву, поэтому, я зажал скорость работы процессора до 600 МГц. Так будет еще интереснее. Система работает стабильно, но даже с ничего не делая, ее load average: 2.00, 2.01, 2.05 (что странно). Установлена Ubuntu 14. Памяти 512Мб, поэтому, на всякий случай я подключил swap раздел в файл на флешке.


    image<h4>Раунд 2</h4>


    Для того, чтобы перебросить проект на Go и на Elixir на Orange PI, я сразу создал два проекта на Github:


    https://github.com/UA3MQJ/go-small-http
    https://github.com/UA3MQJ/elx-small-http-cowboy


    Golang на Orange PI поставился без проблем. А вот с Erlang/Elixir пришлось немного поработать. Но эта работа была проведена уже давно. Сборка проектов и запуск прошли без проблем. В качестве тестов я взял инструмент, который будет работать под Windows — это Jmeter.


    Первые же тесты при следующих параметрах:


    image


    Показали, что силы… равны!


    RPS — Go:


    image


    RPS — Elixir:


    image


    Resp Time — Go:


    image


    Resp Time — Elixir:


    image


    Интересные наблюдения


    Go всегда работало с одним ядром:


    image


    В то время, как Elixir со всеми сразу:


    image


    В этом сферическом тесте получилось так, что Elixir выиграл.


    go vs cowboy wrk -t10 -c100 -d10s http://192.168.1.16:4000/
    Go:
    sea@sea:~$ wrk -t10 -c100 -d10s http://192.168.1.16:4000/
    Running 10s test @ http://192.168.1.16:4000/
      10 threads and 100 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    19.04ms    7.70ms  81.05ms   70.53%
        Req/Sec   531.09     78.11   828.00     77.10%
      52940 requests in 10.02s, 6.82MB read
    Requests/sec:   5282.81
    Transfer/sec:    696.46KB
    
    Elixir cowboy:
    sea@sea:~$ wrk -t10 -c100 -d10s http://192.168.1.16:4000/
    Running 10s test @ http://192.168.1.16:4000/
      10 threads and 100 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    14.27ms   10.54ms 153.60ms   95.81%
        Req/Sec   753.20    103.47     1.09k    80.40%
      74574 requests in 10.04s, 11.53MB read
    Requests/sec:   7429.95
    Transfer/sec:      1.15MB
    

    go vs cowboy wrk -t100 -c100 -d10s http://192.168.1.16:4000/
    Go:
    sea@sea:~$ wrk -t100 -c100 -d10s http://192.168.1.16:4000/
    Running 10s test @ http://192.168.1.16:4000/
      100 threads and 100 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    60.14ms  137.57ms   1.52s    94.28%
        Req/Sec    38.45     20.62   130.00     60.30%
      34384 requests in 10.10s, 4.43MB read
    Requests/sec:   3404.19
    Transfer/sec:    448.79KB
    
    Elixir cowboy:
    sea@sea:~$ wrk -t100 -c100 -d10s http://192.168.1.16:4000/
    Running 10s test @ http://192.168.1.16:4000/
      100 threads and 100 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    13.32ms    5.25ms  90.37ms   73.31%
        Req/Sec    75.51     22.04   191.00     67.49%
      75878 requests in 10.10s, 11.74MB read
    Requests/sec:   7512.75
    Transfer/sec:      1.16MB
    

    go vs cowboy wrk -t100 -c500 -d10s http://192.168.1.16:4000/
    Go:
    sea@sea:~$ wrk -t100 -c500 -d10s http://192.168.1.16:4000/
    Running 10s test @ http://192.168.1.16:4000/
      100 threads and 500 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    93.81ms   18.63ms 328.78ms   84.98%
        Req/Sec    53.13     11.12   101.00     77.60%
      52819 requests in 10.10s, 6.80MB read
    Requests/sec:   5232.01
    Transfer/sec:    689.77KB
    
    Elixir cowboy:
    sea@sea:~$ wrk -t100 -c500 -d10s http://192.168.1.16:4000/
    Running 10s test @ http://192.168.1.16:4000/
      100 threads and 500 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    93.24ms   96.80ms   1.26s    94.47%
        Req/Sec    62.95     23.33   292.00     79.87%
      61646 requests in 10.10s, 9.53MB read
    Requests/sec:   6106.38
    Transfer/sec:      0.94MB
    

    А что, если запустить ерланговый сервер с использованием HiPE?


    Для этого сначала нужно нагуглить, как это сделать. Как это сделать в Erlang — ясное дело. Но про Elixir пришлось "погуглить". К тому же, опробовавшие HiPE пишут, что часто в HiPE получается даже медленее, чем в стандартном. Это связано и с тем, что зависимости могут быть собраны без HiPE (а надо и их собирать в том же режиме), плюс нужно оценивать системный счетчик переключений контекста, и если переключений будет много, то это отрицательно скажется на производительности и покажет худшие результаты.


    Соберем зависимости проекта с компилятором HiPE


    $ ERL_COMPILER_OPTIONS="[native,{hipe, [verbose, o3]}]" mix deps.compile --force

    Соберем проект


    $ ERL_COMPILER_OPTIONS="[native,{hipe, [verbose, o3]}]" mix compile

    Тесты показали, что HiPE не дает прироста, а наоборот, показывает худшие результаты.


    cowboy vs cowboy(HiPE) wrk -t100 -c500 -d10s http://192.168.1.16:4000/
    Elixir cowboy:
    sea@sea:~$ wrk -t100 -c500 -d10s http://192.168.1.16:4000/
    Running 10s test @ http://192.168.1.16:4000/
      100 threads and 500 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    93.24ms   96.80ms   1.26s    94.47%
        Req/Sec    62.95     23.33   292.00     79.87%
      61646 requests in 10.10s, 9.53MB read
    Requests/sec:   6106.38
    Transfer/sec:      0.94MB
    
    Elixir cowboy (HiPE):
    sea@sea:~$ wrk -t100 -c500 -d10s http://192.168.1.16:4000/
    Running 10s test @ http://192.168.1.16:4000/
      100 threads and 500 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency   111.84ms  160.53ms   1.89s    95.42%
        Req/Sec    59.19     29.68   383.00     81.63%
      56425 requests in 10.10s, 8.72MB read
      Socket errors: connect 0, read 0, write 0, timeout 34
    Requests/sec:   5587.39
    Transfer/sec:      0.86MB

    Go наносит ответный удар


    Почему же Go работал в один процессор? Может быть, пакет с Go, что идет по умолчанию старой версии, когда еще Go работал на один процессор? Так и есть!


    sea@OrangePI:~$ go version
    go version go1.2.1 linux/arm

    Придется обновить и повторить! Собранную Go версии 1.7.3 для armv7 удалось найти по адресу https://github.com/hypriot/golang-armbuilds/releases


    Сервер на Golang, собранный на этой версии, загружает уже все 4 ядра:


    image


    cowboy vs Go1.7.4 wrk -t100 -c500 -d10s http://192.168.1.16:4000/
    Elixir cowboy:
    sea@sea:~$ wrk -t100 -c500 -d10s http://192.168.1.16:4000/
    Running 10s test @ http://192.168.1.16:4000/
      100 threads and 500 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    93.24ms   96.80ms   1.26s    94.47%
        Req/Sec    62.95     23.33   292.00     79.87%
      61646 requests in 10.10s, 9.53MB read
    Requests/sec:   6106.38
    Transfer/sec:      0.94MB
    
    sea@sea:~/tender_pro_bots$ wrk -t100 -c500 -d10s http://192.168.1.16:4000/
    Running 10s test @ http://192.168.1.16:4000/
      100 threads and 500 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    70.17ms   57.84ms 754.39ms   90.61%
        Req/Sec    84.09     31.01   151.00     73.20%
      78787 requests in 10.10s, 10.14MB read
    Requests/sec:   7800.43
    Transfer/sec:      1.00MB
    

    Go вырывается вперед!


    Go и fasthttp


    После рекомендации сменить версию Golang, мне советовали fasthttp и gccgo. Начнем с первого.


    https://github.com/UA3MQJ/go-small-fasthttp


    Поглядим загрузку. Видим, что загружены все 4 ядра, но не на 100%.


    image


    А теперь wrk


    go vs cowboy wrk -t10 -c100 -d10s http://192.168.1.16:4000/
    Go fasthttp:
    sea@sea:~/tender_pro_bots$ wrk -t10 -c100 -d10s http://192.168.1.16:4000/
    Running 10s test @ http://192.168.1.16:4000/
      10 threads and 100 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    43.56ms   85.95ms 738.51ms   89.71%
        Req/Sec   676.18    351.12     1.17k    70.80%
      67045 requests in 10.04s, 9.78MB read
    Requests/sec:   6678.71
    Transfer/sec:      0.97MB
    
    Elixir cowboy:
    sea@sea:~$ wrk -t10 -c100 -d10s http://192.168.1.16:4000/
    Running 10s test @ http://192.168.1.16:4000/
      10 threads and 100 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    14.27ms   10.54ms 153.60ms   95.81%
        Req/Sec   753.20    103.47     1.09k    80.40%
      74574 requests in 10.04s, 11.53MB read
    Requests/sec:   7429.95
    Transfer/sec:      1.15MB
    

    go vs cowboy wrk -t100 -c100 -d10s http://192.168.1.16:4000/
    Go fasthttp:
    sea@sea:~/tender_pro_bots$ wrk -t100 -c100 -d10s http://192.168.1.16:4000/
    Running 10s test @ http://192.168.1.16:4000/
      100 threads and 100 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency     8.95ms    3.08ms  42.23ms   75.69%
        Req/Sec   112.61     16.65   320.00     70.18%
      112561 requests in 10.10s, 16.42MB read
    Requests/sec:  11144.39
    Transfer/sec:      1.63MB
    
    Elixir cowboy:
    sea@sea:~$ wrk -t100 -c100 -d10s http://192.168.1.16:4000/
    Running 10s test @ http://192.168.1.16:4000/
      100 threads and 100 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    13.32ms    5.25ms  90.37ms   73.31%
        Req/Sec    75.51     22.04   191.00     67.49%
      75878 requests in 10.10s, 11.74MB read
    Requests/sec:   7512.75
    Transfer/sec:      1.16MB
    

    go vs cowboy wrk -t100 -c500 -d10s http://192.168.1.16:4000/
    Go fasthttp:
    sea@sea:~/tender_pro_bots$ wrk -t100 -c500 -d10s http://192.168.1.16:4000/
    Running 10s test @ http://192.168.1.16:4000/
      100 threads and 500 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    46.44ms   10.69ms 327.50ms   93.21%
        Req/Sec   107.71     15.10   170.00     82.06%
      107349 requests in 10.10s, 15.66MB read
    Requests/sec:  10627.97
    Transfer/sec:      1.55MB
    
    Elixir cowboy:
    sea@sea:~$ wrk -t100 -c500 -d10s http://192.168.1.16:4000/
    Running 10s test @ http://192.168.1.16:4000/
      100 threads and 500 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    93.24ms   96.80ms   1.26s    94.47%
        Req/Sec    62.95     23.33   292.00     79.87%
      61646 requests in 10.10s, 9.53MB read
    Requests/sec:   6106.38
    Transfer/sec:      0.94MB
    

    Golang вместе с fasthttp оказался быстрее самого себя и быстрее Elixir cowboy.


    О правильности измерений


    Если быть более внимательным, то можно выяснить, что cowboy и Go — отвечают разным количеством байт. Это связано с разными HTTP Headers'ами, которые они выдают.


    Выдача go:


    HTTP/1.1 200 OK
    Date: Thu, 30 Mar 2017 14:37:08 GMT
    Content-Length: 18
    Content-Type: text/plain; charset=utf-8

    Выдача cowboy:


    HTTP/1.1 200 OK
    server: Cowboy
    date: Thu, 30 Mar 2017 14:38:17 GMT
    content-length: 18
    cache-control: max-age=0, private, must-revalidate

    Как видим, cowboy выдает еще и дополнительную строку "server: Cowboy", что обязательно как-то сказывается на количестве переданных байт в случае с cowboy. Переданных данных получается больше.


    UPDADE. Забыл включить в BEAM опцию для работы с epoll


    Спасибо nwalker за то, что напомнил про ключ +K. Укажем его при запуске:


    sea@OrangePI:~/http_tests/elx-small-http-cowboy$ iex --erl '+K true' -S mix
    Erlang/OTP 19 [erts-8.1] [source] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:true]

    В этот раз буду грузить не 10 секунд, а минуту.


    cowboy vs cowboy epoll wrk -t100 -c500 -d60s http://192.168.1.16:4000/
    Elixir cowboy:
    sea@sea:~/tender_pro_bots$ wrk -t100 -c500 -d60s http://192.168.1.16:4000/
    Running 1m test @ http://192.168.1.16:4000/
      100 threads and 500 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    78.63ms   38.59ms   1.00s    90.17%
        Req/Sec    65.08     14.19   158.00     54.94%
      389764 requests in 1.00m, 60.28MB read
    Requests/sec:   6485.27
    Transfer/sec:      1.00MB
    
    Elixir cowboy with epoll:
    sea@sea:~/tender_pro_bots$ wrk -t100 -c500 -d60s http://192.168.1.16:4000/ Running 1m test @ http://192.168.1.16:4000/
      100 threads and 500 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    90.25ms   78.45ms   1.96s    95.36%
        Req/Sec    59.91     19.71   370.00     64.78%
      356572 requests in 1.00m, 55.15MB read
      Socket errors: connect 0, read 0, write 0, timeout 21
    Requests/sec:   5932.94
    Transfer/sec:      0.92MB

    Но на OrangePI при включенном epoll, результат оказался хуже.


    Выводы


    А выводы каждый для себя сделает свои. Go'шники порадуются за Go, а Эрлангисты и Эликсирщики — за свой продукт. Каждый останется при своем. Приверженец Erlang увидел скорость Go, которая оказалась выше не на порядок, и даже не в 2 раза (но чуть меньше), при этом он не откажется от всех возможностей Erlang даже ради 10 кратного прироста. В то же время Go'шник врядли заинтересуется Erlang, видя меньшую скорость и слышав про все возможные сложности при изучении функционального программирования.


    В современном мире, время программиста стоит дорого, иногда даже больше, чем стоимость оборудования. Требуется тестирование не только в "сферических" RPS на "сферической" задаче, но и время разработки, сложность доработки и сопровождения. Экономическая целесообразность. Но иногда так хочется втопить на все лошадинные силы мегагерцы и устроить несколько заездов в отличной компании! Отлично покатались.


    image

    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 42
    • +2
      deleted
      @читай невнимательно/сразу пиши
      • +10

        А какой смысл гонять бенчмарки на привет-мирах? Этак какой-нибудь nginx вообще всех уделает. И что? А ничего, сколь-нибудь серьёзного приложения на nginx не напишешь, да и не для того он предназначен.

        • +1
          Допросы среди программистов показывают, что бенчмарки вообще не имеют смысла. Только ради того, чтобы очень грубо оценить «едет/не едет».
          • +1
            Тогда ваша статья показывает нам только, что Elixir и Go «едут», что мы как бы уже и так знаем (ಠ_ಠ).
            • 0
              И едут почти одинаково. И статьи бы не было, если б сначала не было разницы в 2 раза.
              • +2
                Вы с Go 1.8 сравните, где оптимизация под ARM на много лучше, да и компилятор то в общем-то на много лучше и умнее. Получите прыжок по производительности раза в 2, а то и выше.
          • +3
            > Этак какой-нибудь nginx вообще всех уделает.

            читал что хаскеловский warp в подобных тестах был быстрее nginx
            • +2
              > сколь-нибудь серьёзного приложения на nginx не напишешь

              Ради справедливости и оффтопика — там можно на lua код писать, причем достаточно приличную логику.
              • +1

                У нас трекинг на ngx-lua пишет в Редис. Работает действительно быстро.

                • +1

                  Вы считаете это "серьёзным приложением"?

                  • 0

                    Может и не "серьезное", но оно выполняет реальную полезную работу.

                    • 0

                      Я в этом и не сомневался.

              • +8
                Кстати, полезный лайфхак: для запуска серверов, написанных на Go, на ARM машинах вроде Raspberry Pi устанавливать Go вообще не обязательно, можно делать кросс-компиляцию на рабочей машине и выкладывать уже готовый исполняемый файл.

                env GOARCH=arm64 go build
                
                • +4
                  возможные сложности при изучении функционального программирования

                  Я думаю этот тезис вводит людей в заблуждение. Код написанный с сильным упором в FP оказывается:


                  1) Копмактнее — вы переиспользуете поведении за счет передачи функции как аргумента или результат.
                  2) Проще для понимания — имутабельные данные не требуют думать о проблемах изменения их стотояния. Вы не думаете о сайд эфектах когда пишите чистые функции.
                  3) Лучше тестируемый — чистые функции легко проверить, они зависять только от входных параметров, т.е. не нужно гемороиться с моками.
                  4) Concurrency проще — за счет использования Imutability(вы пытаетесь понять почему данные случайным образом меняются на не валидны(всево лишь случайно/по незнанию написанный код рассихноризирующий конкурентное взнаимодейстиве), чистые функции легко паралелятся и не требуют синхронизации.

                  • 0
                    Это не мой тезис. И меня тоже каждый раз напрягает, когда кто-то его начинает проталкивать! Типа, функциональщина — это зло, это сложно. Лучше мы мутексов понавтыкаем. Да в ФП их даже втыкать не надо.
                    Спасибо за список, согласен с каждым пунктом.
                    • 0

                      То есть вы утверждаете, что такого рода шарада (сравните там её с оригинальным вариантом на оорп) компактнее, проще для понимания, тестируемей и конкурентней?

                      • +1

                        А какой там оригинальный вариант, ваш с магическими декораторами? Нет, знаете, этому варианту лучше отказать. Магические декораторы не нужны. Это примерно как написать на scala с имплиситами, выглядит неплохо, а как работает — непонятно.


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

                        • 0

                          Они не более магические, чем createSelector. $mol_mem — обычная функция, которая реактивно кеширует результат выполнения метода. Ни чем не хуже функции createSelector, которая творит под капотом реальную магию. Ну и танцы с типами для $mol_mem, кстати, не потребовались, ибо она подхватывает сигнатуру метода и не меняет интерфейс.

                        • 0

                          Не понял в чем суть приведенного кода как аргумента.
                          ООП не мешает ФП и можно встретить языки как с ФП+ООП так и в других комбинации. У вас похоже понятия о разных концепциях имеют отличное от, если так можно, выразится академического взгляда.
                          ФП это о функциях и их композициях — т.е. о поведении.
                          ООП это о организации — о изоляции и спецификации контрактов взаимодействия между объектами.
                          К тому же я вам дам одно важное пояснение если код вам не понятен то есть не одна а три причины:


                          1. Вы не знакомы с концепциями которые выражает этот код.
                          2. Кто-то просто плохо написал код. к
                          3. Концепция которая реализована в ЯП либо "плоха" сама по себе для данной задачи либо её реализация в языке.

                          По этому предлагаю вам:


                          1. Подумать какие из вышеописанных причин и в каком объеме влияют на код который вы видите,
                          2. Второе — поискать примеры в которых ФП дает бонусы.
                          • +2
                            ФП это о функциях

                            Не простых функциях, а чистых.


                            ООП это о организации — о изоляции

                            Не путайте инкапсуляцию, абстракцию и изоляцию.


                            Подумать какие из вышеописанных причин и в каком объеме влияют на код который вы видите,

                            Иммутабельность вынуждает превращать код в шараду вида: пишем чистые функции, а состояние меняется само, только тут надо прочитать заклинание createSelector, там мантру "создаём новое состояние мира копированием старого, но с новым расположением одного камушка". А ведь всё, что на самом деле надо — идемпотентность. И для её достижения вовсе не нужно насиловать мозг ни себе, ни окружающим, превращая код в хитросплетённую лапшу из однострочных функций.


                            Второе — поискать примеры в которых ФП дает бонусы.

                            Типа "придумайте сами за меня мои аргументы"? :`-D

                        • 0
                          из-за 1 пункта мы получаем callback-hell?
                          • +2

                            Привет, передача поведения это не равносильно callback-hell. То что вы называете callback-hell, вытекает из функций с сайд эффектом. Потому что вы передаете что-то что нужно выполнить потом и это что-то будет влиять на "произвольную" часть программы, т.е. будет выполняться некоторые: IO метод или модификацию данных в вашей программе(т.е данные у вас в программе мутабельны) читая функция может только возвратить аргумент и это для вас не будет напрягающим фактором. Как пример сортировка(пишу на псевдокоде):


                            Array[T] sort(array: Array[T], comparator: (T, T) => Int) // T что-то мы будем упорядочивать


                            • Array[T] — Возвращаемый тип.
                            • array: Array[T] — Сортируемый массив.
                            • comparator: (T, T) => Int — Сравнивает два числа и определяет их порядок. Чистая функция.

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

                            • 0

                              Нет, callback-hell ни как не связан с сайд-эффектами. callback-hell — это увеличение цикломатической сложности линейного алгоритма из-за асинхронных операций. И упомянутые вами Promises решают эту проблему лишь частично и не особо опрятно:


                              function getRecords( config ) {
                                  const cluster = findCluster( config.cluster )
                                  const server = cluster.connectToServer( config.server )
                                  const database = server.findDatabase( config.database )
                              
                                  if( database ) {
                                      const collection = database.useCollection( config.collection )
                                      return collection.queryAll( config.query )
                                  } else {
                                      const database = server.createDatabase( config.database )
                                      database.createCollection( config.collection )
                                      return []
                                  }
                              }

                              function getRecords( config ) {
                                  return findCluster( config.cluster )
                                  .then( cluster => cluster.connectToServer( config.server ) )
                                  .then( server => {
                              
                                      return server.findDatabase( config.database )
                                      .then( database => {
                              
                                          if( database ) {
                                              return database
                                              .then( database => database.useCollection( config.collection ) )
                                              .then( collection => collection.queryAll( config.query ) )
                                          } else {
                                              return server.createDatabase( config.database )
                                              .then( database => database.createCollection( config.collection ) )
                                              .then( ()=> [] )
                                          }
                              
                                      } )
                              
                                  } )
                              }
                              • 0

                                И снова не так. Сallback hell — это прежде всего огромное число вложенных функций, которое приводит к тому что код "уплывает" за правую границу экрана. :-)

                                • 0

                                  Уплывает-то он из-за цикломатической сложности. Да и сейчас у каждого разработчика по паре full-hd экранов — до границы очень далеко.

                        • 0
                          Надо ещё на Assembler добавить бенчмарк, и по результатам сделать такой же вывод — «хочешь быстрее — пиши на асме, го и эликсир не нужны» =)

                          На самом деле важно соответствие скорости/качества(скорость реализации и устойчивость кода к багам)/соответствию задаче, подобные бенчмарки про это не рассказывают, к сожалению.
                        • НЛО прилетело и опубликовало эту надпись здесь
                          • +1
                            Называется хватаем за уши и тянем, тянем пока не получим нужный нам результат.
                            К победе Erlang я притянуть не смог. А за ссылку спасибо!
                          • +4

                            А вы с какими флагами BEAM запускали? '+K true', как водится, не поставили?


                            +K true, enable kernel poll

                            Один из самых важных флагов запуска, который должен быть выставлен в любом production и писькомерном окружении — включает использование epoll/kqueue. Без него всякие изменения не имеют смысла.


                            Возможно, для адекватности сравнения стоит поставить +stbt db. Я точно не помню, как себя ведет планировщик Go, но если он закрепляет треды планировщика на ядрах, то этот флаг нужен, чтобы включить аналогичное поведение в BEAM. Это второй кандидат на звание must have флага.

                            • 0
                              А ведь точно! Про флаг знаю. На erl я бы его не пропустил, а тут, в Elixir'е почему-то был уверен, что он включен. Спасибо за подсказку!

                              sea@OrangePI:~/http_tests/elx-small-http-cowboy$ iex -S mix
                              Erlang/OTP 19 [erts-8.1] [source] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]

                              Interactive Elixir (1.5.0-dev) — press Ctrl+C to exit (type h() ENTER for help)
                              iex(1)>
                              • 0
                                Включение epoll снизило показатели по тестам.
                                • +1

                                  Вот это что-то новенькое. Первый раз такое вижу.

                                  • 0
                                    Я подозреваю, всему виной OrangePI, которая во-первых arm, а во-вторых там какая-то странность с загрузкой 2.0 на простое, при четырех ядрах.
                              • 0
                                Система работает стабильно, но даже с ничего не делая, ее load average: 2.00, 2.01, 2.05.

                                Как же так? Вы точно уверены? А то звучит это как «Система работае стабильно, но даже ничего не делает нагружает два ядра по максимуму».
                                • 0
                                  Стабильно по температуре, по отсутствию ошибок ядра. А почему холостая загрузка составляет 50% — для многих загадка.
                                • +1
                                  1.Как уже сказали бенчмарки простые не показатель хорош ли кто то но показатель если кто то плох.
                                  2. Сравнивать веб сервер на arm процесорах как то не правильно в мире в котором 90+% веб серверов это amd64. разница в том что в golang есть вещи сделаные на ассембли для каждого процесора и в это amd64 лудше представлен в golang. такие вещи как работа со стрингами или математика сделано через ассембли с инструкция для каждого вида процесоров. так что на amd64 golang показывает лудший результат.
                                  3. Скорость исполнения не все в разработки программ. есть много других критериев
                                  • 0
                                    сравнил бы beego с phonex на amd64 вот это пример
                                  • –1

                                    Не знаком с Elixir, но исходя из возможностей Erlang, думаю не учтены важные особенности которые дает Erlang VM, через модель акторов — fault tolerance и transparent distribution. А так же высокий уровень абстракции кода на Elixir.
                                    p.s.
                                    Не знаю где автор вычитал о производительности Erlang, об отличной утилизации мультипроцессорных система это да. Но не о максимальной производительности и низком потреблении памяти.

                                    • +2
                                      статически скомпилированная программа просто обязана быть быстрее виртуальной машины с интерпретатором

                                      Оно как бы да, но это смотря а) как скомпилированна и б) с каким интерпретатором сравнивать...


                                      Вот вам для сравнения на чистом тикле (tcl, threaded, full-async), jit-интерпретатор в quad-code (если что;)
                                      Ответ запроса чуть побольчее в размере, но не критично (ну headers у меня там многовато, лень править да и throughput тут важнее запрос/сек.)...


                                      $ wrk -t10 -c100 -d10s http://172.18.105.109:80/app/empty.htm
                                      Running 10s test @ http://172.18.105.109:80/app/empty.htm
                                        10 threads and 100 connections
                                        Thread Stats   Avg      Stdev     Max   +/- Stdev
                                          Latency    15.00ms    8.18ms 145.16ms   95.58%
                                          Req/Sec   702.82    197.54     6.07k    96.68%
                                        69539 requests in 10.10s, 24.34MB read
                                      Requests/sec:   6885.13
                                      Transfer/sec:      2.41MB

                                      Для сравнения то-же (подогнал по размеру) на Elixir (результаты немного лучше чем у вас, я про Latency и ко):


                                      $ wrk -t10 -c100 -d10s http://172.18.105.109:8085/test-elixir
                                      Running 10s test @ http://172.18.105.109:8085/test-elixir
                                        10 threads and 100 connections
                                        Thread Stats   Avg      Stdev     Max   +/- Stdev
                                          Latency    33.86ms   30.49ms 281.11ms   82.70%
                                          Req/Sec   376.13    283.00     5.25k    77.16%
                                        36967 requests in 10.10s, 12.94MB read
                                      Requests/sec:   3660.63
                                      Transfer/sec:      1.28MB

                                      Причем на tcl 2 из 4-х cpu спят, справляется практически 2-мя потоками 30-40% usage (чтобы загрузить на все 100% надо wrk -t100 юзать).


                                      на Elixir же — все 4-е ядра под 100% busy и контекст-свич кернел-таймом погоняет.


                                      Так-что реализация тоже важна (я не думаю, что tcl "шустрее" elixir)...


                                      Ну и не компиляторами едиными…
                                      П.С. Go не держу, так что сравнить не можу...

                                      • 0
                                        Если можно, хотелось бы подробностей про tcl!
                                        • +1

                                          А чего тут подробнее — Tcl.


                                          Ну а реализация конкретно этого http-server'a — собственная, асинхронная, на tpool-потоках, используя один пул listener/auth/supplier и второй пул workers, между ними асинхронный tcl reflected channel…
                                          Реализаций коих штук двадцать на просторах (и не думаю что моя быстрее). Так же как думаю, что и tcl нисколько не быстрее erlang (просто elixir, хмм..., ну да ладно не хочется холиварить)...


                                          Если совсем лень что-то делать (а тикль юзать или пробовать хочется), да и нужно еще быстрее — nginx + tcl по fastcgi (с некоторой обвязкой) порвет на таких хэлловордских задачах все вышеперечисленное, как тузик грелку.


                                          Я его так и юзаю обычно, http-server на чистом тикле нужен когда тащить nginx тяжело или низя (embedded).


                                          Писать мне лично на тикле удобнее чем на erlang, нодах, рюстах и сях вместе взятых (даже питон думаю не сравним, я про удобство и лаконичность)… Но я немного предвзят, ибо уже сто лет как в TCT (разраб в tcl core team), т.е. я его делаю (и если что-то не нравится переделываю сам;).
                                          Но это не node.js, порог вхождения много-много выше (как минимум при "сборке" сервера)...

                                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.