Pull to refresh

Comments 21

Например, если мы добавим в структуру мьютекс, то передавать его копии будет уже невозможно

К сожалению возможно. Я все жду когда-же в го добавят конструктор копирования ( МУА-ХА-ХА).

Использование go vet должно быть обязательным требованием для любой кодовой базы, видимо автор подразумевал его по-умолчанию. Благодаря правилу copylocks, можно приготовить zero-sized struct для защиты своих структур от копирования, чтобы не тратить место на целый Mutex.

всегда удивляло как на Go впринципе люди пишут

Да нормально пишут. Меня удивляет как люди на С++ пишут:) Хотя я сам на нем пишу, но то подмножество что я использую - достаточно близко к понятию "си с классами и некоторыми элементами функицонального программирования", т.е. в целом достаточно далеко от современного каноничного С++, где люди вместо того чтобы решать прикладные задачи, решают абстрактные проблемы самого языка средствами метапрограммирования.

Никто не мешает на современном С++ решать задачи, но это не суть

Мне просто интересно как в Go совмещается это:

  • на константу нельзя взять адрес (т.е. константости не существует)

  • UB если многопоточно менять память

Впринципе писать на языке, где постоянно все всё передают по мутабельным поинтерам и отсутствуют абстракции кажется дикостью после даже С

А почему UB? Если использовать мьютексы или rwlock всё вроде нормально работает многопоточно

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

на константу нельзя взять адрес (т.е. константости не существует)

Это почему сделан такой вывод?

Чтобы не бояться nil нужно было вводить в язык нуллабельность (опционалы), сопутствующий синтаксис и операции. Но это усложнение языка, а Go позиционируется как предельно простой:)

Меня после С/С++ удивило, что в Go можно спокойно возвращать адрес локального (стекового) объекта, и все будет работать. Скорее всего компилятор отслеживает такие объекты по тому же принципу что и "замкнутые" переменные и перемещает их в кучу со сборкой мусора.

Скорее всего компилятор отслеживает такие объекты по тому же принципу что и "замкнутые" переменные и перемещает их в кучу со сборкой мусора.

Когда как. Иногда он функции настолько сильно инлайнит, что, если там прям нет работы с адресом, может просто оставить объект на стеке. Поэтому я немного не согласен со статьёй, надо смотреть не бенчмарки от go, а конкретный машинный код на выходе и его уже сравнивать, делать какие-то выводы.

А можете показать пример сильного инлайна?

Насколько возился с эскейп- анализом:

  1. Не инлайнятся функции больше 80 попугаев. Это примерно 4 оператора;

  2. Не инлайнятся defer не зависимо от размера функции.

Поэтому я немного не согласен со статьёй, надо смотреть не бенчмарки от go, а конкретный машинный код на выходе и его уже сравнивать, делать какие-то выводы.

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

Если говорить о hot path, то да, его нужно тщательно оптимизировать уже на конкретных кейсах. Но в большинстве случаев при написании кода существенной разницы между передачей по указателю или передачей по значению не будет - это я и хотел доказать, и тем самым призвать читателей использовать то, что больше подходит по смыслу. Например, указатели могут быть nil, и работать с ними тяжелее из-за одного дополнительного возможного значения.

Я иногда использую указатели потому что у них есть чёткое нулевое значение. Да-да, в Go есть опциональность)

Например, если мы добавим в структуру мьютекс, то передавать его копии будет уже невозможно

Можно не передавать указатель на структуру, а хранить в структуре указатель на мьютекс?

Только вот мьютекс по ссылке надо не забыть явно создавать (что бы панику не словить на его блокировании), тогда как мьютекс заданный в структуре значением вовсе инициализировать не нужно (забыл про него, ну и ок).

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

Спасибо за фидбэк.

Просто я регулярно слышу "надо передавать большие структуры по указателю, потому что так быстрее". И действительно без бенчмарков не всегда понятно, что быстрее: скопировать всю структуру в стеке или выделить её в куче и передавать указатель.

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

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

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

а что будет с бенчами если мы берем структуру и передаем ее в цикле в 5-10-15 разных методов? что тогда будет быстрее? копирование во все эти методы? или указатель во все методы?

Так бенч это и есть - стопятьсот раз передать копией/указателем и померить время и память.
Передать копии в 10 разных функций или 10 раз в одну - суть не меняется.

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

  1. использование nil дает каноничный способ передать "используй дефолт", без необходимости проверки передачи "пустой структуры", то есть `if myParam == nil { myParam = getDefaultMyParam()`, то есть семантика "отсутствия чего-то" в таком виде более четкая, проверять на nil не такая уж большая беда, но да runtime контроля не хватает как в Kotlin, но это не выглядит жуткой проблемой

  2. передача указателя по дефолту дает как раз дисциплину считать, что все read-only, не копии, может быть в share и соответственно приучает более осторожно все писать, то есть чтобы переход "на копию" был более внятен. Понятно это противоречит "чистому функциональному подходу", но прикладная разработка на Go она редко про чистую функциональщину

  3. нет пробелмы мнимой копии, когда привычка передавать по значению прячет нюансы общих буферов слайсов, присутствия интерфейсов среди полей (не известно по ссылке или по значению), вложенных структур тоже где-то по указателю, где-то по значению, то есть DEEP COPY пробелма никуда все равно не девается, но когда все на указателях ты и не ждешь, что что-то копируется каждый раз, к тому же снять shallow copy не проблема `var cpyVal MyType = *myObjRef; cpy := &cpyVal;` где действительно вынь да положь нужна эта копия

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

Sign up to leave a comment.