欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入理解Go語言u(píng)nsafe包

 更新時(shí)間:2025年08月12日 10:59:37   作者:哈基咩  
Go語言通過類型和內(nèi)存安全設(shè)計(jì)保障可靠性,但unsafe包允許底層內(nèi)存操作以實(shí)現(xiàn)性能優(yōu)化與特殊交互,適用于結(jié)構(gòu)體字段訪問、零拷貝轉(zhuǎn)換等場景,感興趣的可以了解一下

1.引言

Go語言核心設(shè)計(jì)哲學(xué)

Go語言以簡潔、高效、并發(fā)特性著稱,強(qiáng)調(diào)類型安全和內(nèi)存安全,通過自動(dòng)垃圾回收和嚴(yán)格類型系統(tǒng)降低內(nèi)存錯(cuò)誤風(fēng)險(xiǎn),為開發(fā)者提供可靠編程環(huán)境。

unsafe包引入原因

在與底層硬件交互、極致性能優(yōu)化等特殊場景下,Go的嚴(yán)格類型系統(tǒng)可能受限。unsafe包應(yīng)運(yùn)而生,允許繞過類型安全檢查,直接操作內(nèi)存,但使用風(fēng)險(xiǎn)較高。

Go語言特性與unsafe包背景

為什么要有unsafe指針?

unsafe.Pointer 存在的根本原因是為了突破 Go 語言嚴(yán)格的類型安全限制和內(nèi)存管理限制。直接與底層內(nèi)存、硬件或外部系統(tǒng)(如 C 庫)進(jìn)行高性能或特殊交互的場景中,提供必要的工具

unsafe指針與普通指針的區(qū)別

2.unsafe包的由來與核心概念

Go語言類型安全與內(nèi)存管理機(jī)制

Go的類型特性:

Go通過限制指針使用、禁止直接指針?biāo)阈g(shù)和不同類型指針轉(zhuǎn)換,確保內(nèi)存訪問合法性,防止懸空指針、緩沖區(qū)溢出等問題,保障程序內(nèi)存安全。 特點(diǎn):編譯時(shí)運(yùn)行

內(nèi)存管理機(jī)制:

Go引入垃圾回收機(jī)制,自動(dòng)管理內(nèi)存分配和回收,避免C/C++中常見的內(nèi)存管理復(fù)雜性和安全漏洞,簡化系統(tǒng)編程。

unsafe包的誕生背景:

Go的類型安全雖有優(yōu)勢(shì),但在特定場景下帶來性能或功能挑戰(zhàn)。為解決這些問題,unsafe包提供“逃生艙”機(jī)制,允許開發(fā)者繞過類型和內(nèi)存安全限制。

unsafe包的核心類型與函數(shù)

unsafe.Pointer

源碼實(shí)現(xiàn)

type ArbitraryType int

type Pointer *ArbitraryType

 源碼注釋:

 unsafe.Pointer是特別定義的一種指針類型(譯注:類似C語言中的void*類型的指針),它可以包含任意類型變量的地址.

它代表一個(gè)指向任意類型的指針 ,可以指向任何數(shù)據(jù)類型,并且不攜帶任何類型信息 。

unsafe.Sizeof

unsafe.Sizeof函數(shù)返回操作數(shù)在內(nèi)存中的字節(jié)大小,參數(shù)可以是任意類型的表達(dá)式,但是它并不會(huì)對(duì)表達(dá)式進(jìn)行求值。一個(gè)Sizeof函數(shù)調(diào)用是一個(gè)對(duì)應(yīng)uintptr類型的常量表達(dá)式,因此返回的結(jié)果可以用作數(shù)組類型的長度大小,或者用作計(jì)算其他的常量。

 Sizeof函數(shù)返回的大小只包括數(shù)據(jù)結(jié)構(gòu)中固定的部分,例如字符串對(duì)應(yīng)結(jié)構(gòu)體中的指針和字符串長度部分,但是并不包含指針指向的字符串的內(nèi)容。

unsafe.Alignof(expression)

unsafe.Alignof 函數(shù)返回對(duì)應(yīng)參數(shù)的類型需要對(duì)齊的倍數(shù)。和 Sizeof 類似, Alignof 也是返回一個(gè)常量表達(dá)式,對(duì)應(yīng)一個(gè)常量。

