W Go konwersja między string a []byte zazwyczaj powoduje alokację nowej pamięci, ponieważ ciągi są niemutowalne (immutable), a złożone bajty są mutowalne (mutable). Wyjątkiem są niektóre wewnętrzne optymalizacje kompilatora (analiza ucieczki), ale nie są one zagwarantowane i zmieniają się między wersjami Go.
Bezpośrednia konwersja:
[]byte(s string): zawsze alokuje nową tablicę bajtów i kopiuje dane.string(b []byte): również zawsze kopiuje dane z tablicy bajtów do nowego ciągu.Zero allocation — podejście, w którym unikamy zbędnych alokacji. W standardowej bibliotece Go istnieje niebezpieczna konwersja przez pakiet unsafe, która pozwala na odniesienie się do tych samych danych bez kopiowania. Można to stosować tylko z pełnym zrozumieniem ryzyk.
import ( "reflect" "unsafe" ) func BytesToString(b []byte) string { return *(*string)(unsafe.Pointer(&b)) } func StringToBytes(s string) []byte { sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) bh := reflect.SliceHeader{ Data: sh.Data, Len: sh.Len, Cap: sh.Len, } return *(*[]byte)(unsafe.Pointer(&bh)) }
W większości przypadków, z wyjątkiem szczególnych sytuacji, lepiej nie używać tych technik — naruszają one bezpieczeństwo danych (można zmienić zawartość ciągu poprzez wspólny złożony bajt, co prowadzi do błędów).
Często pytają: "Czy to prawda, że podczas konwersji string do []byte nie będzie alokacji, jeżeli ciąg jest mały lub stały?"
Poprawna odpowiedź: Nie. Niezależnie od rozmiaru ciągu kompilator zawsze utworzy nowy złożony bajt i skopiuje zawartość. Wyjątki to tylko optymalizacje unsafe, które nie gwarantują bezpieczeństwa i nie są wspierane przez oficjalną dokumentację Go.
Historia
Zespół pisał wysokoobciążoną usługę do analizy logów i ciągle przekształcał napływające ciągi w złożone bajty i odwrotnie do przetwarzania. W szczytowych momentach garbage collector spędzał do 30% czasu procesora na zbieraniu krótkotrwałych kopii. Po profilowaniu okazało się, że każda konwersja string↔[]byte alokowała osobny obszar pamięci. Po wprowadzeniu pul i przeprojektowaniu API udało się znaczną część konwersji usunąć, co zmniejszyło obciążenie GC o połowę.
Historia
Jeden z programistów zoptymalizował pracę z JSON, używając niebezpiecznej konwersji bytes→string aby uniknąć alokacji. Na początku zysk wydajności był zauważalny, ale po miesiącu wystąpiły awarie: jakiś bufor bajtowy był ponownie używany, a ciąg wskazywał na stare zmienione dane. Naprawa była możliwa tylko poprzez powrót do standardowych kopii i przearanżowanie międzyprocesowego API.
Historia
Podczas przesyłania dużych danych binarnych przez sieć zdecydowano się "optymalizować" serializację, używając BytesToString (bez kopiowania). Pewnego razu ciąg, który wysłano, stał się publicznie widoczny, a zawartość złożonego bajtu została natychmiast nadpisana, co doprowadziło do wysyłania śmieci i wycieku prywatnej części danych w logach błędnych pakietów. W rezultacie deduplikacja pamięci zamieniła się w wyciek prywatnych danych!