Pull to refresh

Comments 62

Хотелось бы еще увидеть сравнение скорости работы этих программ
Думаю в этой задаче разница будет не заметна, скорость будет определяться внешними факторами: открытием страниц и записью на диск. Но попробовать можно, правда у меня интернет не очень, могу только предложить jar для теста.

Моё субъективное мнение: скорость — это не главное. В крупном приложении скорость определяется архитектурой. На много важнее, чтобы приложение работало правильно и легко изменялось.
В плане «работало и легко изменялось» имхо выигрывает Go, как в принципе и какие-нибудь другие компилируемые ЯП, которые не требуют установки виртуальных машин/интерпретаторов.
UFO just landed and posted this here
Так какая разница, если на машине стоит старый рантайм? Всё зависит от продукта и клиентов, одно дело тырпрайз, где ваш продукт может нигде кроме как уютненьких серверов не крутиться, и совсем другое — десктоп, где может быть дичайший зоопарк. Вот не установит/снимет галочку/удалит/не обновит юзер нужный вам Java runtime, и всё.
Ну или я не понял суть OSGi.
UFO just landed and posted this here
UFO just landed and posted this here
Я говорил к тому, что бывают случаи(подобные этому), где имеет значение отсутствие лишних прослоек и зависимостей.
UFO just landed and posted this here
UFO just landed and posted this here
Сейчас за 1500 рублей продаются вполне работающие китайские андроиды — для них можно скомпилировать бинарник из Go-кода.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
Ну вы же понимаете, что создание 100 потоков на Java — это не то же самое, что создание 100 горутин? В данном простом примере воркеров всего 4, так что можно и забить, а когда будет что-то помасштабнее, чем просто опрос одного HTTP-сервера, то в Java начнутся проблемы. В общем, пока в Java не реализуют настоящую вытесняющую многозадачность, Java так и будет сосать в сторонке.
Правда интересно что в джава не так с этим?
Каждый поток кушает ресурсы, даже если в данный момент они ничего не делает (например, висит на блокирующем вызове).

Но эта проблема спокойно решается в библиотеке, без изменения языка.
UFO just landed and posted this here
Я что-то слышал про то, что в Java есть зеленые потоки, которые легковесны как горутины. Было бы не плохо, если бы кто-то что-то про это разъяснил.
UFO just landed and posted this here
Сейчас пилят библиотеку quasar для реализации зеленых потоков, но там какой-то хардкор с байткод-инструментацией происходит, чтобы всё это могло работать поверх JVM. А вот пример уже рабочего кода.
пишите на clojure и используйте core.async ;)
в следующей жизни )
Akka реализуется поверх обычных OS потоков, в результате чего наследует все те же проблемы. Если не делать блокирующие вызовы, то да, канает. Но а если я хочу? Ну потому что синхронный код выглядит проще и читабельнее, чем асинхронный. Да, есть фьючеры, коллбэки, скедулеры, но с ними код выглядит сложнее — это факт. Спросите любого эрланг-программиста, пересаживающегося на Akka.
Читая статью, создается впечатление, что все более-менее нормально, но вот когда открываешь код…
Код написан плохо не потому, что в нем не хватает паттернов. Грабберы при первом IOException завершатся — программа никогда не закончится (фактически зависнет). Один и тот же файл одновременно открывается на запись и на чтение. Входные параметры не проверяются. Нет синхронизации. Если программа остановится в момент записи в файл — он может быть поврежден, далее на чтении валидность не проверяется и программа прочитает битый хэш. За Hex.encodeHexString еще можно было побороться и написать самому, а не тащить сразу либу. Хэши сохраняются в файл как массив байт и на чтении каждый раз конвертятся в строку — можно было сразу записать строку. Используются обычные стримы, но вдруг — BufferedWriter, который все время флашится. Число записей в секунду округляется.
Сравнивайте go с питоном. там тоже кодестайл влияет на смысл кода.
То же на go: time.Sleep(100 * time.Millisecond)
Что-то на Java я такое не увидел.