內(nèi)存對(duì)齊

什么是內(nèi)存對(duì)齊呢?

內(nèi)存對(duì)齊就是指數(shù)據(jù)在內(nèi)存中的起始地址必須是某個(gè)特定數(shù)字(對(duì)齊值)的倍數(shù)。這個(gè)“特定數(shù)字”通常是 2 的冪次方,比如 1、2、4、8、16 字節(jié)。

為什么需要內(nèi)存對(duì)齊呢?

  • CPU 訪問效率: CPU 并不是一個(gè)字節(jié)一個(gè)字節(jié)地從內(nèi)存中讀取數(shù)據(jù)。它通常會(huì)以為單位(比如 4 字節(jié)、8 字節(jié)、16 字節(jié))進(jìn)行批量讀取。如果一個(gè)數(shù)據(jù)類型(例如一個(gè) 8 字節(jié)的 int64)的起始地址不是其字長的倍數(shù),那么 CPU 可能需要:

    • 進(jìn)行多次內(nèi)存訪問(比如一次讀取前半部分,另一次讀取后半部分)。
    • 或者進(jìn)行額外的位移操作來提取所需的數(shù)據(jù)。 這些都會(huì)增加 CPU 的負(fù)擔(dān),降低程序運(yùn)行速度。如果數(shù)據(jù)是對(duì)齊的,CPU 就能在一個(gè)內(nèi)存周期內(nèi)高效地讀取整個(gè)數(shù)據(jù)。
  • 緩存優(yōu)化: CPU 有高速緩存(Cache),它一次性會(huì)加載一塊內(nèi)存數(shù)據(jù)到緩存中(稱為緩存行)。如果數(shù)據(jù)對(duì)齊,并且能完整地放入一個(gè)或幾個(gè)緩存行中,就能提高緩存命中率,進(jìn)一步提升性能。

有內(nèi)存對(duì)齊,就肯定要有內(nèi)存對(duì)齊規(guī)則

  • 每個(gè)數(shù)據(jù)類型都有一個(gè)默認(rèn)的對(duì)齊值。

    • 通常,一個(gè)基本數(shù)據(jù)類型的對(duì)齊值等于它在內(nèi)存中占用的字節(jié)數(shù)。

      • bool、byte:1 字節(jié)對(duì)齊
      • int16:2 字節(jié)對(duì)齊
      • int32、float32:4 字節(jié)對(duì)齊
      • int64、float64、指針、string(頭部)、slice(頭部)、interface(頭部):8 字節(jié)對(duì)齊(在 64 位系統(tǒng)上)
  • 在 Go 語言中,可以通過 unsafe.Alignof() 函數(shù)來查看任何變量的對(duì)齊值。

  • 結(jié)構(gòu)體(Struct)的對(duì)齊值。

    • 整個(gè)結(jié)構(gòu)體的對(duì)齊值是其所有字段中最大那個(gè)字段的對(duì)齊值。
  • 填充(Padding)字節(jié)。

    • 為了滿足對(duì)齊要求,編譯器會(huì)在結(jié)構(gòu)體字段之間以及結(jié)構(gòu)體末尾插入額外的填充(Padding)字節(jié)。這些填充字節(jié)不存儲(chǔ)任何實(shí)際數(shù)據(jù),只是為了確保下一個(gè)字段(或下一個(gè)結(jié)構(gòu)體實(shí)例)能夠從正確的對(duì)齊地址開始。
    • 你可以通過 unsafe.Sizeof() 來查看結(jié)構(gòu)體的實(shí)際大小,這個(gè)大小包含了填充字節(jié)。
  • 結(jié)構(gòu)體總大小必須是對(duì)齊值的倍數(shù)。

    • 即使結(jié)構(gòu)體的所有字段都正確對(duì)齊了,如果結(jié)構(gòu)體的總大小不是其自身對(duì)齊值的倍數(shù),編譯器也會(huì)在結(jié)構(gòu)體末尾添加填充字節(jié),以確保當(dāng)這個(gè)結(jié)構(gòu)體作為數(shù)組元素或嵌套在其他結(jié)構(gòu)體中時(shí),下一個(gè)元素也能正確對(duì)齊。

unsafe. Offsetof

