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

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

Не хватает в бенчмарке результатов apache ab

Ну и, думаю, с прямыми руками те же самые ЯП можно ускорить ещё на десятки процентов, а то и в разы.

"люди пишут небольшие приложения на Java с итоговым весом под 100 Мбайт"

пук в лужу. или саму jvm посчитали? тогда не забудьте ещё ОС посчитать, оно же JVM запускает. в итоге чё там, 2гб против 2.1гб выйдет

"люди пишут небольшие приложения на Java с итоговым весом под 100 Мбайт"

Вроде через GraalVM можно сделать "нативный бинарник", в который нужные куски JVM будут вкомпилены / влинкованы статически. В результате будет меньше.

В самой Java опыта не имею и именно на бекенд разработку, выполнение под JVM и подобное вообще не лезу, конкретно я имел ввиду скомпилированные бинарники из Java кода. Таких на GitHub хватает.

ИМХО не должна весить небольшая консольная утилита под 100 Мбайт, это уже перебор как-то :)

>ИМХО не должна весить небольшая консольная утилита под 100 Мбайт

и зачем такие люди как ты статьи пишут? )

ls -l target/mtLoader-1.0-jar-with-dependencies.jar

3501 Mar 5 21:01 target/mtLoader-1.0-jar-with-dependencies.jar

3501 БАЙТ, Карл, 3501 БАЙТ!

внутри

` final String URL = "http://127.0.0.1:11456/tika"; HttpClient httpClient = HttpClient.newBuilder().build(); IntStream.range(1, 1_000_000).parallel().forEach(num -> {

try {

HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(URL)) .GET() .build(); httpClient.send(request, HttpResponse.BodyHandlers.discarding()); } catch (Exception ee) { ee.printStackTrace(); } }); System.out.println("used memory: " + ((Runtime.getRuntime().totalMemory()-(Runtime.getRuntime().freeMemory())) / 1048576) + " MB"); System.out.println("allocated memory: " + (Runtime.getRuntime().totalMemory() / 1048576) + " MB"); }

`

запуск с лимитом 15МБ

java -Xmx15m -jar target/mtLoader-1.0-jar-with-dependencies.jar

used memory: 8 MB
allocated memory: 16 MB

шах и мат )))

ещё можно с 2017 года резать JRE на модули, гугли jigsaw, оставлю в качестве домашнего задания. Не думаю, что до 5МБ ужмется, но блин насколько это всё далеко от цитируемого бреда.

p.s. 100МБ jar даже очень жирные сервисы не занимают, у которых внутри и spring (30МБ) с вебсервером и IOC контейнером и куча бизнес-фарша.

p.p.s. Про apache ab то что скажем? Насколько помню, он легко 50k rps выжимает на локалхосте.

Сама идея "написать свой бенчмарк" появилась, когда я ещё не нашёл другие удобные утилиты, но а после хотелось самому что-то подобное реализовать.

А почему в бенчмарке одни ошибки?

Requests:            606176
Errors:              603539
Percent of errors:   99.56%

Потому, что он ДДОСил чужой сервер, а тот его блокировал. Кстати, такой тест считается провальным.
Меня в этой истории не удивляет то, что человек просто переписал свою программу не вдаваясь в детали, почему предыдущая версия тормозила и какие у неё были боттлнеки.

Случайно минус нажал. Прошу прощения.

Ничего, я не коллекционирую плюсы-минусы :)

Про ДДОС сервера верно, httpbin мог начать отвечать ошибками при RPS от 1К.
Я переписал приложение не потому, что не хотел чинить версию на Go, а потому, что хотел написать версию на Rust для обучения. Так-то понятно, что и Go версию можно оптимизировать, но размер бинарника вряд ли получится сильно уменьшить.

И в целом, ничего против Go не имею, это сравнение не языков, а двух "сферических коней в вакууме".

Видимо, автор был уверен, что только ошибки и будут, вон, аж u128 для подсчетов использовал :)

Сравнивали с hey?

Глянул в исходник hey, вижу там net/http, это собственно стандартная библиотека про которую писал автор

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

А что за проблема с кроскомпиляцией? Под m1 нет тулчейна?

С M1 не получается скомпилировать под x86 Windows или Linux. Сам особо не вникал, но ни одно решение из интернета не сработало.

Узким местом все равно будет база.

Гошный код не показали, бенчмарк на чужом удаленном сервере (небось еще и по wifi). Даж не знаю, что автор тут тестировал, но точно не языки