Ну и не совсем понятен посыл статьи. Производилось сравнение? Какое сравнение? Читабельности кода? Скорости? Использование тредов вместо гороутин — это совсем не равноценная замена: по семантике они чем-то похожи, а по смыслу — совсем не похожи. Делать выводы на основе одной несложной задачи тоже не совсем верно. Что автор хотел показать-то?
Использование time.Sleep обсуждали в этой ветке комментариев. Если я правильно понял, это нужно для того, чтобы дать возможность выполниться основному потоку. Но в моём случае это не нужно, т.к. я явно указал приоритет worker.setPriority(2). Чтобы сохранить дословность пересказа, можно использовать Thread.sleep(100), но лучше подойдёт Thread.yield() — он переместит поток в конец очереди планировщика без задержки.

О смысле статью расскажу подробнее. Я работаю с Java относительно давно, и в целом уже нормально разобрался, а о Go только слышал, но довольно много. Мне стало интересно, стоит ли бросать Java и переходить на более современный Go? Поэтому я и решил их сравнить на этой задаче. Как вывод скажу, что Go моложе и что-то в нём сделано удобнее, и если вы не знаете ни того ни другого, то возможно лучше выбрать Go. Но Java пока держится, хоть и некоторые решения выглядят как костыли, например аннотации. И имея опыт в Java пока рано её бросать.
Я работаю с Java относительно давно

Ха-ха. Как сказал Joel Spolsky, 5 years of experience may be 1 year repeated 5 times

Мало того, что у вас ужасный код, так ещё и непотокобезопасный. Почему вы quotesCount модифицируете из нескольких потоков без синхронизации?
Это похоже на вызов :) Я не могу не ответить.
Вам не кажется глупым ради обнуления счётчика городить синхронизацию? Ведь самое страшное, что может произойти — мы недосчитаемся цитаты при подсчёте скорости.
Тогда уж больше опасность представляет чтение этих переменных, они ведь не объявлены volatile, и результат может быть устаревшим.
Вы уверены, что нужно уделять столько внимания вспомогательной функциональности? Вероятность ошибки ведь очень низкая.
Вот честное слово не это хотел написать :)
На работе pastebin заблокирован злой прокси, кода не видел. Не хватило бы сделать счетчик volatile?
Синхронизация и volatile несут несколько различный смысл.
Думаю, что volatile значительно улучшило бы ситуацию в случае инкремента. Без volatile переменная может кэшироваться в потоке. Т.е. есть вероятность, что переменная обнулится из другого потока, а мы не заметим, и присвоим ей {старое значение + 1}.
Но, теоретически, между чтением и записью переменная всё равно может быть изменена другим потоком. Наиболее вероятно это между подсчётом скорости и обнулением, т.к. там несколько операций выполняется. Поэтому для полной уверенности нужно синхронизировать блок от чтения до записи.
Забавно смотреть, как автор, не разобравшись толком в Go, начинает сравнивать его с другими языками.

Для начала, флаги можно указать так:
var (
WORKERS = flag.IntVar(«w», 2, «количество потоков»)
REPORT_PERIOD = flag.Int(«r», 10, «частота отчетов (сек)»)
DUP_TO_STOP = flag.Int(«d», 500, «кол-во дубликатов для остановки»)
HASH_FILE = flag.String(«hf», «hash.bin», «файл хешей»)
QUOTES_FILE flag.String(«qf», «quotes.txt», «файл записей»)
)
это гораздо короче и удобнее того что автор понаписал как для java, так и для Go. Более того, аналога этой конструкции в java нет.

Далее, автор превратно понял логику обработки исключений в Go. Если по каким-то причинам удобнее при ошибке прерывать flow и обрабатывать в 1 месте, вы можете даже не проверять err != nil, а просто recover'ить рантайм панику при вызове метода у нулевого объекта, созданного при ошибке — не идеальное решение, но избавляет от проверки результатов работы функции.

Вообще же сама по себе логика, когда при почти любой ошибке прерывается flow и исключения «обрабатываются» где-то высоко, если явно не предусмотрено обратное — ущербна — непредусмотренная явно ошибка может привести к непредсказуемым последствиям. Особо приятно ловить в Java null pointer exception. В Go в этом смысле более явный и унифицированный подход, когда разворачивание стека вызовов наверх происходит только по делу — при паниках — а ошибки возвращаются из самой функции.

В данном случае у вас go-код, иллюстрирующий парсинг HTML, работает неидентично java-коду. Если в java-код добавить дополнительный try-catch вокруг запроса страницы, чтобы она не падала при сетевых ошибках — получится ещё уродливее, чем в go, в дополнение к неудобной проверке ошибки далеко от её места возникновения.

Прибавляя к этому абсурдность сравнения системных тред и лёгких тред, общий вывод печальный для автора: данная статья — треш, и её надо очень долго дорабатывать, чтобы она не обесценивала хабр своим присутствием.
Да будет холивар :)

Вообще же сама по себе логика, когда при почти любой ошибке прерывается flow и исключения «обрабатываются» где-то высоко, если явно не предусмотрено обратное — ущербна

Вы, очевидно, не сталкивались с большими приложениями. Надо сказать спасибо, что это так. Основное применение java это бизнес приложения. У нас всегда несколько уровней и ошибка должна обрабатываться там, где известно что с ней делать, т.е. гораздно чаще нам и нужно бросить ошибку наверх. И спасибо, что у нас нету «err != nil».

непредусмотренная явно ошибка может привести к непредсказуемым последствиям

Вот это поворот!

Особо приятно ловить в Java null pointer exception.

Если вы ловите null pointer, значит проблема в коде, java тут не при чем
И вообще не стоит называть что-либо ущербным, если заведомо знаете, что это субъективное мнение.
Зато у нас есть другие плюшки, например можем ограничить длину очереди, если не будем успевать её разгрести.
Ну так и в Гоу это есть: github.com/bolknote/NarchTools/blob/master/cgodownloader.go#L85 тут она как раз у меня ограничивается.

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

//открытие файла на чтение
hash_file, err := os.OpenFile(HASH_FILE, os.O_RDONLY, 0666)
//открытие файла на запись
hash_file, err := os.OpenFile(HASH_FILE, os.O_APPEND|os.O_CREATE, 0666)

можно написать

//открытие файла на чтение
hash_file, _ := os.OpenFile(HASH_FILE, os.O_RDONLY, 0666)
//открытие файла на запись
hash_file, _ := os.OpenFile(HASH_FILE, os.O_APPEND|os.O_CREATE, 0666)
Java с Go сравнивают не из-за синтаксиса, а из-за того, что Go компилируемый, а компилятор с хорошим оптимизатором. А Java собирается в байт-код и выполняется в VM.
Ну а во-время выполнения байт-код компилируется, и по скорости выполнения проигрыш обычно совсем не значительный, в сравнении со скомпилированным кодом.
Ещё нужно учесть, что JIT-компиляторы знают про целевую машину больше, чем AOT, и могут применять различные оптимизации, доступные только этому железу.

Плюс, хотспот умеет делать ещё много оптимизаций по ходу выполнения, зная какие участки программы будут работать чаще, и с какими данными.
Зато у AOT сколько угодно времени на компиляцию и оптимизацию, а у JIT — очень мало. В общем, и у AOT, и у JIT есть свои плюсы, не вижу смысла холивар устраивать по этому поводу.
Для JIT (хотя там со своими заморочками) у гугла имеется Dart. Логичнее было бы сравнивать Dart и Java.
Да, где байт-код транслируется в машинный, причем транслятор очень хороший.
Как-то мне try-resource показались значительно хуже defer. Особенно если их будет много да еще и вложенных.
Заменять горутины нитями тоже не то: это как нити заменять процессами.
Обращу внимание на новые неблокирующие классы для работы с файлами из Java7. Найти из можно в java.nio


Чагоу? В каком месте они, простите, неблокирующие?
java.nio.files.WatchService — хоть бы погуглили сначала
Погуглить надо бы вам :) WatchService, что бы это ни было, обертка вокруг inotify, fsevents и что-там-в-винде или самопальная реализация в отдельном тредике, или и то и другое, не имеет никакого отношения к неблокирующей работе с файлами :)

К неблокирующей работе с файлами можно отнести то, как с ними работает nodejs, erlang (при +A > 0), linux AIO, libeio. И то, все это можно назвать неблокирующей работой с файлами лишь условно :)
Мне тут рядом сообщили, что я не прав ;) Не про WatchService, но все же.
О, AsynchronousFileChannel это оно! Работает по тому же принципу что озвученные мной выше решения. Не знал что это есть в nio. Впрочем, автор, судя по всему, тоже не знал и имел ввиду что-то другое, так что профит от этого треда получили все :)

Спасибо :)
Sign up to leave a comment.

Articles