函數(shù)返回結(jié)構(gòu)體中某個(gè)字段相對(duì)于結(jié)構(gòu)體起始地址的字節(jié)偏移量。這個(gè)偏移量是考慮了字段大小和內(nèi)存對(duì)齊后,該字段實(shí)際開始的字節(jié)位置。

目的: 這個(gè)函數(shù)揭示了編譯器在內(nèi)存中如何排列結(jié)構(gòu)體字段,包括為了對(duì)齊而插入的任何填充。

示例:

對(duì)于一個(gè)結(jié)構(gòu)體:

var x struct {
    a bool
    b int16
    c []int
}

下面顯示了對(duì)x和它的三個(gè)字段調(diào)用unsafe包相關(guān)函數(shù)的計(jì)算結(jié)果:

顯示了一個(gè)結(jié)構(gòu)體變量 x 以及其在32位和64位機(jī)器上的典型的內(nèi)存?;疑珔^(qū)域是空洞。

對(duì)于不同的系統(tǒng)計(jì)算是不一樣的:

32位系統(tǒng):

Sizeof(x)   = 16  Alignof(x)   = 4
Sizeof(x.a) = 1   Alignof(x.a) = 1 Offsetof(x.a) = 0
Sizeof(x.b) = 2   Alignof(x.b) = 2 Offsetof(x.b) = 2
Sizeof(x.c) = 12  Alignof(x.c) = 4 Offsetof(x.c) = 4

64位系統(tǒng):

Sizeof(x)   = 32  Alignof(x)   = 8
Sizeof(x.a) = 1   Alignof(x.a) = 1 Offsetof(x.a) = 0
Sizeof(x.b) = 2   Alignof(x.b) = 2 Offsetof(x.b) = 2
Sizeof(x.c) = 24  Alignof(x.c) = 8 Offsetof(x.c) = 8

unsafe.Add(ptr Pointer, len IntegerType) Pointer

此函數(shù)將一個(gè)偏移量 len 添加到 ptr 指向的地址,并返回一個(gè)新的 unsafe.Pointer,代表新的內(nèi)存地址。這部分地覆蓋了之前通過 uintptr 進(jìn)行指針?biāo)阈g(shù)的常見用法,并提供了更清晰的語義 。  

unsafe.Slice(ptr *ArbitraryType, len IntegerType)ArbitraryType:

從一個(gè)安全指針 ptr 和指定長度 len 創(chuàng)建一個(gè)切片。ArbitraryType 是結(jié)果切片的元素類型。這允許在不復(fù)制數(shù)據(jù)的情況下將底層數(shù)組解釋為切片 。

unsafe.String(ptr *byte, len IntegerType) string:

從一個(gè) byte 指針 ptr 和指定長度 len 創(chuàng)建一個(gè)字符串。由于Go字符串是不可變的,通過此函數(shù)創(chuàng)建的字符串,其底層字節(jié)在返回后不應(yīng)被修改 。  = 

unsafe.StringData(str string) *byte:

 返回字符串 str 底層字節(jié)的指針。對(duì)于空字符串,返回值是不確定的,可能為 nil。同樣,返回的字節(jié)不應(yīng)被修改 。

unsafe.SliceData(sliceArbitraryType) *ArbitraryType:

 返回切片 slice 底層數(shù)組的指針。這有助于在不進(jìn)行額外內(nèi)存分配的情況下,獲取切片底層數(shù)據(jù)的直接引用 。  

示例:

package main

import (
	"fmt"
	"unsafe"
)

type Employee struct {
	ID     int32
	Name   string
	Age    int16
	Active bool
}

func main() {
	emp := Employee{ID: 101, Name: "Alice", Age: 30, Active: true}
	basePtr := unsafe.Pointer(&emp)
    fmt.Println(basePtr)
	ageOffset := unsafe.Offsetof(emp.Age)
	agePtr := unsafe.Add(basePtr, ageOffset)
	// add函數(shù)是將原始的地址加上一個(gè)偏移量,返回一個(gè)新的地址
	fmt.Println(agePtr)

	data := [5]byte{10, 20, 30, 40, 50}
	fmt.Printf("原始 Go 數(shù)組: %v (地址: %p)\n", data, &data[0])
	// 使用 unsafe.Slice 將原始數(shù)組的底層內(nèi)存轉(zhuǎn)換為 []byte 切片
	// 第一個(gè)參數(shù)是原始內(nèi)存的起始指針
	// 第二個(gè)參數(shù)是切片的長度
	// 這是 Go 1.17+ 用于安全創(chuàng)建切片的方式
	slice := unsafe.Slice(&data[0], len(data))
	fmt.Printf("通過 unsafe.Slice 創(chuàng)建的切片: %v (地址: %p)\n", slice, &slice[0])
	// 驗(yàn)證地址是否一致 (零拷貝)
	fmt.Printf("原始數(shù)組起始地址 == 切片起始地址? %t\n", unsafe.Pointer(&data[0]) == unsafe.Pointer(&slice[0]))

	slice[0] = 100
	fmt.Printf("修改切片后原始數(shù)組: %v\n", data) // Output: [100 20 30 40 50]

}