Размер бинаря же :)

"Лучше" в плане меньшего веса бинарника

Интересно на кой нужен меньший вес бинарника, с роутера кого-то дудосить или я не в теме. Просто привык иметь дело с продом в несколько десятков терабайт места на ссд, смысл мне за пару мегабайт код преписывать

Как минимум потому что чем меньше бинарь — тем меньше вероятность выпадения «горячих участков» из кеша. Особенно в случае с GC-языками, когда сборщик может запускаться достаточно редко, чтоб быть изгнанным из кеша, но достаточно регулярно чтоб постоянно загружаться обратно.
НЛО прилетело и опубликовало эту надпись здесь

Не то, чтобы нужен, но я изучаю почти параллельно несколько языков и было интересно, на сколько меньше будет бинарник на Rust. Сам Go изначально задуман для бекенда, где размер бинарника не особо важен, а я хотел маленькую утилиту.

Горутина на каждый реквест, действительно, куда ж все время и память уходит :)

Я и сам писал об этом:

[...а также немного сравнения характеристик двух сферических коней в вакууме.]

Этот тест совсем не показательный, просто сравнение двух моих приложений с похожей архитектурой на двух языках. Ну а главным "камнем преткновения" был размер итогового бинарника.

НЛО прилетело и опубликовало эту надпись здесь
Если не требуется отлаживать или анализировать бинарник, то вполне годный компромисс. Или я чего-то не знаю?
НЛО прилетело и опубликовало эту надпись здесь
Почитал статейку по ссылке: как будто go-компилятор не умел (не умеет?) правильно собрать заголовки, из-за чего strip делал как привык, а не как надо в этих странных случаях.
Простите, но это бага компилятора, а не strip. По-хорошему, об этом надо кричать в баг-трекере, потому что некорректные заголовки бинаря — это вообще как так можно накодить компилятор??
НЛО прилетело и опубликовало эту надпись здесь
Так я тоже нигде не утверждал, будто вы сказали что это баг strip.
Я хотел сказать, что обычно люди думают, что если нечто (бинарь) работало до воздействия (strip), но перестало после — значит виновато именно воздействие (и хотя в большинстве случаев это так, в этом конкретном примере виноваты сами бинари). И из вашего
НЕЛЬЗЯ стрипать бинарники на go
можно подумать, что это именно стрипанье их ломает, хотя оно лишь обнажает изначально некорректную структуру бинарей.
А также я хотел сказать, что если корректное стрипанье показывает проблему из-за некорректности структуры (которая, вообще-то, описана задолго до go) — НУЖНО стрипать, чтобы заваливать разработчиков go баг-репортами. Если сегодня некорректная структура приводит к проблемам после strip, то кто знает, что может сломаться завтра? Стандарты и спецификации (в данном случае ELF) не просто так существуют.

Не натыкался на этот момент ранее, спасибо. Да и в целом оно не много веса отнимает, так что я его и не использовал особо, но указать стоило для понимания.

Кто ж нам запретит?

С прошлого лета конструктор Mutex может быть static, поэтому уже можно создавать глобальные переменные и обойтись без lazy static.

Стесняюсь спросить, зачем здесь асинхронщина с tokio? Если я правильно понял сценарий, то выполнить все запросы в цикле последовательно vs распараллелить никак не повлияет на результат теста, тк меряется сумма времён обработки запросов.

Tokio использовал не из необходимости, а потому, что в Go приложении во всю использовалить корутины (горутины) и здесь хотелось чего-то похожего. К тому же это максимальное простое решение проблемы для новичка вроде меня. Т.е. мы просто на кажды запрос создаём отдельную корутину, которая и занимается обработкой своего запроса.

Особой разницы не заметил, да бинарник в 3 траза больше, потребление памяти такое же, а скорость на 10% больше. Не, в качестве изучения Rust норм. Короче паритет.

Если вы считаете только max/min/average зачем вы храните массив со всеми данными (в хипе! С реалокациями!) , вместо того чтобы хранить четыре переменные u64?

Вы каждом цикле клонируете строку ( опять же - реаллокация в хипе!) вместо того чтобы завернуть строку в Arc и получить почти бесплатный clone()?

Какой рантайм токио вы использовали? Пробовали ли вы запустить это все в one thread runtime? Как писали выше у вас нет никакой конкурентности, вам не нужно запускать треды на каждом ядре.

