Проблематика
Я стал программировать на Go после Java и PHP. И сейчас расскажу почему.
Java классная штука. У нее приятный синтаксис. Многие крупные проекты используют его в бою. Все было бы круто, если не JVM. Для того, чтобы развернуть серьёзное приложение на Java, вам понадобится тачка с большим количеством оперативной памяти. И это совершенно не годится для стартапов(в моем случае).
В свою очередь в PHP при каждом запросе приложение инициализируется заново, нет многопоточности из коробки, ниже производительность.
У каждого языка программирования, да и у каждой технологии, есть свои слабые стороны. Всегда чем то приходится жертвовать — производительностью, ресурсами системы, сложностью разработки.
Go в этом плане не исключение. Выдавая неплохую производительность и экономно расходуя ресурсы, язык требует от разработчика особого подхода. Если подходить к разработке приложений на Go, как на Java, то тогда действительно возникнут трудности. Мы сталкиваемся с отсутствием таких привычных вещей, как ООП, перегрузка методов и функций, обобщенное программирование, а также исключений.
Когда нет привычных решений, на смену им приходят новые, но не менее эффективные: гибкая система типов, интерфейсы, разделение данных и поведения. Сейчас я продемонстрирую все на примерах.
Перегрузка методов и функций
В Go нет перегрузки методов и функций. Предлагается просто давать разные имена методам и функциям.
func SearchInts(a []int, x int) bool
func SearchStrings(a []string, x string) bool
Иной подход — это использование интерфейсов. Для примера создадим интерфейс и функцию для поиска:
type Slice interface {
Len() int
Get(int) interface{}
}
Search(slice Slice, x interface{}) bool
Теперь достаточно создать два типа:
type Ints []int
type Strings []string
И реализовать интерфейс в каждом из типов. После этого можно использовать поиск и по строкам и по числам:
var strings Strings = []string{"one", "two", "three"}
fmt.Println(Search(strings, "one")) // true
fmt.Println(Search(strings, "four")) // false
var ints Ints = []int{0, 1, 2, 3, 4, 5}
fmt.Println(Search(ints, 0)) // true
fmt.Println(Search(ints, 10)) // false
ООП
В Go нет того ООП, к которому мы так привыкли. ООП в Go это по сути встраивание типов с возможностью перегрузки методов родителя методами потомка. Пример:
// родитель
type A struct {}
// может быть переопределен в потомке
func (a *A) CallFirst() {
fmt.Println("A CallFirst")
}
// потомок
type B struct {
A
}
// переопределяем метод в потомке
func (b *B) CallFirst() {
fmt.Println("B CallFirst")
}
a := new(A)
a.CallFirst() // "A CallFirst"
b := new(B)
b.CallFirst() // "B CallFirst"
В этом случае все работает так, как необходимо. Как поступить, если нам нужна реализация метода в родительском типе, работа которого зависит от переопределенных в потомке методов? Добавляем в родительский тип метод со сложной логикой и несколько методов для переопределения:
// метод со сложной логикой
func (a *A) CallSecond() {
fmt.Println(a.GetName(), a.GetMessage())
}
// может быть переопределен в потомке
func (a *A) GetName() string {
return "A"
}
// может быть переопределен в потомке
func (a *A) GetMessage() string {
return "CallSecond"
}
// переопределяем метод в потомке
func (b *B) GetName() string {
return "B"
}
a.CallSecond() // “A CallSecond”
b.CallSecond() // “A CallSecond”, а нужно “B CallSecond”
Я выбрал для себя такое решение — создаем и реализуем интерфейс для родителя и потомка. При вызове сложного метода передаем ссылку на интерфейс и в родителе и в потомке:
// создаем интерфейс
type SuperType interface {
GetName() string
GetMessage() string
CallSecond()
}
// метод со сложной логикой
func (a *A) сallSecond(s SuperType) {
fmt.Println(s.GetName(), s.GetMessage())
}
// реализуем метод интерфейса в родителе
func (a *A) CallSecond() {
a.callSecond(a)
}
// реализуем метод в потомке
func (b *B) CallSecond() {
b.callSecond(b)
}
a.CallSecond() // “A CallSecond”
b.CallSecond() // “B CallSecond”
Вам может, что это не совсем элегантно, зато мы избегаем дублирование логики. Кроме того интерфейсы понадобятся, когда метод или функция в качестве аргумента будут принимать обобщенный тип:
// создадим еще одного потомка
type C struct {
A
}
func (c *C) GetName() string {
return "C"
}
func (c *C) CallSecond() {
c.callSecond(c)
}
// функция, которая должна работать с A, B и C
func DoSomething(a *A) {
a.CallSecond()
}
DoSomething(a)
DoSomething(b) // ошибка, не тот тип
DoSomething(c) // ошибка, не тот тип
Переделаем функцию DoSomething так, что бы она принимала интерфейс:
// функция, которая должна работать с A, B и C
func DoSomething(s SuperType) {
s.CallSecond()
}
DoSomething(a) // “A CallSecond”
DoSomething(b) // “B CallSecond”
DoSomething(c) // “C CallSecond”
Таким образом мы отделяем данные от поведения, что является хорошей практикой.
Обобщенное программирование
В Go все таки есть обобщенное программирование и это interface{}. Опять же это непривычно, т.к. нет синтаксического сахара, как в Java.
ArrayList<String> list = new ArrayList<>();
String str = list.get(0);
Что же мы получаем в Go?
type List []interface{}
list := List{"one", "two", "three"}
str := list[0].(string)
На мой взгляд разница не велика! Если же использовать интерфейсы, то можно избежать явного приведения типов. Приведу пример:
// создаем интерсейсы
type Getter interface {
GetId() int
}
type Setter interface {
SetId(int)
}
// обобщаем интерфейсы
type Identifier interface {
Getter
Setter
}
// создаем новый список
type List []Identifier
Добавим несколько типов, которые будут реализовывать Identifier и функции, которые будут работать с интерфейсами.
// реализует Identifier
type User struct {
id int
}
// реализует Identifier
type Post struct {
id int
}
func assign(setter Setter, i int)
func print(getter Getter)
Теперь мы можем пройтись циклом по массиву без явного приведения типов
list := List{new(User), new(User), new(Post), new(Post), new(Post)}
for i, item := range list {
assign(item, i)
print(item)
}
Обработка ошибок
Блок try/catch отсутствует в языке, вместо этого методы и функции должны возвращать ошибку. Кто то считает это недостатком языка, кто то нет. Есть люди, которые принципиально не используют try/catch, т.к. это медленный блок. Как правило хватает стандартной обработки ошибок:
func Print(str string) error
if Print(str) == nil {
// делаем что то еще
} else {
//обработка ошибки
}
Если же нужна сложная обработка ошибок, как в блоке try/catch, то можно воспользоваться следующим приемом:
switch err := Print(str); err.(type) {
case *MissingError:
// обработка ошибки
case *WrongError:
// обработка ошибки
default:
// делаем что то еще
}
Таким образом с помощью конструкции switch можно свести в минимуму отсутствие try/catch.
Подводя итоги
Итак, действительно ли язык имеет такие серьезные проблемы? Я думаю, нет! Это вопрос привычки! Для меня Go — это оптимальное решение, когда надо написать системную утилиту, сетевого демона или веб приложение, которое способно обработать десятки тысяч запросов в секунду. Go молод, но очень перспективен, хорошо решает свои задачи. Go предлагает новый подход, к которому нужно привыкнуть, обжиться в нем, чтобы чувствовать себя комфортно. Я это уже сделал, советую и вам!
P.S.: полный код примеров доступен здесь.
Комментарии (77)
solver
27.04.2016 13:26+12В начале дается причина
>Все было бы круто, если не JVM. Для того, чтобы развернуть серьёзное приложение на Java, вам понадобится тачка с большим количеством оперативной памяти. И это совершенно не годится для спартапов.
А потом рассказывается, как можно написать кучу кода, для замены JVM.
А где собственно подтверждение причины?
Где доказательства, что памяти действительно надо во столько раз больше, что дешевле перейти на Go?
Почему писать больше кода в Go дешевле, чем добавить памяти на сервак (это не учитывая, что доказательство большего потребления памяти вами не приведено)?asolomonoff
27.04.2016 15:36-5первое, JVM не подходит мне лично, т.к. я не хочу платить за дорогой хостинг
второе, достаточно запустить любой сервер приложений Java и посмотреть количество потребляемой памяти. Недавно мы тестили Go и Java(spring + jetty). Go скушал 37мб, против 678мб у Java, да производительность показал в два раза выше. Мы списали это на spring.solver
27.04.2016 16:25+8>первое, JVM не подходит мне лично, т.к. я не хочу платить за дорогой хостинг
Вот так и надо было писать в статье «Лично я не смог хоть немного выучить Java и сделал приложение которое потребляло много ресурсов», а не громкие завления в духе «JVM жрет много ресурсов и точка»
Ну и опять же, Давно под Go есть шаред хостинги по 1$?
Ибо у меня несколько проектов на Java и Scala крутится на 5$ инстансах DO.
При чем ДО не самый дешевый, аналогичные инстансы можно найти сильно дешевле.
>второе, достаточно запустить любой сервер приложений Java и посмотреть количество потребляемой памяти
А зачем его запускать-то? Ну давайте напишем приложение на Go, которое отожрет много памяти и будем его в пример приводить… Да, кстати, а есть они вообще под Go эти самые сервера приложений? А то вы сравниваеете навороченный програмный комплекс с простым web приложением, что как бы косвенно говорит о вашей квалификации как разработчика и о достоверности ваших выводов.
>Недавно мы тестили Go и Java(spring + jetty)…
А недавно мы написали кривое приложение на Go и оно съело все ресурсы… и что с того?
Это лично вы не умеете писать на Java, а не Java потребляет много ресурсов.
Опять пример не в пользу вашей квалификации и сделанных выводов.
Чтобы писать статьи с такими громкими заявлениями, надо для начала хорошо разбираться в вопросе.
Если уж беретесь что-то сравнивать в таком ключе, то потрудитесь запастись реальными примерами и корректными сравнениями, а не домыслами вытекающими из вашей слабой квалификации.
Для начала, далеко не всем нужны сервера приложений. Если вы на гласфише пытались делать публичный веб проект, то вы должны были четко понимать для чего вы это делаете. А так по вашим каментам все выглдяит так, буд-то вы по хреновым хеловордам из инета пытались на Java поднять проект и он у вас тормозил.
Не разобравшись начали писать на Go. К сожалению, такими «саксес стори» пестрит весь интернет…
Я не противник Go, и не адвокат Java.
Просто надо корректно сравнивать технологии, а не выдавать собственную некомпетентность за плюсы и минусы технлогий. Если это ваш личный опыт, то не делайте обобщений, так и пишите «мы не смогли», «мы не умели», «мы не знали как, поэтому взяли первое попавшееся решение в интенете» и т.д.
Тогда ваша статья будет иметь правильную тональность.
Не «JVM говно, а Go молодец»,
а «Начинающему разработчику, проще начать с Go, т.к. язык молодой и все решения свежие в гугле проще находятся. А Java уже 20 лет и при поиске надо фильтровать старые не эффективные решения от новых более эффективных»
P.S. Ну и да, ждем пруфов в виде одинаковых проектов, в которых Go есть памяти в 18 раз меньше чем Java.
Чтобы можно было понять, на сколько адекватно все здесь вами написанное.asolomonoff
27.04.2016 17:22-4я не смог хоть немного выучить Java
А так по вашим каментам все выглдяит так, буд-то вы по хреновым хеловордам из инета пытались на Java поднять проект и он у вас тормозил.
взял офф доки java ee, по ним делал. Проект не тормозил, просто много кушал. Я не противник java, просто мне не подошло.
А недавно мы написали кривое приложение на Go
писал не я, а коллега java программист, не в его интересах было писать криво.
Не «JVM говно, а Go молодец»,
я этого и не говорил, и вообще статья о проблемах go, а не jvm))
А Java уже 20 лет
круто! Но это не означает, что это панацея для всех проектов
Ну и да, ждем пруфов в виде одинаковых проектов, в которых Go есть памяти в 18 раз меньше чем Java
легко, достаточно написать сервер на go и и на spring, но повторюсь статья не об этом))) извините, если задел за живое)
Moxa
27.04.2016 13:51+5у меня есть веб-приложение, написанное на жаве, жрет 24 мегабайта оперативы. Что я делаю не так?
j_wayne
27.04.2016 15:02+1Видимо, ограничиваете heap и т.п.) По умолчанию этого мало кто делает, оттуда и сказки.
Немного оффтоп, но я так и не смог нормально ограничить heap руби программе, в отличие от явы. Может, я конечно плохо старался, но я думал, такие вещи должны быть очевидными.
А в плане потребления памяти — как правило, любая VM будет есть сколько дадут, т.к. это позволяет реже собирать мусор. Руби тоже жрет пока может, если объекты интенсивно создаешь.
asolomonoff
27.04.2016 15:39значит вы не используете энтерпрайз решения, тот же glassfish на старте потребляет 500мб + ~200 на приложение
cy-ernado
27.04.2016 16:35-2У меня есть сервер, на которым запущены debian, веб-приложение на Go (с использованием fasthttp) и nginx.
Веб-приложение кушает 3640 килобайт.
asolomonoff
27.04.2016 17:25понятное дело, что приложение на любом языке может потреблять много ресурсов, я не спорю)
dim_s
27.04.2016 14:25+11Я не хочу обидеть тех, кто программирует на Go, но пожалуй я лучше заплачу дополнительно за железо и заплачу меньше, чем буду тратить время (т.е. деньги) на то, чтобы реализовать в Go то, что есть в JVM из коробки. Java подходит для стартапов как любой другой язык и потребление памяти это сейчас не такая большая проблема как написание хорошего и оптимизированного кода.
kekekeks
27.04.2016 14:44+9Для того, чтобы развернуть серьёзное приложение на Java, вам понадобится тачка с большим количеством оперативной памяти. И это совершенно не годится для спартапов.
Что за бред. У хецнера 60 евро в месяц за Xeon® E3-1275 v5 и 64 гигабайта DDR4 оперативки.
solver
27.04.2016 15:05+9Написано же «не годится для спартапов».
Выходит, по мнению автора, 60 евро в месяц это просто неподъемная сумма для стартапа.
asolomonoff
27.04.2016 15:44-1я писал проект — несколько вебсервисов + фронт, который с ними работает. Приложение потребляло от 2гб оперативки. Я писал один, мне было накладно. Согласен, что когда у тебя есть бюджет и команда, то ничего страшного.
kekekeks
27.04.2016 16:08+3Ну вот на таком сервере ваших приложений можно запустить 32 штуки. Итого чуть меньше двух евро в месяц за приложение. Что у вас за сервис такой, что два евро в месяц найти негде?
asolomonoff
27.04.2016 16:15-6java ee + restfull + клиент на java
это было давно, когда только выходила восьмая javakekekeks
28.04.2016 01:08+2Т. е. вы сравниваете джаву "когда-то давно" и го сейчас, я правильно понимаю?
UA3MQJ
28.04.2016 15:28+1И это еще не предел. У меня на chicagovps серверы, один за 12, второй за 15 долларов в год. Тоже VM, правда от Erlang. Всё работает.
ertaquo
27.04.2016 14:47+1Мне в Go не нравится еще то, что в нем нет возможности задать значение аргументов по умолчанию, типа
Это можно обойти через динамические аргументы (args ...int), но дико неудобно.func Foo(bar int = 5)
madmanul
27.04.2016 15:48+1Согласен, многие стартаперы ошибочно считают что у них больше времени чем денег, а еще наивно полагают что им прям со старта придется столкнуться с высокими нагрузками. И еще — хотелось бы видеть сравнение go с его главным конкурентом по потреблению ресурсов — nodejs.
asolomonoff
27.04.2016 15:52могу сказать, что мы в компании проводили тесты java(Netty), nodejs, go(нативная реализация), scala, erlang. Основными конкурентами по производительности были java и go, чуть чуть позади erlang, потом scala, а только потом nodejs. Выложу статью про это дело попозже
cy-ernado
27.04.2016 16:40сравнение go с его главным конкурентом по потреблению ресурсов — nodejs.
Судя по опыту миграции некоторых компаний с nodejs на go (Digg, VK), нода конкурентом не является.
defaultvoice
27.04.2016 15:52-1>В свою очередь в PHP при каждом запросе приложение инициализируется заново
Я, конечно, не пхпшник, но разве php-fpm не решает эту проблему? Обычный CGI вроде бы давно никто толком не использует, разве что для простеньких скриптов.asolomonoff
27.04.2016 15:58+1php-fpm загрузит в память только скомпиленные модули php. Ваше же приложение будет инициализироваться при каждом запросе. Отчасти эту проблему решает phalcon — это фреймворк для php на плюсах. Но опять же ваш код будет инициализироваться при каждом запросе.
andrewnester
27.04.2016 16:48есть php-pm для таких случаев, но это совсем другая история уже https://github.com/php-pm/php-pm
Source
27.04.2016 15:58+3А зачем бороться с тем, что Go ругают? Многим не нравятся языки, которые вводят искусственные ограничения. И это вполне нормально, особенно с учётом того, что есть отличные альтернативы, например: Elixir и Nim.
asolomonoff
27.04.2016 16:02согласен, с тем не всем может понравится язык. Это нормально. Я говорю с статье о том, с чем сталкивается любой Go программист. И как это можно решить)
Mesmer
27.04.2016 16:12-7А где Катя, я вас спрашиваю!
asolomonoff
27.04.2016 16:13-3ой, это не я) сам жду)))
Mesmer
27.04.2016 16:15-3Вообще-то сериал про Катю закончился Леной (если не ошибаюсь), поищите.
Но как-то скучно без неё (Кати) на хабре стало. ))Source
27.04.2016 18:52-1Надо вводить традицию: хочешь писать про Go на Хабре — сначала заведи Катю )))
Mesmer
27.04.2016 19:15-2Судя по минусам, которыми нас щедро одаривают, у людей психологическая травма от Кати.
asolomonoff
27.04.2016 19:21-2вообще, очень забавно народ минусует, некоторые комменты вполне безобидные) впрочем, я ожидал похожую реакцию) а что касается статей про Катю, то интересная была подача!
zelenin
27.04.2016 21:35-4то, что для вас безобидно, т.к. в комменте нет ни оскорблений, ни разжигания нацистских настроений, для меня вполне опасно. С неуместного в технической статье (и техническом обсуждении статьи) коммента начался тред на 8 комментов из 60. Удивительно, что вам как автору статьи интересно в комментах «хихи-хаха» на отвлеченную тему.
asolomonoff
27.04.2016 23:14я тоже читал цикл этих статей, не хотел игнорировать человека. Да и в любой момент может быть минута лирики, не вижу ничего такого
Source
28.04.2016 00:40Можно feature-request в TM отправить на сворачивание тредов или самому на JS аддончик запилить.
А пока из консоли браузера выполните$("#comment_8873074").hide();
дабы не видеть это безобразие xD
Sirikid
28.04.2016 01:02+2Зачем, мистер Андерсон, зачем вы пишете ещё одну статью про Go?
asolomonoff
28.04.2016 01:18+1зачем пишут новые статьи о других языках? может потому, что возникают проблемы и их решение, и этим хочется поделиться… разве нет?
Sirikid
28.04.2016 01:29-1> Конечно же, речь пойдет об отсутствии ООП как такового, перегрузки методов и функций, обобщенного программирования и исключений.
Давно уже все это обсудили и здесь и на других площадках.
UA3MQJ
28.04.2016 16:56+6Лично сам не переходил на Go и из этого с серьезными проблемами не сталкивался. Но когда узнал, что в Go есть «зеленые процессы» и обмен сообщениями между ними, то решил узнать об этом подробнее. Все что я узнавал, я критически оценивал в контексте знаний об Erlang. В итоге хотел даже написать какой-нибудь веб сервер и на том и на другом, потом померяться, написать статью. Но потом поуспокоился и эту идею отложил до лучших дней.
В общем, мои скромные изыскания в части сравнения реализации процессов и способов обмена данными между ними, выявили следующее:
1. Обмен сообщениями.
В Go для обмена сообщениями используются каналы. Канал явно существует, как понятие. Он передается в функцию (горутину), а та уже с ним работает.
Что понравилось:
— вроде бы можно одно сообщение из канала принять двумя корутинами
Что не понравилось:
— необходимость самого существования такого понятия, как канал (в ерланг это не надо)
— при отправке сообщения в канал, отправляющий процесс «зависает» пока принимающий не примет данные. Для чего такое может быть нужно, мне сложно понять. В итоге, может выстроиться цепочка из процессов от соединения с пользователем, до записи на диск, и вся она «зависнет» на работе с файловой системой. Но в принципе, на сколько мне сумели объяснить, в таких случаях сообщения «принимают и складывают на стек», читай принимают и складывают в память процесса. Чтобы устранить зависания, в канале может быть включен буфер ФИФО, это от части решает проблему с зависанием.
В Erlang для обмена сообщениями между процессами, не используется понятие канала. Нужно знать PID процесса и отправить ему сообщение. Отправляющий процесс не зависает при отправке сообщения. Он его просто отправляет и работает себе дальше. Отправленное сообщение упадет в почтовый ящик процесса, в очередь, и тот его считает тогда, когда ему будет нужно. Но процесс читающий сообщение из почтового ящика «зависнет», если там нет сообщения. Но это я считаю нормально (нет данных для дальнейшей работы — ложимся спать). К тому же есть возможность установить таймаут и после него выполнять работу дальше. Если же нужно отправить сообщение синхронно, с подтверждением приема от удаленного процесса, то мы его отправляем, потом читаем входящие — в этот момент мы «зависнем», пока не придет ответ. То есть мы можем выбирать режим (почти аналогия буфера каналов).
Вопрос того, что в Erlang процесс обмена сообщениями внутри ноды или кластера происходит прозрачно, тут не обсуждаем. Приложения Go в кластер не объединяются. Там своя тема под названием микросервисы и то, как они меж собой связаны уже решает разработчик, но пусть так. Из положения выходят с помощью grpc или как-то иначе. Но для меня до сих пор остается загадкой, как множество процессов одного приложения, например tcp соединений с клиентами, должны обмениваться с процессом, например, базы данных? Сколько будет каналов, как их столько держать и обрабатывать? В Erlang даже вот чисто концептуально все это получается менее нагроможденным и простым. Процесс клиент просто отправит сообщение серверу, в сообщении будет PID, куда слать ответ. Сервер процесс не хранит всю связность с процессами — клиентами. Очередность будет соблюдена благодаря тому, что сообщения в почтовом ящике процесса выстраиваются в очередь. В то же время, есть возможность выбрать сообщения в другом порядке.
2. Зеленые процессы. В Erlang всё понятно — это виртуальная машина, она выполняет байткод, каждый процесс выполняется на заданное количество редукций, разрешенных для него, потом, что бы он не делал (за исключением атомарных операций), он будет прерван и выполняться будет уже другой процесс. Распределяет работу шедулер. Из коробки ноду Erlang можно считать целой ОС мягкого реального времени.
Я грешным делом сначала подумал и возрадовался, что в Go для реализации работы горутин используется что-то типа встроенной виртуальной машины. Это было бы шикарно: виртуальная машина в приложении. Но в процессе общения в Go'шном канале (ребятам, кстати, спасибо), выяснилось, что никакой виртуальной машины нет. Но как же работают горутины в параллельном режиме? Как осуществляется шедулинг? Оказалось, что если создать горутину, которая в бесконечном цикле прибавляет единицу и никуда это не передает, то она «зависнет» насовсем. И если запустить Go приложение в один процесс (а не по умолчанию 6 — 8), то приложение зависнет насовсем с момента запуска горутины и то, что будет после этого, никогда не выполнится! Я даже сделал ошибочный вывод о том, что на одноядерном процессоре это все в принципе может перестать работать. Но потом вспомнилось про системные threads и что они существуют как бы параллельно даже на одном ядре. Так что, приложение Go вполне себе честно виснет, будучи запущенным в один процесс, если горутина зациклена. Открытием было то, что передача управления на другие горутины происходит при вызове функций других модулей (просто, или, к примеру, вывода на экран). То есть, ничего общего с равномерным распределением ресурсов тут получается, что и нет. Каждый процесс может неопределенное время монопольно использовать один thread, пока не отпустит. В условиях того, что thread'ов выделяется несколько, то это бросается в глаза как бы не сразу. Все это напоминает многозадачность Win95, когда задачи сами решали, когда отдать управление ОС. На сколько мне известно, решение о том, какую горутину выполнять дальше, решает рандом. И вообще это еще в процессе разработки, потому что этот алгоритм показал плохие результаты. Другие статьи по Go вполне ожидаемо сообщают о том, что не всегда целесообразно создавать много параллельных горутин.
3. Производительность. Перед тем, как сделать простой веб сервер на Go и на Erlang и сравнить их по производительности, я взял задачу, которая гарантировано удобнее для Go. Это счастливые билетики. Тупо в лоб 6 вложенных циклов от 0 до 9 и миллион итераций сравнения. Естественно, Go обогнал Erlang больше, чем на порядок. Потом я скомпилировал модуль Erlang с применением HiPE и расчет на Erlang оказался в 4 раза медленнее расчета на Go.
На этом я и остановился. Ощутимой прибавки производительности можно и добиться, но другие минусы расстроили. А целого ряда возможностей Erlang'а нет и скорее всего никогда не будет в Go. Поэтому Erlang и дальше для меня остается более предпочтительным в качестве платформы для разработки серверных приложений. Серьезные проблемы типа дедлоков в нем решаются вообще иной организацией построения приложений и иным подходом в решении задач. А Go это такой же язык, как и сотня других. Простой синтаксис и компиляция в двоичный код спасают скоростью своей работы. Но на этом пока и все. Реализация зеленых процессов и обмен сообщений между ними произвели впечатление каких-то сырых костылей, скрученных изолентой. Еще в процессе обсуждения в канале нашлась еще одна задача, которая тяжело решается на Go из за его принципиально статической природы: это прием и разбор json если он не имеет строго определенной структуры (мап, а в нем мап, а в мапе тоже мап, а может и нет). Так что можно предположить, что с парсингом xml, html там могут быть похожие проблемы. Так же есть информация, что http сервер на Go оптимальнее всего работает в один процесс. Тогда зачем все эти зеленые процессы и каналы?
Ну а в связи с тем, что статья про Go, я закончу на позитивной ноте. Go мне понравился тем, что я будучи совсем ничего не знающим про Go даже могу что-то понять из исходного кода. Концепт там такой, что кода больше, но зато всем понятно. Так вот — действительно общий смысл понятно. В том же Erlang многие вещи будут очень непонятны. На Go вполне удобно делать какие-то консольные утилиты (каких то wxWidgets пока не прикручено, чаще поднимают локальный вебсервер и заходят на него из браузера, но работы вроде бы идут). Полученный exe файл получается размером меньше 10 мегабайт и рядом не требуется укладывать дополSource
28.04.2016 19:29+2Каждый второй пишет какую-нибудь библиотеку для какой-нибудь задачи (csv, orm etc), которые находятся в разных состояниях готовности.
Как по мне, это жирный минус экосистемы Go. У меня есть опыт написания микросервисов на Go, но даже в рамках микросервиса этот буриданов выбор из кучи библиотек, которые делают одно и то же, начинает раздражать. Причём большинство из них напрочь лишены версионности и приходится линковать их к проекту по ревизии в master-ветке o_O
Кстати, для управления зависимостями в Go тоже 100500 альтернатив и ни одной, которая по удобству была бы близка к bundler или hex.
Для себя решил отказом от Go в пользу Elixir. Да, по памяти Go экономнее, но не критично. А по производительности для веб-сервера они одинаковы с точностью до погрешности. При этом код на Elixir получается проще, лаконичнее и понятнее. Хоть и требует, чтобы программист был в курсе функциональной парадигмы и потратил недельку на знакомство с OTP.
GeckoGreen
29.04.2016 11:39+11. При отправке сообщения отправляющая рутина зависает только в случае небуферизированного канала. Если же создать канал с заданным рамером, то она может зависнуть только при его переполнении.
2. С помощью библиотеки runtime можно контролировать смену контекста. Ну в зацикливающиемся приложении вина не на языке.
ElOtroYo
29.04.2016 18:29Вы не могли бы поделиться своим субъективным взглядом на Phoenix Framework и Elixir в частности? Даже поверхностным.
UA3MQJ
29.04.2016 21:30Сложно вот так без подготовки. В принципе фреймворков на Erlang (в конечном итоге) не так и много. Каждый из них находится в определенной мере либо заброшен уже сколько то лет, либо малопопулярен по количеству последователей. Думаю, это общая проблема Erlang экосистемы. Я бы попробовал оценить сообщество разработчиков на Phoenix (где общаются, активность, количество), примеры готовых проектов на нем, попробовать что-то сделать на нем и уже после этого пробовать делать выводы. В этой статье его сравнивают с рубирейлс. Может рельсовики что-то скажут. А так, основа erlang, cowboy, вебсокеты, в техническом плане вроде все нормально. Оценивать надо на сколько удобны сами модели представления, разработки. Сам я пробовал только N2O, но было это еще до того, как я стал серьезно работать с вебом, и на тот момент у меня как-то совсем не пошло.
Конкретно в веб проектах я участвую не очень давно. Но приходится как-то поддерживать две системы, одна на yii, вторая на codeigniter. Поэтому фреймворки я как-то не очень люблю. И не потому что PHP, а из за их замкнутости. Фреймворк загнулся — знания на помойку. Посадили за фреймворк «B», и знания по фреймворку «А» тебе не помогут. Если делать отдельно фронт и бекенд, связывая их стандартными протоколами, то части системы можно будет заменять. Сейчас вот работаю в проекте с AngularJS на фронте. Так он не вызывает внутреннего отторжения. И при разработке такой системы даже в случае с крахом ангуляра, знания и опыт по бекенду точно не пропадут. Либо наоборот, если фронт на AngularJS остается без изменений, а бекенд на скорую руку написан на PHP. После этого можно его переделать на Python или Go или на чем-то еще, в зависимости от того, каких разработчиков получится найти. Поэтому сейчас, когда захотелось написать фронт на ангуляре и бекенд на ерланге со связью через вебсокеты, мне пришлось поискать, кто из ерланг веб серверов умеет вебсокеты. В итоге я остановился на cowboy и yaws, последний выбрал по причине того, что он поставился на win ;) Но даже вот после этого я постарался выйти к тому, чтобы по максимуму отвязаться от типа выбранного сервера. Фронт устанавливает стандартное ws соединение. Сервер yaws это поддерживает в виде конечной точки и функции обратного вызова. В нее попадает поток из ws. В этом месте надо уже забыть про yaws и слать этот поток стандартному Erlang приложению. И тогда нормально будет. А то представьте, на 1/6 часть суши 1000 эрлангистов. Из них yaws знают 1/4. Даже с этой точки зрения найти людей будет проще. Но, возможно, я слишком глобально смотрю на этот вопрос. Если нужен стандартно-типовой проект, быстро что-то сделать, внедрить и забыть о нем, то фреймворки как раз позволяют это сделать. Так случилось и в моем случае: до меня запилили два решения(yii и codeigniter), а когда дошло дело до постоянных каких-то доработок (как это часто бывает на производстве)… а мне с этим жить )
Про плюсы Elixir недавно описал Юрий Жлоба из варгейминга. Я сделал для себя выводы, что знающих напрямую Erlang не особо интересует Elixir. И Exilir интересуются вроде бы как тех, кто в лоб Erlang тяжело воспринял. Однако, сложилось впечатление, что в Elixir более активное сообщество, библиотеки, системы установки модулей. Но не суть. Я думаю, по той причине, что elixir работает на VM Erlang, то под капотом там всё нормально (вспоминаем scala и JVM).
Извините, как-то опять много буков вышло.Source
30.04.2016 02:39+1По внутреннему устройству Phoenix совсем не похож на RoR. Он гораздо проще и по сути является набором plugs, которые можно использовать и комбинировать по своему усмотрению, + Ecto для работы с базами данных + Channels для веб-сокетов.
Собственно, Plug — и есть центральная концепция, чем-то сродни middleware в Ruby, только используется единообразно на всю глубину стека: роутер — это plug, контролер — тоже plug и т.д.
lair
Эм, вы правда не знаете, что обобщенное программирование — это не синтаксический сахар, а
interface{}
— это не обобщенное программирование?andrewnester
наверное неправильное понимание, что такое interface{} — одна из самых больших ошибок начинающих «гоферов»
оставлю тут ссылочку
asolomonoff
в одном из своих докладов Роб Пайк говорил, что в Go есть обобщенное программирование и это interface{}
Sirikid
Роб тролль и не особо скрывается
asolomonoff
слушал курс по java от mail.ru, в нем говорится, что:
конструкция для программиста, но для JVM это:
так что ничем иным, как синтаксическим сахаром это назвать нельзя
lair
То, что в Java это так, совершенно не означает, что в других языках это так же. В частности, в каком-нибудь банальном С#
List<int>
— это именноList<int>
, и никаких кастов (и никакого боксинга/анбоксинга, связанного с кастами) в рантайме не происходит.asolomonoff
согласен, но я и не говорил же про другие языки
lair
Вы говорили, что в Go есть обобщенное программирование — на основании того, что оно реализовано "как в Java, но без синтаксического сахара". Проблема в том, что то, что осталось (а именно — касты всего и вся к/из
interface{}
, он жеobject
, он жеvoid *
) — это не обобщенное программирование.asolomonoff
Про interface{} — это слова создателей языка, не мои) Должно быть, я не точно выразился( Основная мысль была такова, что если мы объявляем списки или карты, которые используют интерфейс(Identifier, не interface{}), то в такие структуры данных мы можем положить любой тип, реализующий этот интерфейс. Кроме того методы и функции смогут работать с такими структурами данных без кастов
lair
Пока что в факе про язык написано, что дженериков в нем нет. Так что я не уверен, что вы правильно понимаете "слова создателей языка".
Мне кажется, вы упускаете из виду тот факт, что списки и словари в Go — как раз дженерики (да, вот так непоследовательно). И именно поэтому вы можете создать список объектов любого типа, вместо необходимости объявлять новый тип списка для каждого типа содержимого. Но попробуйте теперь создать свою собственную коллекцию для произвольного типа (в качестве чисто академического упражнения).
asolomonoff
статья ребят из яндекса. В заключении статьи написано, про дженерики. Говорю же, не я это придумал)))
Полностью с вами согласен — списки и словари дженерики. Я говорю про другое:
в Go
lair
Правда? Цитата:
Я говорю про другое
Угу. Попробуйте с помощью этого "другого" реализовать банальную монаду Maybe (она же
Option[T]
). Естественно, для всехT
. Или, оставаясь в пределах вашего примера, описать следующее: нам нужен методSort<T>(T[] data, Comparer<T> comparer)
, который принимает на вход массив (ура, они уже дженеричные) и компаратор (который бы работал с тем же типом, которого массив).asolomonoff
lair
Гибкость тут ни при чем. Это просто наглядная демонстрация, зачем бывают нужны дженерики помимо навязших всем в зубах контейнеров.
Без кастов? Я бы с любопытством посмотрел на это.
asolomonoff
Я вас прекрасно понял! Я пытался на простом примере с Equaler показать, что это возможно в принципе. Т.е. в функции и методы структур и сами структуры в go могут работать с интерфейсами, а не с конкретными типами данных.
Нужна пища для размышления…
lair
Вы путаете обобщенный код с полиморфизмом. Возможность работать с интерфейсами — это полиморфизм, он с обобщенным (generic) кодом ничего общего не имеет. А обобщенный код — это возможность "типизовать" конкретный фрагмент кода (метод, класс, интерфейс, бла-бла) конкретным типом, известным только потребителю (и при этом, понятное дело, сохранить статическую типизацию).
asolomonoff
да, вы правы! Я вкладывал иной смыл в термин обобщенный код.
lair
А люди, которые говорят, что в Go нет дженериков, имеют в виду вполне конкретный смысл. Поэтому когда вы говорите, что обобщенное программирование есть, но подменяете смысл — это, скажем так, некрасиво.
asolomonoff
пожалуй, соглашусь) что неправильно использовал термины( эх…
Gorthauer87
Это уже проблемы явы, но если языки поддерживают мономорфизацию, то этот сахар превращается в уже очень мощную фичу без накладных расходов.
codemax
Компилятор следит за типами в дженериках и не даст сделать глупостей. Или хотя бы предупредит об опасности. Как бы уже нехилый бонус.
asolomonoff
согласен, в Go тоже следит. Если тщательно проектировать проект, то использование interface{} можно свести к минимуму, тогда большинство глупостей отпадут на этапе компиляции.