運(yùn)行結(jié)果:

0xc0000943a0
0xc0000943b8
原始 Go 數(shù)組: [10 20 30 40 50] (地址: 0xc00008c0a8)
通過 unsafe.Slice 創(chuàng)建的切片: [10 20 30 40 50] (地址: 0xc00008c0a8)
原始數(shù)組起始地址 == 切片起始地址? true
修改切片后原始數(shù)組: [100 20 30 40 50]

3.unsafe包的應(yīng)用場景與代碼示例

不同類型間的零拷貝轉(zhuǎn)換:

Go通常不允許不同類型間直接零拷貝轉(zhuǎn)換,unsafe包打破限制,實(shí)現(xiàn)底層內(nèi)存布局兼容的類型轉(zhuǎn)換,避免內(nèi)存分配和復(fù)制,提高性

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

// Float64bits 返回 f 的 IEEE 754 浮點(diǎn)數(shù)的二進(jìn)制表示
func Float64bits(f float64) uint64 {
	return *(*uint64)(unsafe.Pointer(&f)) // 將 float64 的地址轉(zhuǎn)換為 *uint64 類型,然后解引用
}

// Float64frombits 返回 IEEE 754 浮點(diǎn)數(shù)的二進(jìn)制表示 b 對(duì)應(yīng)的 float64 值
func Float64frombits(b uint64) float64 {
	return *(*float64)(unsafe.Pointer(&b)) // 將 uint64 的地址轉(zhuǎn)換為 *float64 類型,然后解引用
}

func main() {
	f := 3.1415926535
	bits := Float64bits(f)
	fmt.Printf("Original float64: %f\n", f)
	fmt.Println(reflect.TypeOf(bits).Name())

	newFloat := Float64frombits(bits)
	fmt.Println(reflect.TypeOf(newFloat).Name())

	fmt.Printf("Converted back:   %f\n", newFloat)
	// 演示byte 和 string 的零拷貝轉(zhuǎn)換
	byteSlice := []byte{'H', 'e', 'l', 'l', 'o', ' ', 'G', 'o'}
	fmt.Printf("原始 byteSlice 地址: %p\n", &byteSlice[0])

	// 將byte 轉(zhuǎn)換為 string,避免復(fù)制。
	// 注意:轉(zhuǎn)換后的 string 不應(yīng)再修改原始 byteSlice 的內(nèi)容。
	s := unsafe.String(unsafe.SliceData(byteSlice), len(byteSlice))

	fmt.Printf("轉(zhuǎn)換為 string (s) 的底層數(shù)據(jù)地址: %p\n", unsafe.StringData(s))
	fmt.Printf("Byte slice to string (zero-copy): %s\n", s)
	// 將 string 轉(zhuǎn)換為byte,避免復(fù)制。
	// 注意:轉(zhuǎn)換后的byte 不應(yīng)修改,因?yàn)樵?string 是不可變的。
	b := unsafe.Slice(unsafe.StringData(s), len(s))
	fmt.Printf("轉(zhuǎn)換為 []byte (b) 的底層數(shù)據(jù)地址: %p\n", unsafe.SliceData(b))
	fmt.Printf("String to byte slice : %v\n", b)
}

運(yùn)行結(jié)果:

Original float64: 3.141593
uint64
float64
Converted back:   3.141593
原始 byteSlice 地址: 0xc00000a128
轉(zhuǎn)換為 string (s) 的底層數(shù)據(jù)地址: 0xc00000a128
Byte slice to string (zero-copy): Hello Go
轉(zhuǎn)換為 []byte (b) 的底層數(shù)據(jù)地址: 0xc00000a128
String to byte slice : [72 101 108 108 111 32 71 111]
結(jié)構(gòu)體內(nèi)部字段的直接訪問與修改:

