Как стать автором
Обновить

Комментарии 62

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

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

Но эта проблема спокойно решается в библиотеке, без изменения языка.
Гляньте вот этот тред. Там всё довольно неплохо описано
НЛО прилетело и опубликовало эту надпись здесь
Я что-то слышал про то, что в Java есть зеленые потоки, которые легковесны как горутины. Было бы не плохо, если бы кто-то что-то про это разъяснил.
НЛО прилетело и опубликовало эту надпись здесь
Сейчас пилят библиотеку 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, и результат может быть устаревшим.
Вы уверены, что нужно уделять столько внимания вспомогательной функциональности? Вероятность ошибки ведь очень низкая.
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, но все же.
Строго говоря, в java.nio есть классы для асинхронного (с Java7) и неблокирующего на основе селекторов (c Java 1.4) ввода-вывода. С файлами, правда, нельзя работать с помощью селекторов, а с сокетами — ещё как можно.
О, AsynchronousFileChannel это оно! Работает по тому же принципу что озвученные мной выше решения. Не знал что это есть в nio. Впрочем, автор, судя по всему, тоже не знал и имел ввиду что-то другое, так что профит от этого треда получили все :)

Спасибо :)
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории