Pull to refresh

Comments 42

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

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

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

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

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

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

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

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

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

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

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


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

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

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


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

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

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


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

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


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

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


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

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


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

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


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

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

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

Привет, передача поведения это не равносильно 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 и т.д. которые как ни странно принимают на вход код(т.е. поведение), но их устройство само по себе помогает уменьшить умственную нагрузку на программиста.

Нет, 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( ()=> [] )
            }

        } )

    } )
}

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

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

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

На самом деле важно соответствие скорости/качества(скорость реализации и устойчивость кода к багам)/соответствию задаче, подобные бенчмарки про это не рассказывают, к сожалению.
UFO just landed and posted this here
Называется хватаем за уши и тянем, тянем пока не получим нужный нам результат.
К победе Erlang я притянуть не смог. А за ссылку спасибо!

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


+K true, enable kernel poll

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


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

А ведь точно! Про флаг знаю. На 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)>
Включение epoll снизило показатели по тестам.

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

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

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

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

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

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


Вот вам для сравнения на чистом тикле (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 не держу, так что сравнить не можу...

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

А чего тут подробнее — 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, порог вхождения много-много выше (как минимум при "сборке" сервера)...

Sign up to leave a comment.

Articles

Change theme settings