Go語言的結(jié)構(gòu)體字段默認(rèn)是可訪問的,但對(duì)于未導(dǎo)出的(小寫字母開頭)字段,外部包無法直接訪問。unsafe 包可以繞過這種訪問限制,允許直接通過內(nèi)存地址計(jì)算來訪問和修改結(jié)構(gòu)體的任何字段,包括未導(dǎo)出的字段 。

package main

import (
	"fmt"
	"unsafe"
)

type MyStruct struct {
	id   int    // 未導(dǎo)出字段
	Name string // 導(dǎo)出字段
}

func main() {
	s := MyStruct{
		id:   123,
		Name: "Original Name",
	}

	fmt.Printf("Original struct: %+v\n", s)

	// 1. 通過 unsafe.Offsetof 獲取未導(dǎo)出字段 id 的偏移量
	idOffset := unsafe.Offsetof(s.id)
	fmt.Printf("Offset of 'id' field: %d bytes\n", idOffset)

	// 2. 獲取結(jié)構(gòu)體 s 的內(nèi)存地址,并轉(zhuǎn)換為 uintptr
	sPtr := uintptr(unsafe.Pointer(&s))

	// 3. 計(jì)算 id 字段的內(nèi)存地址
	idAddr := sPtr + idOffset

	// 4. 將 id 字段的內(nèi)存地址轉(zhuǎn)換為 *int 類型指針,并修改其值
	idPtr := (*int)(unsafe.Pointer(idAddr))
	*idPtr = 456

	fmt.Printf("Modified struct: %+v\n", s)

	// 驗(yàn)證修改是否成功
	fmt.Printf("Accessing modified id: %d\n", s.id)

}
運(yùn)行結(jié)果;
Original struct: {id:123 Name:Original Name}
Offset of 'id' field: 0 bytes
Modified struct: {id:456 Name:Original Name}
Accessing modified id: 456

具體性能提升:

這里從類型轉(zhuǎn)化和字段修改,兩個(gè)方面具體,通過測(cè)試體現(xiàn)出使用unsafe的速度提升:

可以看出由于unsafe直接可以操作底層內(nèi)存,對(duì)于性能的提升是很大的。

類型轉(zhuǎn)換:

package main

import (
	"fmt"
	"strings"
	"testing" // 導(dǎo)入 testing 包,用于基準(zhǔn)測(cè)試函數(shù)
	"unsafe"
)

// stringFromBytesSafe 是安全、常規(guī)的 []byte 到 string 轉(zhuǎn)換(有復(fù)制)
func stringFromBytesSafe(b []byte) string {
	return string(b)
}

// stringFromBytesUnsafe 是不安全、零拷貝的 []byte 到 string 轉(zhuǎn)換
func stringFromBytesUnsafe(b []byte) string {
	// 確保傳入的 []byte 在 string 的生命周期內(nèi)不會(huì)被修改!
	return unsafe.String(unsafe.SliceData(b), len(b))
}

func main() {
	// 創(chuàng)建一個(gè)大字節(jié)切片,模擬需要轉(zhuǎn)換的數(shù)據(jù)
	data := []byte(strings.Repeat("A", 1024*1024)) // 1MB 的字節(jié)數(shù)據(jù)

	fmt.Println("--- []byte 到 string 轉(zhuǎn)換性能比較 ---")

	// 模擬基準(zhǔn)測(cè)試,實(shí)際項(xiàng)目中應(yīng)使用 go test -bench=.
	fmt.Println("運(yùn)行安全轉(zhuǎn)換 (string(b))...")
	safeResult := testing.Benchmark(func(b *testing.B) {
		for i := 0; i < b.N; i++ {
			_ = stringFromBytesSafe(data)
		}
	})
	fmt.Printf("安全轉(zhuǎn)換平均耗時(shí): %s/op\n", safeResult.T)
	fmt.Printf("安全轉(zhuǎn)換平均內(nèi)存分配: %d B/op (每次操作的內(nèi)存分配量)\n", safeResult.AllocedBytesPerOp())
	fmt.Printf("安全轉(zhuǎn)換平均內(nèi)存分配次數(shù): %d allocs/op\n", safeResult.AllocsPerOp())

	fmt.Println("\n運(yùn)行不安全零拷貝轉(zhuǎn)換 (unsafe.String())...")
	unsafeResult := testing.Benchmark(func(b *testing.B) {
		for i := 0; i < b.N; i++ {
			_ = stringFromBytesUnsafe(data)
		}
	})
	fmt.Printf("不安全轉(zhuǎn)換平均耗時(shí): %s/op\n", unsafeResult.T)
	fmt.Printf("不安全轉(zhuǎn)換平均內(nèi)存分配: %d B/op\n", unsafeResult.AllocedBytesPerOp())
	fmt.Printf("不安全轉(zhuǎn)換平均內(nèi)存分配次數(shù): %d allocs/op\n", unsafeResult.AllocsPerOp())
}

