Comments 21
На Java писали или на C# ? Горутины ничего общего не имеют с паттернами многопоточки из языков с серьёзными тредами. Пока горутин меньше 100000 - просто запускайте ещё одну, главное убедитесь, что она завершится так, как ожидается. Если горутин меньше 1000000, это всё ещё окей, но надо иметь в виду нагрузку на железо. Горутины и каналы нужны не для того, чтобы очевидно тяжёлое или медленное считать в несколько потоков, а для того чтобы максимально использовать то, что во всех(!) современных процессорах больше 1 ядра и самое главное - делать это легко! Все (почти) части программы исполняются на максимуме ядер, даже места, которые кажутся простыми и быстрыми, но на практике бывают бутылочным горлышком. Рантайм Go сам создаст столько серьёзных потоков в ОС, сколько имеет смысла, а ваш расчёт не учитывает того, что main - отдельная горутина и многие библиотеки активно используют многопоточность под капотом (но это окей, как я уже говорил).
Кажецца вы не поняли посыл. Я согласен со всем, чт вы сказали, но не понял с какой из моих идей вы спорите =)
Если что, я тогда поясню...
Статья о том, что есть в основном совсем немного общих случаев, когда нам нужна многопоточность.
Первый, самый основной, наверно, - это когда у вас есть несколько акторов, которые просто заставляют ваше приложение создавать новый поток выполнения. К примеру обработка HTTP запросов. Так вот эта на первый взгляд довольно простая тема, это не про эту статью.
Второй случай - это когда вам нужно в процессе какой то обработки взять да и сходить по сети в разные места или в одно, но много раз. Так вот этот случай хорошо параллелится, но тут обязательно нужно ограничение, просто бросить в чужой сервис 100 тысяч запросов - это жестоко и непрофессионально. Статья объясняет, что в таких случаях ограничивать кол-во потоков числом ядер процессора - это ошибка.
Третий случай - это локальные вычисления внутри одной машины. Добавил я эту тему сюда чисто потому, что вышло интересно. Вот тут нужно ограничивать кол-во потоков числом ядер, это факт. Попробуйте ка запустить 100 тысяч параллельных вычислений хэша. Ваш процессор потонет в переключениях контекста и вымываниях кеша. По крайней мере, мой ПК так и не смог справиться с той же самой задачей за разумный срок.
Так что ваше "если горутин меньше миллиона, то это все еще окей" - это сомнительное заявление.
или на C# ?
имя Ибрагим await вам ни о чём не говорит?
Я недавно бился с подобной задачей, делил на некоторые batch данные чтобы отдельно посчитать. В целом пришел к тому же о чем статья. Все очень красиво и похоже на некий маркетинг буллшит – wg и смотрите, многопоточность.
На деле действительно все конструкции wg – история про большую стоимость вызова и надо думать где применять. Т.е разработчик должен заранее понимать что операции которые он "паралелит" – они тяжелые и занимают время. И, безусловно, если заранее неопределенно каков массив данных входит, то надо разрабатывать объвязку которая будет от количества данных запускать только необходимое количество групп.
Еще один вариант остается – через ожидающие каналы. Для тех кто не понимает, поясню – делается условно срез куда создается определенное количество каналов с обрабатывающими гоурутинами....и далее закидываются данные в более свободный канал. В теории эта схема легкая, но сама "обвязка" для выбора "более свободного канала" довольно сложная. Я делал и такое; И могу сказать работает это чуть быстрее (в моем случае это несколько тысяч наносекунд по сравнению с wg sync) бонусом дает более плавную нагрузку т.к заранее определено сколько потоков работает в блоке. Но опять же обвязка "управления" обошлась в 200-300 строк кода.
Подскажите. что это за термин такой "более свободный канал"?
Самое главное, как вы определяете свободность канала?
Буф канал создаем и по счетчику свободного буфера работаем.
Признаюсь, что отстал от жизни
Покажите, как определить в канале свободное место, пожалуйста
Из доки Golang: len(Ch)
Либо когда делаете управляющую функцию, лепить свой счетчик
точно. вот я балда. ни разу за все время разработки не видел такого и даже не было необходимости в этом
а знаете, что интересно?
а то, что пока вы делаете cap(ch) - len(ch) - там уже все поменяется и тот канал, который вы определили, как свободный, на самом деле будет уже под завязку
такова конкурентность
А почему бы вам не сделать всего один канал и несколько читающих воркеров? зачем вам несколько каналов, я право не понимаю
какой то не гошный подход
а то, что пока вы делаете cap(ch) - len(ch) - там уже все поменяется и тот канал, который вы определили, как свободный, на самом деле будет уже под завязку
Это становится не таким критичным если буффер указан с запасом. Где-то залетит чуть больше, но в целом кривая нагрузки будет более правильная
А почему бы вам не сделать всего один канал и несколько читающих воркеров? зачем вам несколько каналов, я право не понимаю
какой то не гошный подход
Если уж на то пошло там можно по всякому реализовать. Мне подошел способ с большим количеством каналов в срезе
Все таки я за то чтобы разработчик выбирал тот или иной способ согласно задаче, а не на основании "тру или не тру" подход.
В своем проекте я работаю над оптимизацией постоянно, для меня очень важна latency каждого отдельного блока. Притом координаты измерения – сотни наносекунд. И если какой-то способ не совсем тру, но даст прирост на тестах 5 наносекунд по сравнению с "тру" подходом – буду пользоваться им
Более показательный пример, подключение "Сишных" модулей в Golang...это воротит многих кто настоящий гошник, но я отлично использую этот функционал для бинарных деревьев. Бинарные деревья – узкое место в Golang и чтобы от него избавиться я их реализовал на "Сишной" библиотеки, оттестировал и получил разницу в скорости почти в 10 раз лучше.
А за счёт чего в го хуже именно деревья? Указатели и gc?
Они тормозят, а вот из-за чего - не знаю. Сам поиск слишком долгий при сравнении с тем же самом на C++
Если ваша реализация деревьев на ГО тормозит, значит вы неправильно написали =P
Вы наверно использовали ссылочные типы (указателями вощем), а надо было построить дерево на слайсе.
Ловите вот пример реализации такого дерева на ГО https://github.com/iv-menshenin/tree
Бэнчмарков не делал и с C++ не сравнивал
Спасибо, будет время и на досуге посмотрю. Вижу что есть отличия от классики, интерфейсы добавлены. Но читая по "диагонали" не нашел того места которое должно реально увеличить скорость.
Разница скорости Go vs C++ была ~10 раз на моем коде. Т.е условные 60 против 6 секунд.
Хочу еще сказать что с Golang довольно долго бьюсь за производительность того или иного. Для себя принял правду:
Что слезть с него – очень сложно. В любом случае каналы/гоурутины и прочие мелкие радости многопоточности – наркотик с которого слезть очень сложно. И даже дело не в том что это просто, а в надежности решения. Если взять один блок моей разработки и попытаться переписать на C++ то это займет очень большое количество времени. И основное время будет потрачено на борьбу с утечками памяти следующая из многопоточности
Но другая правда, что за удобство платим производительностью, притом есть вещи которые работают на космических скоростях и ничуть не уступают C++, а есть вещи типа деревьев или некоторых математических операций которые тебя вводят в некий ступор c немым вопросом: "это вообще почему?". Но опять же хочу отметить что когда я говорю о скорости, но мы говорим о цифрах в пределах наносекунд, т.е это все равно космические скорости особенно если сравнивать со стеком языков типа ноды или питона.
Пока формула успеха моя личная – там где узко, меняем на C++ модуль и не паримся. И рад что такая возможность вообще есть.
а профайлером не стали смотреть? получилась бы интересная статья. я думаю что скорее всего указатели и write barriers влияют.
Где-то залетит чуть больше, но в целом кривая нагрузки будет более правильная
КМК вы пытаетесь делать работу за планировщика. Он под капотом и так не плохо справляется с эвакуацией. Т.е. перекидывает задачи между освободившимися ядрами.
Но опять же обвязка "управления" обошлась в 200-300 строк кода.
А можно не велосипедить и взять стороннюю библиотеку, например https://github.com/alitto/pond
А потом вам нужно на пару нулей в хеше больше, и вот вы уже лезете в C вместе с CUDA.
Зачем мне нули и зачем куда?
Статья же не об этом. А о том, что если вы ограничиваете кол-во запущенных горутин, то этот как минимум должно быть осознанно и обоснованно.
Но так вот в тему. GO тоже умеет в CUDA
В статье подбирают значение, чтобы его хеш оканчивался на определенное количество нулей. Если вам нужен более конкретный хеш, где в конце не, к примеру, 5 нулей а 7, то сложность задачи возрастает экспоненциально. И вот возможностей Go уже не хватает. Следующий уровень-расчет на видеокарте и использование Cuda.
Здоровая конкуренция в GO. Главное не перехитрить самого себя