Wątek przeniesiony 2021-09-16 09:49 z Inne języki programowania przez cerrato.

go, zwracać wartość czy pointer

0

Siemano,

piszę taki programik w go i zastanawiam się, czy lepiej żeby funkcja zwracała wartość czy pointer. W ogóle zastanawia jaka jest najlepsza praktyka odnośnie kiedy zwracać pointer, a kiedy value.
A więc jest tak, mam strukture, przykładowo

type Person struct {
	imie, nazwisko string
}

dalej funkcje która ją tworzy z podstawowymi danymi

func makePerson() *Person {
	p := Person{"marian", "kowalski"}
	return &p
}

i teraz mogę ją zwrócić jak wyżej, albo zwrócić po prostu Person. Kolejne funkcje ją modyfikują np.

func changePersonName(p *Person) {
	p.imie = "Janusz"
}

func changePersonSurname(p *Person) {
	p.nazwisko = "Nowak"
}

a ostatnia funkcja formatuje te dane i je wypisuje np.

func printPerson(p *Person) {
	fmt.Println("Imie " + p.imie)
	fmt.Println("Nazwisko " + p.nazwisko)
}

no i dobra, wszystko działa super, ale równie dobrze zamiast *Person w pierwszej funkcji, mógłbym zwrócić po prostu Person, a do funkcji modyfikujących przekazywać po prostu &p
Uznałem jednak, że skoro struktura będzie kilka razy modyfikowana, lepie zwrócić od razu wskaźnik, ale czy takie podejście jest dobre? Bo w ostatni funkcji ze struktury pobierane są tylko dane, a przykładowo mogłaby zostać tam omyłkowo zmieniona. Natomiast zwracając wartość, zachodzi potrzeba przekazywania za każdym razem &, co może jest mniej ergonomiczne, ale za to, wydaje mi się, bezpieczniejsze. I jak to wygląda pod względem wydajności? Z tego co wiem, to nie zawsze zwracanie pointera jest wydajniejsze.

0

Natomiast zwracając wartość, zachodzi potrzeba przekazywania za każdym razem &, co może jest mniej ergonomiczne, ale za to, wydaje mi się, bezpieczniejsze. I jak to wygląda pod względem wydajności? Z tego co wiem, to nie zawsze zwracanie pointera jest wydajniejsze.

Na czym polega to bezpieczeństwo, bo nie rozumiem? Chodzi o to, że programista nie zdziwi się, że funkcja modyfikuje wartość, bo musi jawnie zmienić typ na wskaźnik zanim wywoła? Jak dla mnie to pierdoła i zwracałbym przez wskaźnik, bo w go jest to wygodniejsze jeżeli wiele metod akceptuje wskaźnik. Jedynie przekazywałbym (nie zwracał) przez wartość, o ile struktury są małe.

I jak to wygląda pod względem wydajności? Z tego co wiem, to nie zawsze zwracanie pointera jest wydajniejsze.

Może dla typów podstawowych. Poza tym ciężko mi sobie wyobrazić kiedy miałoby być nieefektywne.

0

mistrzem Go nie jestem, ale bez względu na język zazwyczaj mutable state wychodzi słabo więc zwracałbym value i operował na kopiach -> modyfikacja obiektu tworzy nową kopię

chyba, że te metody sa na tym structcie (modyfikują "this"), to wtedy jeszcze

0

Generalnie, ja bym polecał robić tak:

  • zawsze zwracał przez wartość
  • chyba, że chcesz, aby funkcje mogły modyfikować (tak jak jest to w Twoim przypadku).

Operowanie na kopiach jest wygodniejsze i bezpieczniejsze jeśli chodzi o operacje współbieżne. Przekazując przez wskaźnik musisz pomyśleć co się stanie jakaś inne goroutine zacznie przy tym obiekcie modyfikować. To się nazywa race condition.

Kiedyś napisałem post, który trochę zahacza o Twoje pytanie https://developer20.com/pointer-and-value-semantics-in-go/

Można się ustrzec konieczności modyfikowania, jeśli masz obiekty typu value object - wtedy przy każdej modyfikacji po prostu tworzysz nowy obiekt. Musisz się zastanowić, czy jak zedytujęsz ten obiekt to:

  • czy w innych miejscach ta zmiana też powinna być widoczna?
  • czy w po edycji to nadal jest ten sam obiekt?

Książkowym i dobrym przykładem jest obiekt Money. Jak masz Money(10, "PLN"), to czy jak chcesz dodać 2 złote, to powinieneś stworzyć nowy obiekt typu Money czy też zedytować istniejący?
W Twoim przypadku, jak zedytujesz obiekt Person.surname, to czy nadal jest to ta sama osoba? A co z innymi polami? :P Bo np do ID byś pewnie podchodził inaczej.

0

Wskaźnik zwraca się wtedy, gdy to co go otrzymuje nie ma być właścicielem obiektu. Wartość zwraca się, kiedy oddaje się obiekt i to co go oddaje go już nie potrzebuje. Innymi słowy, jeśli widzę metodę zwracającą wskaźnik, to zapala mi się czerwona lampka, że obiekt może być współdzielony i że tak naprawdę nie dostałem go na wyłączność, a jedynie został mi pożyczony na chwilę. Zwracanie przez wartość jest zdecydowanie bezpieczniejsze, zwłaszcza w języku takim jak Go gdzie kompilator nie kontroluje aliasingu wskaźników. W Javie to już w ogóle masakra, bo tam nie można przez wartość zwracać i często bez przeczytania pokaźnej ilości kodu nie wiadomo "kto jeszcze ma referencję do tego obiektu".

Co do tego że wskaźniki nie zawsze są szybsze - to dotyczy złożonych struktur danych. Płaska struktura zajmująca jeden ciągły obszar pamięci jest przyjaźniejsza procesorowi niż struktura w postaci drzewa (a może nawet grafu) małych struktur powiązanych wskaźnikami.

Co do tego czy alokacja przez malloc czy alokacja w językach z GC jest szybsza, to zdania są mocno podzielone. Z moich wstępnych eksperymentów wynika że GC wygrywa na szybkość alokacji tylko przy absurdalnie wysokich limitach na wielkość sterty i przy braku ograniczeń na czas pauz. Jak dorzucić wymaganie że sterta ma nigdy nie być większa niż 120% używanego zbioru danych oraz jakieś sensowne pauzy poniżej 0.1 ms, to GC ma wydajność taką jak moja córka, jak jej każę posprzątać w kuchni.

Dlatego też preferowałbym przekazywanie przez wartość, bo kopiowanie w obrębie stosu jest niesamowicie szybkie i o ile obiekt nie jest jakiś bardzo opasły (poniżej 128B) to nie potrafię sobie wyobrazić przypadku żeby alokacja na stercie i zwrócenie wskaźnika było szybsze.

1 użytkowników online, w tym zalogowanych: 0, gości: 1