--- []byte 到 string 轉(zhuǎn)換性能比較 ---
運(yùn)行安全轉(zhuǎn)換 (string(b))...
安全轉(zhuǎn)換平均耗時(shí): 1.0996818s/op
安全轉(zhuǎn)換平均內(nèi)存分配: 1048583 B/op (每次操作的內(nèi)存分配量)
安全轉(zhuǎn)換平均內(nèi)存分配次數(shù): 1 allocs/op

運(yùn)行不安全零拷貝轉(zhuǎn)換 (unsafe.String())...
不安全轉(zhuǎn)換平均耗時(shí): 320.9903ms/op
不安全轉(zhuǎn)換平均內(nèi)存分配: 0 B/op
不安全轉(zhuǎn)換平均內(nèi)存分配次數(shù): 0 allocs/op

修改結(jié)構(gòu)體字段:

package main

import (
	"fmt"
	"reflect"
	"testing" // 導(dǎo)入 testing 包,用于基準(zhǔn)測(cè)試
	"unsafe"
)

type MyData struct {
	id    int
	name  string
	value float64
}

// 通過 unsafe 直接修改私有字段 'id'
func unsafeSetID(data *MyData, newID int) {
	basePtr := unsafe.Pointer(data)
	idOffset := unsafe.Offsetof(data.id)
	idPtr := unsafe.Add(basePtr, idOffset)
	*(*int)(idPtr) = newID
}

type MyDataPublic struct {
	ID    int // 公共字段
	name  string
	value float64
}

// 通過 reflect 修改公共字段 'ID'
func reflectSetID(data *MyDataPublic, newID int) {
	v := reflect.ValueOf(data).Elem()
	idField := v.FieldByName("ID")
	idField.SetInt(int64(newID))
}

func main() {
	privateData := &MyData{id: 1, name: "private", value: 1.23}
	publicData := &MyDataPublic{ID: 1, name: "public", value: 1.23}

	// 基準(zhǔn)測(cè)試:通過 unsafe 修改私有字段 'id'
	fmt.Println("unsafe 修改私有字段 'id':")
	unsafeResult := testing.Benchmark(func(b *testing.B) {
		for i := 0; i < b.N; i++ {
			unsafeSetID(privateData, i)
		}
	})
	fmt.Printf("  平均耗時(shí): %s/op\n", unsafeResult.T)
	fmt.Printf("  內(nèi)存分配: %d B/op (bytes allocated per operation)\n", unsafeResult.AllocedBytesPerOp())
	fmt.Printf("  分配次數(shù): %d allocs/op (allocations per operation)\n", unsafeResult.AllocsPerOp())

	// 基準(zhǔn)測(cè)試:通過 reflect 修改公共字段 'ID'
	fmt.Println("\nreflect 修改公共字段 'ID':")
	reflectResult := testing.Benchmark(func(b *testing.B) {
		v := reflect.ValueOf(publicData).Elem()
		idField := v.FieldByName("ID")
		b.ResetTimer() // 重置計(jì)時(shí)器,從這里開始測(cè)量
		for i := 0; i < b.N; i++ {
			idField.SetInt(int64(i))
		}
	})
	fmt.Printf("  平均耗時(shí): %s/op\n", reflectResult.T)
	fmt.Printf("  內(nèi)存分配: %d B/op\n", reflectResult.AllocedBytesPerOp())
	fmt.Printf("  分配次數(shù): %d allocs/op\n", reflectResult.AllocsPerOp())

}

