Pull to refresh

Comments 20

Сам тест заключается в том, что каждая корутина читает информацию из файла и записывает её в канал (в случае Go) или в вектор (в случае Rust).

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


Впрочем, содержание теста отличается от описания — корутина в Rust вообще ничего никуда не записывает, она возвращает значение. В таком виде тест имеет смысл: в Go горутина не может вернуть значение иначе чем через канал, так что аналог справедлив, но меня настораживает тот факт, что канал в Go используется всего 1.

Я тоже об этом думал. Однако, мне показалось, что на миллионе каналов программе может стать дурно. К тому же представить реальную задачу, где требовалось бы миллион каналов, мне тяжело. Поэтому я просто выбрал способ, где данные в принципе сохранялись бы, любым доступным способом

Так почему тогда в Go не в слайс "писать"?

По всей видимости мое описание тестов вводит людей в заблуждение. Имелось в виду, что канал использовался для передачи информации от одной горутины к другой, а в Rust асинхронные функции просто возвращали значение. В конечном итоге значения и в Rust и в Go записывались в вектор и массив. Я дополню свою статью

А канал буферизованный? Если нет, то из него могли не успевать читать...

Я обновил статью. Теперь канал в тестах используется буферизованный

Знаете, ваши опасения были не напрасны. Результаты изменились не катастрофически но значимо. Rust начал выглядеть не настолько крутым языком, а скорее как примерно такой же.

Не понял зачем использовать N каналов. Ведь можно сделать один канал с буфером N, чтобы не блочиться :) тогда должно стать ещё быстрее

Я бы закинул PR, но на маке (подозреваю и на линуксе) скрипт run не запускается из-за множества ошибок в путях. Было бы полезно, если бы его можно было ранить везде

Если представить, что каждая корутина - это задача, выделенная под запрос пользователя, то логично ожидать, что и каналов будет много. Так что условие с N каналов мне показалось более реалистичным.

А по поводу закуска везде... Ну, можно, конечно. Но кода на Python в репозитории и так уже больше всего)

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

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

В четвертом тесте дело, скорее всего, в cgo. Компилятор GO генерирует значительное кол-во кода вокруг сишных вызовов, в раст zero cost ffi т.к. abi совместимы.

За пятый тест сказать не могу, но там мы с базой общаемся через сокет, так что ситуация кардинально другая.

Не "за пятый тест", а "о пятом тесте" или "про пятый тест". Учитесь говорить правильно.

В четвертом тесте основные тормоза — это вроде как update. Если не делать одновременный update (в случае sqlite вроде бы так и рекомендуется, по крайне мере с настройками по-умолчанию), то работает в разы быстрее. Что-то типа:
var mu sync.Mutex

func routine(db *sql.DB, age, id int, ch chan<- string) {
	if age%2 == 0 {
		queryUpdate := fmt.Sprintf("UPDATE users SET salary = salary + 1 WHERE id = %d", id)
		mu.Lock()
		db.Exec(queryUpdate)
		mu.Unlock()

Интересное замечание. Я попробую запустить тест с этой правкой

Попробуйте сделать буфер у канала в коде Go. Например:

ch := make(chan float64, n)

Должно стать заметно быстрее на больших объемах :)

В раст реализации попробуйте не ждать в цикле, а использовать join_all

Да, спасибо. Обязательно попробую

Крайне плохая идея использовать асинхронщину в задачах с упором в процессор. Сами разработчики не рекомендуют (секция "When not to use tokio") использовать в подобных задачах tokio, т.к. он спроектирован для решения проблем, завязанных на вводе-выводе. В качестве альтернативы предлагается крейт rayon (который, к слову, тоже использует модель задач, не порождая бесчисленное количество потоков).

Все верно. Первый тест является эмулирующим. И задержка как раз эмулирует ожидание при вводе-выводе. Как и тесты с SQL. Можно представить, что у нас есть веб сервер, где миллион пользователей одновременно пытаются что-то сделать. Так что по мне так tokio здесь очень даже к месту. Если не согласны, распишите, пожалуйста, подробнее почему

Действительно, мне стоило сначала статью прочитать, прежде чем что-то писать. Здесь нет упора в процессор (о которой я думал, читая про конкурентность), а как раз IO-нагрузка. Моя ошибка, прошу прощения.

По поводу памяти: асинхронные задачи в расте представляют собой структуру, в которую была преобразована асинхронная функция (или была явно реализована структура с трейтом Future), в Go же корутины имеют свой собственный контекст (~4.4KiB по словам знакомого разработчика на Go).

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

весьма интересно, спасибо за труд

Было бы интересно узнать разницу с отключенным сборщиком мусора в Go

Sign up to leave a comment.

Articles