Спасибо за дельный совет, учту. Изначально хотел просто сделать идиоматически схожее приложение с Go версией.Под "рантаймом" подразумевал, скорее, некую схожесть с удобными корутинами в Go, ведь Tokio позволяет создавать миллионы тасков (task) и работать с ними также, как и в Go, что очень легко воспринимается при переходе между языками.

Касательно

Какой рантайм токио вы использовали? Пробовали ли вы запустить это все в one thread runtime? Как писали выше у вас нет никакой конкурентности, вам не нужно запускать треды на каждом ядре.

Я, если честно, не совсем понял. Как я уже указывал в дисклеймере в начале статьи - я и сам новичёк в Rust и многого не знаю, но спасибо за упоминание, поищу информацию об этом.

Плюсы:
Отсутствие GC (Сборщика мусора)
Кто-то может пояснить, почему это «плюс» языка?

Все просто. Сборщик мусора занимает цпу, память, лишает программиста бдительности и сам по себе предоставляет рантайм который определяет язык, его модель и опять же влияет на перфоманс. О том что он медленный и неэффективный в средних/больших проектах думаю даже и говорить не нужно.

Отсутствие гк в связке с идеей владения в контексте раста - даёт не только буст по перфомансу но так же лишает части проблем, как например гонка состояний и заставляет программиста думать над своим кодом. Понятно, что работает не всегда и не везде, но процент кода на порядок выше чем у скажем какого нибудь питона или го.

В гарантиях там есть довольно четкие грани - по поводу "гонки состояний" можно уточнить, что Rust без unsafe исключает data race, который UB, но позволяет создавать race condition, которые логические ошибки.

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

GC - это решение проблем с ручным управлением памятью за счет накладных расходов в рантайме. Rust тоже решает проблемы с памятью, но позволяет получить определенные гарантии статически.

Поскольку не приведен код на go, хочу уточнить у Вас один вопрос, как изменятся результаты если выключить gc.

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

Большое спасибо за статью! А почему Arc<Mutex<u128>> вместо atomic'а?

Спасибо за отзыв!

Я новичок в Rust и использую в первую очередь то, до чего легче всего додуматься :)
А об Arc я часто слышал в гайдах / лекциях, потому и использовал его.

На самом деле сейчас переписываю код после прочтения комментариев под статьёй и скоро изменю её, в новой, предположительно, не будет Arc совсем, просто Mutex, т.к. это глобальная переменная и нет нужны считать количество ссылок. Но пока не точно :)

Если я правильно понял по результатам, то переписывание не стоило переписывания, Go показал себя вполне достойно.

Интересно узнать сколько ушло времени и сил на отладку кода на Rust. У rust всётаки порог вхождения повыше чем в go. Соответственно и отлаживать должно быть тяжелее.

Сам Rust я, на момент написания, лениво изучал уже как пару месяцев. А на само прилодежение ушло пол дня, на первую версию. В основном спотыкался об особенности языка и Tokio, как те же sleep и interval в токио, котя в Go хватало лишь метода Sleep

Статья немного перекликается с работой, которую я проводил месяц назад. Скажу сразу, что для меня это был рабочий проект, так что он под NDA и исходники не могу выложить. Но у меня тоже было сравнение Go и Rust (и другие решения включая чистый Си), но только в совершенно другом масштабе: мне нужно было нагрузить наши железки высокоуровневым (высшие слои OSI) трафиком под завязку. iperf-тесты уже были, но нам нужен был реалистичный трафик.

Мои результаты на localhost на моем рабочем linux ноутбуке (задейственны все ядра CPU, никаких мьютексов и других пожирателей CPU, статистика собиралась lock-free алгоритмами, сокеты с TCP_NODELAY):

  • Go - 400 тыс запросов секунду

  • Tokio/hyper - 300 тыс запросов в секунду (да, у меня эта пара оказалась медленнее, но у меня была немного другая методика тестирования)

  • Чистый epoll (rust и C показали примерно одинаковые результаты) - 600 тыс запросов в секунду

  • io_uring (пробовал только Rust, программировать на Си все-равно не собирался), тестировал пакеты glommio и monoio - 800 тыс. Остановился на последнем потому что есть fallback на epoll (старые linux) и kqueue (macos).

Ещё хотел бы отметить про hyper - я от него в конце отказался полностью и написал специализированный генератор и парсер HTTP, благо протокол простой. Flamegraph показал что hyper тратит слишком много времени CPU, и хотя у него могут быть более корректные по стандарту парсеры, для benchmark-а он стал бутылочным горлышком.

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

Публикации

Истории