運(yùn)行結(jié)果:
unsafe 修改私有字段 'id':
  平均耗時(shí): 213.3485ms/op
  內(nèi)存分配: 0 B/op (bytes allocated per operation)
  分配次數(shù): 0 allocs/op (allocations per operation)

reflect 修改公共字段 'ID':
  平均耗時(shí): 1.1784909s/op
  內(nèi)存分配: 0 B/op
  分配次數(shù): 0 allocs/op

具體使用案例:

unsafe在GO標(biāo)準(zhǔn)庫使用:

reflect 包

runtime包

bytes 包和 strings 包

go內(nèi)置的還有map、slice、chan 等

unsafe在第三方庫使用:

jsoniter/go (json-iterator/go):(高性能JSON庫)

valyala/fasthttp:(高性能HTTP框架)

高性能核心:

規(guī)避不必要的內(nèi)存分配和數(shù)據(jù)復(fù)制。

 繞過運(yùn)行時(shí)類型系統(tǒng)和反射的開銷,直接與內(nèi)存打交道。

4.使用unsafe包的風(fēng)險(xiǎn)

1. 破壞類型安全

如果你轉(zhuǎn)換的類型與實(shí)際內(nèi)存中的數(shù)據(jù)不匹配,那么在解引用或操作時(shí),就會(huì)讀取到無意義的數(shù)據(jù),或者更糟糕,導(dǎo)致程序崩潰(panic)。

2.懸空指針 (Dangling Pointers) 和垃圾回收問題

首先我們要先理解Go GC工作方式(這里作簡要描述):

Go GC 的工作方式

Go 語言的垃圾回收器是精確的 (precise)。這意味著 GC 能夠準(zhǔn)確地識(shí)別內(nèi)存中的哪些值是指針,以及這些指針指向了哪里。為了做到這一點(diǎn),GC 嚴(yán)重依賴于 Go 語言在編譯時(shí)和運(yùn)行時(shí)維護(hù)的類型信息。

當(dāng) GC 掃描內(nèi)存時(shí),它會(huì):

  • 知道每個(gè)對(duì)象的類型:

根據(jù)類型信息追蹤指針:

  • 如果 GC 發(fā)現(xiàn)某個(gè)對(duì)象沒有任何活躍的指針指向它(即從根對(duì)象,如全局變量、活躍 Goroutine 的棧等,都無法到達(dá)它),那么 GC 就會(huì)認(rèn)為這個(gè)對(duì)象是“垃圾”,可以在后續(xù)階段將其內(nèi)存回收。

為什么會(huì)出現(xiàn)這個(gè)問題?

unsafe.Pointer的設(shè)計(jì)目的就是為了擺脫類型信息,它只是一個(gè)純粹的內(nèi)存地址。

1.GC無法跟蹤unsafe.Pointer所指向的對(duì)象:

當(dāng) Go GC 看到一個(gè) unsafe.Pointer 時(shí),它不知道這個(gè)指針指向的內(nèi)存區(qū)域包含什么類型的數(shù)據(jù)。它無法判斷這個(gè)內(nèi)存區(qū)域里是否有其他 Go 對(duì)象指針,也無法判斷這個(gè) unsafe.Pointer 是否是某個(gè) Go 對(duì)象的唯一“活著”的引用。

因此,Go GC 明確選擇不追蹤 unsafe.Pointer 本身所指向的內(nèi)存。它將其僅僅視為一個(gè)普通的數(shù)字 (uintptr)

2.懸空指針的產(chǎn)生:

由于 GC 不追蹤 unsafe.Pointer 所指向的對(duì)象,這可能導(dǎo)致一個(gè)嚴(yán)重的后果:當(dāng)你通過 unsafe.Pointer 獲得了某個(gè) Go 對(duì)象的內(nèi)存地址,但這個(gè)對(duì)象卻沒有其他 “可追蹤的 Go 指針” 指向它時(shí),GC 可能會(huì)錯(cuò)誤地認(rèn)為這個(gè)對(duì)象是垃圾并將其回收。

這時(shí)unsafe.Pointer 就變成了一個(gè)“懸空指針”。

如何避免

1.你所操作的內(nèi)存區(qū)域不會(huì)在其生命周期內(nèi)被 GC 回收,除非你已經(jīng)明確知道并處理了回收后的行為。

