Pull to refresh

Comments 27

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


Либо попал на эффект "часто обращаешь внимание на то, о чём сейчас думаешь", либо на Хабре можно объявлять "месяц статей по Go" ;)

Согласен, давно такого не было, а вот последний период активно начали писать. Реально "месяц статей по Go". Хочется только поблагодарить авторов.

Вы изучаете кор-либ уже целый месяц? Долго что-то.

Нет, я уже месяц (на самом деле уже почти два) изучаю сам язык. Изучаю привычным способом — беру интересную и актуальную для меня задачу и начинаю её решать на новом языке.


И нередко обнаруживаю совершенно неочевидные для меня косяки. К примеру, долго не мог получить загрузку CPU выше 60-70%, а потом благодаря одному ну совсем очевидному комментарию получил +30% производительности и нагрузку на проц "в полочку".

На Go пишу недавно. Может, не понимаю чего… Объясните, пожалуйста, в чем смысл такого теста?


Я бы понял, если бы сравнивали передачу функции в качестве параметра
а) указателя на одну и ту же структуру
с б) передачей структуры целиком.


Но здесь вроде как по-другому: каждый раз в цикле создаётся новая структура и предаётся указатель на неё.


Смысл такого действия мне понять сложно, может, я неправильно понимаю происходящее?

Тут смысл в том что автор хотел показать как на производительность влияет ескейп данных в кучу (отсюда и тест который выделяет в куче память b.N раз), но может создаться впечатление что на производительность влияют именно указатели (которые являются только одним из условий перемещения в кучу).
Вот именно — название статьи похоже на заголовок желтушной газетки.

Тоже ожидал другое содержимое статьи, для совсем новичков. А тут скорее статья про аллокацию и деаллокацию...

А скажите пожалуйста, неужели на хабре найдется хоть один человек, который НЕ ЗНАЕТ, чем и как отличаются передача параметров по значнию и по ссылке?

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

Чисто теоретически это понятно.


Но, опять же, чисто теоретически — могло бы, например, быть, что при передаче по значению выделяется сплошной кусок памяти (сплошной блок адресов), а при передаче по ссылке — создаётся какая-то фрагментированная "теневая копия" в свободных блоках. Мало ли, как это реализовано… я вот какого-то такого подвоха ожидал что для экономии памяти тут что-то такое хитрое.

Вот только Go нету ссылок, только передача по значению.

BenchmarkMemoryStack(b *testing.B)
А это что?

Указатель, который передается по значению.

Везде указатель передается по-значению (по другому никак). Передается адрес в памяти.
Это и называют передачей по-ссылке.
Короч я всё напутал) Да, вы правы. Это указатель.
Заметил, много где в статьях про го, авторы считают ссылки и указатели как одно и тоже. Отсюда и путаница.
Круто, что для понимания того, что куча с gc работает медленнее чем стек, надо бенчмарки писать :)

Название статьи не отражает сути проблемы. Другая важная часть, которая напрямую относится к проблеме — escape analysis — вообще не затронута.

Тем, кто не в курсе таких проблем статья и так не поможет(потому что разница между стеком и хипом это довольно фундаментальная вещь, и скорее всего там кроме незнания кучи еще миллион пробелов в других областях), а для тех кто в курсе дела — просто смешная.

Она там же, где и в оригинальной статье, в выводах.

а кто знает как делать такую трассировку go?
Еще можно почитать статью go tool trace от Will Sewell. Этот материал немного свежее.
В Go работа с памятью весьма тонка́, и эта тонкость является обратной стороной простоты. Даже и наоборот — для достижения максимальной производительности Go весьма непрост, ибо нужно учитывать скрытые нюансы, которые вроде как и не являются частью языка.

Немного переписав тест BenchmarkMemoryHeap получаем ту же производительность, что и для BenchmarkMemoryStack:

func BenchmarkMemoryHeap2(b *testing.B) {

	f, err := os.Create("heap.out")
	if err != nil {
		panic(err)
	}
	defer f.Close()

	err = trace.Start(f)
	if err != nil {
		panic(err)
	}

	for i := 0; i < b.N; i++ {
		s := byPointer()
		if s.a != 1 {
			log.Fatalln("a!=1")
		}
	}

	trace.Stop()

	b.StopTimer()

}


BenchmarkMemoryStack-4   	159990848	         7.48 ns/op	       0 B/op	       0 allocs/op
BenchmarkMemoryHeap-4    	19594401	        61.1 ns/op	      96 B/op	       1 allocs/op
BenchmarkMemoryHeap2-4   	164374148	         7.31 ns/op	       0 B/op	       0 allocs/op


Даже стабильно немного быстрее. То ли ил опускается, то ли вода поднимается, то ли кэш процессора прогревается…

Обсуждаемый вопрос, соответственно, несколько «подвисает»…
Так а почему в последнем примере ситуация совершенно другая? Почему вызов метода и передачу аргументов в функцию лучше делать через указатель?
Кстати, в комментариях оригинальной статьи этот вопрос также задавался. Vincent Blanchon, автор статьи, ответил на него примерно так:
Во втором примере мы больше не создаем структуры в стеке/куче. Мы только отслеживаем стоимость использования указателя/копии в качестве параметра. При использовании указателя Go просто делиться адресом на структуру. При копировании копируется вся структура для использования в качестве параметра, что медленнее.

Данный вид бенчмаркинга — в чистом виде лукавство. Просто удивлён даже, насколько это лукаво и ужасно.


  1. Считаю, что некорректно оставлять создание объекта (не важно каким способом) внутри измеряемого пространства. Поскольку в любом случае объект будет создан хотя бы один раз и ресурсы будут потрачены неизбежно и причём одинаковые.
  2. При подобном подходе в написании бенчмарка вполне естественно, что возврат объекта в виде указателя будет нести с собой бОльший оверхед за счёт gc, это ясно как дважды два, зачем это делать — непонятно, и что таким образом хотелось продемонстрировать тоже не ясно.
  3. Самое главное. Если следовать заданному в статье введению, то речь идёт именно о совместном использовании. А если говорить о совместном использовании, то надо говорить о передаче объекта в произвольную функцию. Здесь то и начинается самое важное. Если передавать объект по значению, то при данной операции неизбежно будет выполнена новая аллокация копии объекта целиком. Если объект состоит из двух полей int, то здесь и говорить не о чем, а теперь подумайте если объект содержит в одном из полей массив на 100 000 элементов. Есть смысл сравнивать аллокацию такого нового объекта с аллокацией переменной под указатель и временем для последующего сбора gc? Вопрос риторический.
  4. Прочитав эту статью меня больше всего насторожило, что ни в одном комментарии никто не обратил на это внимание. Главное зло статьи в том, что у начинающих разработчиков может создаться иллюзия того, что не стоит заморачиваться и использовать указатели… «человек вон доказал бенчмарками, что разницы нет!». А доказательство это — чистой воды лукавство и ввод в заблуждение. Измените 3 строки в тесте и вы увидите реальное положение дел.
  5. Данная статья годна только для ответа на вопрос «стоит ли создавать объект в виде указателя при инициализации, не планируя его совместное использование»… то есть, вопрос сам по себе довольно утопичен.

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

Sign up to leave a comment.

Articles