Comments 42
@читай невнимательно/сразу пиши
А какой смысл гонять бенчмарки на привет-мирах? Этак какой-нибудь nginx вообще всех уделает. И что? А ничего, сколь-нибудь серьёзного приложения на nginx не напишешь, да и не для того он предназначен.
читал что хаскеловский warp в подобных тестах был быстрее nginx
Ради справедливости и оффтопика — там можно на lua код писать, причем достаточно приличную логику.
У нас трекинг на ngx-lua пишет в Редис. Работает действительно быстро.
env GOARCH=arm64 go build
возможные сложности при изучении функционального программирования
Я думаю этот тезис вводит людей в заблуждение. Код написанный с сильным упором в FP оказывается:
1) Копмактнее — вы переиспользуете поведении за счет передачи функции как аргумента или результат.
2) Проще для понимания — имутабельные данные не требуют думать о проблемах изменения их стотояния. Вы не думаете о сайд эфектах когда пишите чистые функции.
3) Лучше тестируемый — чистые функции легко проверить, они зависять только от входных параметров, т.е. не нужно гемороиться с моками.
4) Concurrency проще — за счет использования Imutability(вы пытаетесь понять почему данные случайным образом меняются на не валидны(всево лишь случайно/по незнанию написанный код рассихноризирующий конкурентное взнаимодейстиве), чистые функции легко паралелятся и не требуют синхронизации.
Спасибо за список, согласен с каждым пунктом.
То есть вы утверждаете, что такого рода шарада (сравните там её с оригинальным вариантом на оорп) компактнее, проще для понимания, тестируемей и конкурентней?
А какой там оригинальный вариант, ваш с магическими декораторами? Нет, знаете, этому варианту лучше отказать. Магические декораторы не нужны. Это примерно как написать на scala с имплиситами, выглядит неплохо, а как работает — непонятно.
В "шараде" бОльшая часть наблюдаемой сложности появилась из-за танцев вокруг типов, но при этом там определенно меньше магии.
Они не более магические, чем createSelector. $mol_mem — обычная функция, которая реактивно кеширует результат выполнения метода. Ни чем не хуже функции createSelector, которая творит под капотом реальную магию. Ну и танцы с типами для $mol_mem, кстати, не потребовались, ибо она подхватывает сигнатуру метода и не меняет интерфейс.
Не понял в чем суть приведенного кода как аргумента.
ООП не мешает ФП и можно встретить языки как с ФП+ООП так и в других комбинации. У вас похоже понятия о разных концепциях имеют отличное от, если так можно, выразится академического взгляда.
ФП это о функциях и их композициях — т.е. о поведении.
ООП это о организации — о изоляции и спецификации контрактов взаимодействия между объектами.
К тому же я вам дам одно важное пояснение если код вам не понятен то есть не одна а три причины:
- Вы не знакомы с концепциями которые выражает этот код.
- Кто-то просто плохо написал код. к
- Концепция которая реализована в ЯП либо "плоха" сама по себе для данной задачи либо её реализация в языке.
По этому предлагаю вам:
- Подумать какие из вышеописанных причин и в каком объеме влияют на код который вы видите,
- Второе — поискать примеры в которых ФП дает бонусы.
ФП это о функциях
Не простых функциях, а чистых.
ООП это о организации — о изоляции
Не путайте инкапсуляцию, абстракцию и изоляцию.
Подумать какие из вышеописанных причин и в каком объеме влияют на код который вы видите,
Иммутабельность вынуждает превращать код в шараду вида: пишем чистые функции, а состояние меняется само, только тут надо прочитать заклинание createSelector, там мантру "создаём новое состояние мира копированием старого, но с новым расположением одного камушка". А ведь всё, что на самом деле надо — идемпотентность. И для её достижения вовсе не нужно насиловать мозг ни себе, ни окружающим, превращая код в хитросплетённую лапшу из однострочных функций.
Второе — поискать примеры в которых ФП дает бонусы.
Типа "придумайте сами за меня мои аргументы"? :`-D
Привет, передача поведения это не равносильно 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( ()=> [] )
}
} )
} )
}
На самом деле важно соответствие скорости/качества(скорость реализации и устойчивость кода к багам)/соответствию задаче, подобные бенчмарки про это не рассказывают, к сожалению.
А вы с какими флагами BEAM запускали? '+K true', как водится, не поставили?
+K true
, enable kernel poll
Один из самых важных флагов запуска, который должен быть выставлен в любом production и писькомерном окружении — включает использование epoll/kqueue. Без него всякие изменения не имеют смысла.
Возможно, для адекватности сравнения стоит поставить +stbt db
. Я точно не помню, как себя ведет планировщик Go, но если он закрепляет треды планировщика на ядрах, то этот флаг нужен, чтобы включить аналогичное поведение в BEAM. Это второй кандидат на звание must have флага.
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)>
Система работает стабильно, но даже с ничего не делая, ее load average: 2.00, 2.01, 2.05.
Как же так? Вы точно уверены? А то звучит это как «Система работае стабильно, но даже ничего не делает нагружает два ядра по максимуму».
2. Сравнивать веб сервер на arm процесорах как то не правильно в мире в котором 90+% веб серверов это amd64. разница в том что в golang есть вещи сделаные на ассембли для каждого процесора и в это amd64 лудше представлен в golang. такие вещи как работа со стрингами или математика сделано через ассембли с инструкция для каждого вида процесоров. так что на amd64 golang показывает лудший результат.
3. Скорость исполнения не все в разработки программ. есть много других критериев
Не знаком с 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.
Ну а реализация конкретно этого 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, порог вхождения много-много выше (как минимум при "сборке" сервера)...
Простейший HTTP сервер на Golang и Elixir. Сравнение производительности