2.通常情況下,你所操作的 Go 對(duì)象至少有一個(gè)普通 Go 指針在活躍地引用它,從而阻止 GC 回收它。

5.unsafe包的替代方案與常規(guī)方法

1.性能優(yōu)化方面:

優(yōu)化數(shù)據(jù)結(jié)構(gòu)布局 (內(nèi)存對(duì)齊):

算法和數(shù)據(jù)結(jié)構(gòu)的優(yōu)化:

2.繞過類型系統(tǒng)

Go 1.18+ 的泛型:

訪問私有字段:reflect

6.總結(jié)

價(jià)值:

unsafe包為Go語言提供底層內(nèi)存操作能力,在特定場景下實(shí)現(xiàn)極致性能和靈活性,是Go生態(tài)系統(tǒng)的重要補(bǔ)充。

限制:

使用unsafe面臨非可移植性、安全問題、未定義行為和調(diào)試?yán)щy等風(fēng)險(xiǎn),

到此這篇關(guān)于深入理解Go語言u(píng)nsafe包的文章就介紹到這了,更多相關(guān)Go語言u(píng)nsafe包內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 深入了解Golang官方container/list原理

    深入了解Golang官方container/list原理

    在?Golang?的標(biāo)準(zhǔn)庫?container?中,包含了幾種常見的數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn),其實(shí)是非常好的學(xué)習(xí)材料,本文主要為大家介紹了container/list的原理與使用,感興趣的可以了解一下
    2023-08-08
  • 詳解Go語言中的作用域和變量隱藏

    詳解Go語言中的作用域和變量隱藏

    這篇文章主要為大家介紹了Go語言中的作用域和變量隱藏,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Go語言有一定的幫助,感興趣的小伙伴可以了解一下
    2022-04-04
  • 在Go中使用JSON(附demo)

    在Go中使用JSON(附demo)

    Go開發(fā)人員經(jīng)常需要處理JSON內(nèi)容,本文主要介紹了在Go中使用JSON,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • golang 跳出多重循環(huán)的高級(jí)break用法說明

    golang 跳出多重循環(huán)的高級(jí)break用法說明

    這篇文章主要介紹了golang 跳出多重循環(huán)的高級(jí)break用法說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • 淺析Gin框架中路由參數(shù)的使用

    淺析Gin框架中路由參數(shù)的使用

    這篇文章主要為大家介紹了路由參數(shù)的基本語法,以及路由匹配和路由參數(shù)值提取等相關(guān)內(nèi)容,以幫助讀者更好地對(duì)Gin?框架中路由參數(shù)進(jìn)行使用,需要的可以參考下
    2023-08-08
  • 使用docker構(gòu)建golang線上部署環(huán)境的步驟詳解

    使用docker構(gòu)建golang線上部署環(huán)境的步驟詳解

    這篇文章主要介紹了使用docker構(gòu)建golang線上部署環(huán)境的步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-11-11
  • Golang TCP粘包拆包問題的解決方法

    Golang TCP粘包拆包問題的解決方法

    這篇文章主要給大家介紹了Golang TCP粘包拆包問題的解決方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Golang具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-07-07
  • 超詳細(xì)Go語言中JSON處理技巧分享

    超詳細(xì)Go語言中JSON處理技巧分享

    這篇文章主要為大家總結(jié)了go語言中對(duì)JSON數(shù)據(jù)結(jié)構(gòu)和結(jié)構(gòu)體之間相互轉(zhuǎn)換問題及解決方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-06-06
  • Golang中自定義json序列化時(shí)間格式的示例代碼

    Golang中自定義json序列化時(shí)間格式的示例代碼

    Go語言作為一個(gè)由Google開發(fā),號(hào)稱互聯(lián)網(wǎng)的C語言的語言,自然也對(duì)JSON格式支持很好,下面這篇文章主要介紹了關(guān)于Golang中自定義json序列化時(shí)間格式的相關(guān)內(nèi)容,下面話不多說了,來一起看看詳細(xì)的介紹吧
    2024-08-08
  • go語言接口用法實(shí)例分析

    go語言接口用法實(shí)例分析

    這篇文章主要介紹了go語言接口用法,實(shí)例分析了Go語言接口的定義及使用技巧,需要的朋友可以參考下
    2015-03-03

最新評(píng)論