当前位置:首页 > 科技  > 软件

Go 内存优化与垃圾收集

来源: 责编: 时间:2024-01-15 17:10:40 127观看
导读Go提供了自动化的内存管理机制,但在某些情况下需要更精细的微调从而避免发生OOM错误。本文将讨论Go的垃圾收集器、应用程序内存优化以及如何防止OOM(Out-Of-Memory)错误。Go中的堆(Heap)栈(Stack)我不会详细介绍垃圾收

Go提供了自动化的内存管理机制,但在某些情况下需要更精细的微调从而避免发生OOM错误。本文将讨论Go的垃圾收集器、应用程序内存优化以及如何防止OOM(Out-Of-Memory)错误。Ggp28资讯网——每日最新资讯28at.com

Ggp28资讯网——每日最新资讯28at.com

Go中的堆(Heap)栈(Stack)

我不会详细介绍垃圾收集器如何工作,已经有很多关于这个主题的文章和官方文档(比如A Guide to the Go Garbage Collector[2]和源码[3])。但是,我会提到一些有助于理解本文主题的基本概念。Ggp28资讯网——每日最新资讯28at.com

你可能已经知道,Go的数据可以存储在两个主要的内存存储中: 栈(stack)和堆(heap)。Ggp28资讯网——每日最新资讯28at.com

Ggp28资讯网——每日最新资讯28at.com

通常,栈存储的数据的大小和使用时间可以由Go编译器预测,包括函数局部变量、函数参数、返回值等。Ggp28资讯网——每日最新资讯28at.com

栈是自动管理的,遵循后进先出(LIFO)原则。当调用函数时,所有相关数据都放在栈的顶部,函数结束时,这些数据将从栈中删除。栈不需要复杂的垃圾收集机制,其内存管理开销最小,在栈中检索和存储数据的过程非常快。Ggp28资讯网——每日最新资讯28at.com

然而,并不是所有数据都可以存储在栈中。在执行过程中动态更改的数据或需要在函数范围之外访问的数据不能放在栈上,因为编译器无法预测其使用情况,这种数据应该存储在堆中。Ggp28资讯网——每日最新资讯28at.com

与栈不同,从堆中检索数据并对其进行管理的成本更高。Ggp28资讯网——每日最新资讯28at.com

栈里放什么,堆里放什么?

正如前面提到的,栈用于具有可预测大小和寿命的值,例如:Ggp28资讯网——每日最新资讯28at.com

  • 在函数内部声明的局部变量,例如基本数据类型变量(例如数字和布尔值)。
  • 函数参数。
  • 函数返回后不再被引用的返回值。

Go编译器在决定将数据放在栈中还是堆中时会考虑各种细微差别。Ggp28资讯网——每日最新资讯28at.com

例如,预分配大小为64 KB的数据将存储在栈中,而大于64 KB的数据将存储在堆中。这同样适用于数组,如果数组超过10 MB,将存储在堆中。Ggp28资讯网——每日最新资讯28at.com

可以使用逃逸分析(escape analysis)来确定特定变量的存储位置。Ggp28资讯网——每日最新资讯28at.com

例如,可以通过命令行编译参数-gcflags=-m来分析应用程序:Ggp28资讯网——每日最新资讯28at.com

go build -gcflags=-m main.go

如果使用-gcflags=-m参数编译下面的main.go:Ggp28资讯网——每日最新资讯28at.com

package mainfunc main() {  var arrayBefore10Mb [1310720]int  arrayBefore10Mb[0] = 1  var arrayAfter10Mb [1310721]int  arrayAfter10Mb[0] = 1  sliceBefore64 := make([]int, 8192)  sliceOver64 := make([]int, 8193)  sliceOver64[0] = sliceBefore64[0]}

结果是:Ggp28资讯网——每日最新资讯28at.com

# command-line-arguments./main.go:3:6: can inline main./main.go:7:6: moved to heap: arrayAfter10Mb./main.go:10:23: make([]int, 8192) does not escape./main.go:11:21: make([]int, 8193) escapes to heap

可以看到arrayAfter10Mb数组被移动到堆中,因为大小超过了10MB,而arrayBefore10Mb仍然留在栈中(对于int变量,10MB等于10 * 1024 * 1024 / 8 = 1310720个元素)。Ggp28资讯网——每日最新资讯28at.com

此外,sliceBefore64没有存储在堆中,因为它的大小小于64KB,而sliceOver64被存储在堆中(对于int变量,64KB等于64 * 1024 / 8 = 8192个元素)。Ggp28资讯网——每日最新资讯28at.com

要了解更多关于在堆中分配的位置和内容,可以参考malloc.go源码[4]。Ggp28资讯网——每日最新资讯28at.com

因此,使用堆的一种方法是尽量避免用它!但是,如果数据已经落在堆中了呢?Ggp28资讯网——每日最新资讯28at.com

与栈不同,堆的大小是无限的,并且不断增长。堆存储动态创建的对象,如结构体、分片和映射,以及由于其限制而无法放入栈中的大内存块。Ggp28资讯网——每日最新资讯28at.com

在堆中重用内存并防止其完全阻塞的唯一工具是垃圾收集器。Ggp28资讯网——每日最新资讯28at.com

浅谈垃圾收集器的工作原理

垃圾收集器(GC)是一种专门用于识别和释放动态分配内存的系统。Ggp28资讯网——每日最新资讯28at.com

Go使用基于跟踪和标记和扫描算法的垃圾收集算法。在标记阶段,垃圾收集器将应用程序正在使用的数据标记为活跃堆。然后,在清理阶段,GC遍历所有未标记为活跃的内存并复用。Ggp28资讯网——每日最新资讯28at.com

垃圾收集器不是免费工作的,需要消耗两个重要的系统资源: CPU时间和物理内存。Ggp28资讯网——每日最新资讯28at.com

垃圾收集器中的内存由以下部分组成:Ggp28资讯网——每日最新资讯28at.com

  • 活跃堆内存(在前一个垃圾收集周期中标记为"活跃"的内存)
  • 新的堆内存(尚未被垃圾收集器分析的堆内存)
  • 存储元数据的内存,与前两个实体相比,这些元数据通常微不足道。

垃圾收集器所消耗的CPU时间与其工作细节有关。有一种称为"stop-the-world"的垃圾收集器实现,它在垃圾收集期间完全停止程序执行,导致CPU时间被花在非生产性工作上。Ggp28资讯网——每日最新资讯28at.com

在Go里,垃圾收集器并不是完全"stop-the-world",而是与应用程序并行执行其大部分工作(例如标记堆)。Ggp28资讯网——每日最新资讯28at.com

但是,垃圾收集器的操作仍然有一些限制,并且会在一个周期内多次完全停止工作代码的执行,想要了解更多可以阅读源码[5]。Ggp28资讯网——每日最新资讯28at.com

如何管理垃圾收集器

在Go中可以通过某些参数管理垃圾收集器: GOGC环境变量或runtime/debug包中的等效函数SetGCPercent。Ggp28资讯网——每日最新资讯28at.com

GOGC参数确定将触发垃圾收集的新未分配堆内存相对于活跃内存的百分比。Ggp28资讯网——每日最新资讯28at.com

GOGC的默认值是100,意味着当新内存达到活跃堆内存的100%时将触发垃圾收集。Ggp28资讯网——每日最新资讯28at.com

Ggp28资讯网——每日最新资讯28at.com

当新堆占用活跃堆的100%时,将运行垃圾收集器Ggp28资讯网——每日最新资讯28at.com

我们以示例程序为例,通过go tool trace跟踪堆大小的变化,我们用Go 1.20.1版本来运行程序。Ggp28资讯网——每日最新资讯28at.com

在本例中,performMemoryIntensiveTask函数使用了在堆中分配的大量内存。这个函数启动一个队列大小为NumWorker的工作池,任务数量等于NumTasks。Ggp28资讯网——每日最新资讯28at.com

package mainimport ( "fmt" "os" "runtime/debug" "runtime/trace" "sync")const ( NumWorkers    = 4     // Number of workers. NumTasks      = 500   // Number of tasks. MemoryIntense = 10000 // Size of memory-intensive task (number of elements).)func main() { // Write to the trace file. f, _ := os.Create("trace.out") trace.Start(f) defer trace.Stop() // Set the target percentage for the garbage collector. Default is 100%. debug.SetGCPercent(100) // Task queue and result queue. taskQueue := make(chan int, NumTasks) resultQueue := make(chan int, NumTasks) // Start workers. var wg sync.WaitGroup wg.Add(NumWorkers) for i := 0; i < NumWorkers; i++ {  go worker(taskQueue, resultQueue, &wg) } // Send tasks to the queue. for i := 0; i < NumTasks; i++ {  taskQueue <- i } close(taskQueue) // Retrieve results from the queue. go func() {  wg.Wait()  close(resultQueue) }() // Process the results. for result := range resultQueue {  fmt.Println("Result:", result) } fmt.Println("Done!")}// Worker function.func worker(tasks <-chan int, results chan<- int, wg *sync.WaitGroup) { defer wg.Done() for task := range tasks {  result := performMemoryIntensiveTask(task)  results <- result }}// performMemoryIntensiveTask is a memory-intensive function.func performMemoryIntensiveTask(task int) int { // Create a large-sized slice. data := make([]int, MemoryIntense) for i := 0; i < MemoryIntense; i++ {  data[i] = i + task } // Latency imitation. time.Sleep(10 * time.Millisecond) // Calculate the result. result := 0 for _, value := range data {  result += value } return result}

跟踪程序执行的结果被写入文件trace.out:Ggp28资讯网——每日最新资讯28at.com

// Writing to the trace file.f, _ := os.Create("trace.out")trace.Start(f)defer trace.Stop()

通过go tool trace,可以观察堆大小的变化,并分析程序中垃圾收集器的行为。Ggp28资讯网——每日最新资讯28at.com

请注意,go tool trace的精确细节和功能可能因go版本不同而有所差异,因此建议参考官方文档,以获取有关其在特定go版本中使用的详细信息。Ggp28资讯网——每日最新资讯28at.com

Ggp28资讯网——每日最新资讯28at.com

GOGC的默认值

GOGC参数可以使用runtime/debug包中的debug.SetGCPercent进行设置,GOGC默认设置为100%。Ggp28资讯网——每日最新资讯28at.com

用下面命令运行程序:Ggp28资讯网——每日最新资讯28at.com

go run main.go

程序执行后,将会创建trace.out文件,可以使用go tool工具对其进行分析。要做到这一点,执行命令:Ggp28资讯网——每日最新资讯28at.com

go tool trace trace.out

然后可以通过打开web浏览器并访问http://127.0.0.1:54784/trace来查看基于web的跟踪查看器。Ggp28资讯网——每日最新资讯28at.com

Ggp28资讯网——每日最新资讯28at.com

GOGC = 100Ggp28资讯网——每日最新资讯28at.com

在"STATS"选项卡中,可以看到"Heap"字段,显示了在应用程序执行期间堆大小的变化情况,图中红色区域表示堆占用的内存。Ggp28资讯网——每日最新资讯28at.com

在"PROCS"选项卡中,"GC"(垃圾收集器)字段显示的蓝色列表示触发垃圾收集器的时刻。Ggp28资讯网——每日最新资讯28at.com

一旦新堆的大小达到活动堆大小的100%,就会触发垃圾收集。例如,如果活跃堆大小为10 MB,则当当前堆大小达到10 MB时将触发垃圾收集。Ggp28资讯网——每日最新资讯28at.com

跟踪所有垃圾收集调用使我们能够确定垃圾收集器处于活动状态的总时间。Ggp28资讯网——每日最新资讯28at.com

Ggp28资讯网——每日最新资讯28at.com

GOGC=100时的GC调用次数Ggp28资讯网——每日最新资讯28at.com

示例中,当GOGC值为100时,将调用垃圾收集器16次,总执行时间为14 ms。Ggp28资讯网——每日最新资讯28at.com

更频繁的调用GC

如果我们将debug.SetGCPercent(10)设置为10%后运行代码,将观察到垃圾收集器调用的频率更高。现在,如果当前堆大小达到活跃堆大小的10%时,将触发垃圾收集。Ggp28资讯网——每日最新资讯28at.com

换句话说,如果活跃堆大小为10 MB,则当前堆大小达到1 MB时就将触发垃圾收集。Ggp28资讯网——每日最新资讯28at.com

Ggp28资讯网——每日最新资讯28at.com

GOGC = 10Ggp28资讯网——每日最新资讯28at.com

在本例中,垃圾收集器被调用了38次,总垃圾收集时间为28 ms。Ggp28资讯网——每日最新资讯28at.com

Ggp28资讯网——每日最新资讯28at.com

GOGC=10时的GC调用次数Ggp28资讯网——每日最新资讯28at.com

可以观察到,将GOGC设置为低于100%的值可以增加垃圾收集的频率,可能导致CPU使用率增加并降低程序性能。Ggp28资讯网——每日最新资讯28at.com

更少的调用GC

如果运行相同程序,但将debug.SetGCPercent(1000)设置为1000%,我们将得到以下结果:Ggp28资讯网——每日最新资讯28at.com

Ggp28资讯网——每日最新资讯28at.com

GOGC = 1000Ggp28资讯网——每日最新资讯28at.com

可以看到,当前堆的大小一直在增长,直到达到活跃堆大小的1000%。换句话说,如果活跃堆大小为10 MB,则当前堆大小达到100 MB时将触发垃圾收集。Ggp28资讯网——每日最新资讯28at.com

Ggp28资讯网——每日最新资讯28at.com

GOGC=1000时的GC调用次数Ggp28资讯网——每日最新资讯28at.com

在当前情况下,垃圾收集器被调用一次并执行2毫秒。Ggp28资讯网——每日最新资讯28at.com

关闭GC

还可以通过设置GOGC=off或调用debug.SetGCPercent(-1)来禁用垃圾收集。Ggp28资讯网——每日最新资讯28at.com

下面是禁用垃圾收集器而不设置GOMEMLIMIT时堆的行为:Ggp28资讯网——每日最新资讯28at.com

Ggp28资讯网——每日最新资讯28at.com

当GC=off时,堆大小不断增长。Ggp28资讯网——每日最新资讯28at.com

可以看到,在关闭GC后,应用程序的堆大小一直在增长,直到程序执行为止。Ggp28资讯网——每日最新资讯28at.com

堆占用多少内存?

在活跃堆的实际内存分配中,通常不像我们在trace中看到的那样定期和可预测的工作。Ggp28资讯网——每日最新资讯28at.com

活跃堆随着每个垃圾收集周期动态变化,并且在某些条件下,其绝对值可能出现峰值。Ggp28资讯网——每日最新资讯28at.com

例如,如果由于多个并行任务的重叠,活跃堆的大小可以增长到800 MB,那么只有在当前堆大小达到1.6 GB时才会触发垃圾收集。Ggp28资讯网——每日最新资讯28at.com

Ggp28资讯网——每日最新资讯28at.com

现代开发通常在具有内存使用限制的容器中运行应用。因此,如果容器将内存限制设置为1 GB,并且总堆大小增加到1.6 GB,则容器将失效,并出现OOM(out of memory)错误。Ggp28资讯网——每日最新资讯28at.com

让我们模拟一下这种情况。例如,我们在内存限制为10 MB的容器中运行程序(仅用于测试目的)。Dockerfile:Ggp28资讯网——每日最新资讯28at.com

FROM golang:latest as builderWORKDIR /srcCOPY . .RUN go env -w GO111MODULE=onRUN go mod vendorRUN CGO_ENABLED=0 GOOS=linux go build -mod=vendor -a -installsuffix cgo -o app ./cmd/FROM golang:latestWORKDIR /root/COPY --from=builder /src/app .EXPOSE 8080CMD ["./app"]

Docker-compose描述:Ggp28资讯网——每日最新资讯28at.com

version: '3'services: my-app:   build:     context: .     dockerfile: Dockerfile   ports:     - 8080:8080   deploy:     resources:       limits:         memory: 10M

让我们使用前面设置GOGC=1000%的代码启动容器。Ggp28资讯网——每日最新资讯28at.com

可以使用以下命令运行容器:Ggp28资讯网——每日最新资讯28at.com

docker-compose builddocker-compose up

几秒钟后,容器将崩溃,并产生与OOM相对应的错误。Ggp28资讯网——每日最新资讯28at.com

exited with code 137

这种情况非常令人不快: GOGC只控制新堆的相对值,而容器有绝对限制。Ggp28资讯网——每日最新资讯28at.com

如何避免OOM?

从1.19版本开始,在GOMEMLIMIT选项的帮助下,Golang引入了一个名为"软内存管理"的特性,runtime/debug包中名为SetMemoryLimit的类似函数(可以阅读48409-soft-memory-limit.md[6]了解有关此选项的一些有趣的设计细节)提供了相同的功能。Ggp28资讯网——每日最新资讯28at.com

GOMEMLIMIT环境变量设置Go运行时可以使用的总体内存限制,例如: GOMEMLIMIT = 8MiB。要设置内存值,需要使用大小后缀,在本例中为8 MB。Ggp28资讯网——每日最新资讯28at.com

让我们启动将GOMEMLIMIT境变量设置为8MiB的容器。为此,我们将环境变量添加到docker-compose文件中:Ggp28资讯网——每日最新资讯28at.com

version: '3'services: my-app:    environment:      GOMEMLIMIT: "8MiB"   build:     context: .     dockerfile: Dockerfile   ports:     - 8080:8080   deploy:     resources:       limits:         memory: 10M

现在,当启动容器时,程序运行没有任何错误。该机制是专门为解决OOM问题而设计的。Ggp28资讯网——每日最新资讯28at.com

这是因为启用GOMEMLIMIT=8MiB后,会定期调用垃圾收集器,并将堆大小保持在一定限制内,结果就是会频繁调用垃圾收集器以避免内存过载。Ggp28资讯网——每日最新资讯28at.com

Ggp28资讯网——每日最新资讯28at.com

运行垃圾收集器以使堆大小保持在一定的限制内。Ggp28资讯网——每日最新资讯28at.com

成本是什么?

GOMEMLIMIT是强有力的工具,但也可能适得其反。Ggp28资讯网——每日最新资讯28at.com

在上面的堆跟踪图中可以看到这种场景的一个示例。Ggp28资讯网——每日最新资讯28at.com

当总内存大小由于活跃堆或持久程序泄漏的增长而接近GOMEMLIMIT时,将开始根据该限制不断调用垃圾收集器。Ggp28资讯网——每日最新资讯28at.com

由于频繁调用垃圾收集器,应用程序的运行时可能会无限增加,从而消耗应用程序的CPU时间。Ggp28资讯网——每日最新资讯28at.com

这种行为被称为死亡螺旋[7],可能导致应用程序性能下降,与OOM错误不同,这种问题很难检测和修复。Ggp28资讯网——每日最新资讯28at.com

这正是GOMEMLIMIT机制作为软限制起作用的原因。Ggp28资讯网——每日最新资讯28at.com

Go不能100%保证GOMEMLIMIT指定的内存限制会被严格执行,而是会允许使用超出限制的内存,并防止频繁调用垃圾收集器的情况。Ggp28资讯网——每日最新资讯28at.com

为了实现这一点,需要对CPU使用设置限制。目前,这个限制被设置为所有处理器时间的50%,CPU窗口为2 * GOMAXPROCS秒。Ggp28资讯网——每日最新资讯28at.com

这就是为什么我们不能完全避免OOM错误,而是会将其推迟到很久以后发生。Ggp28资讯网——每日最新资讯28at.com

在哪里应用GOMEMLIMIT和GOGC

如果默认垃圾收集器设置在大多数情况下是足够的,那么带有GOMEMLIMIT的软内存管理机制可以使我们避免不愉快的情况。Ggp28资讯网——每日最新资讯28at.com

使用GOMEMLIMIT内存限制可能有用的例子:Ggp28资讯网——每日最新资讯28at.com

  • 在内存有限的容器中运行应用程序时,最好将GOMEMLIMIT设置为保留5-10%的可用内存。
  • 在运行资源密集型库或代码时,对GOMEMLIMIT进行实时管理是有好处的。
  • 当在容器中以脚本形式运行应用程序时(意味着应用程序在一段时间内执行某些任务,然后终止),禁用垃圾收集器但设置GOMEMLIMIT可以提高性能并防止超出容器的资源限制。

避免使用GOMEMLIMIT的情况:Ggp28资讯网——每日最新资讯28at.com

  • 当程序已经接近其环境的内存限制时,不要设置内存限制。
  • 在无法控制的执行环境中部署时,不要使用内存限制,特别是在程序的内存使用与其输入数据成正比的情况下,例如CLI工具或桌面应用程序。

如上所述,通过深思熟虑的方法,我们可以管理程序中的微调设置,例如垃圾收集器和GOMEMLIMIT。然而,仔细考虑应用这些设置的策略无疑非常重要。Ggp28资讯网——每日最新资讯28at.com

参考资料

  • [1]Memory Optimization and Garbage Collector Management in Go: https://betterprogramming.pub/memory-optimization-and-garbage-collector-management-in-go-71da4612a960
  • [2]A Guide to the Go Garbage Collector: https://tip.golang.org/doc/gc-guide
  • [3]mgc.go: https://go.dev/src/runtime/mgc.go
  • [4]malloc.go: https://go.dev/src/runtime/malloc.go
  • [5]mgc.go: https://go.dev/src/runtime/mgc.go
  • [6]48409-soft-memory-limit.md: https://github.com/golang/proposal/blob/master/design/48409-soft-memory-limit.md
  • [7]Soft Memory Limit Death Spirals: https://github.com/golang/proposal/blob/master/design/48409-soft-memory-limit.md#death-spirals

本文链接:http://www.28at.com/showinfo-26-61903-0.htmlGo 内存优化与垃圾收集

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 基于 Rust 的 linter 工具速度很快,但有严重缺陷...

下一篇: 高可靠的跨系统转账如何设计

标签:
  • 热门焦点
  • 掘力计划第 20 期:Flutter 混合开发的混乱之治

    掘力计划第 20 期:Flutter 混合开发的混乱之治

    在掘力计划系列活动第20场,《Flutter 开发实战详解》作者,掘金优秀作者,Github GSY 系列目负责人恋猫的小郭分享了Flutter 混合开发的混乱之治。Flutter 基于自研的 Skia 引擎
  • 让我们一起聊聊文件的操作

    让我们一起聊聊文件的操作

    文件【1】文件是什么?文件是保存数据的地方,是数据源的一种,比如大家经常使用的word文档、txt文件、excel文件、jpg文件...都是文件。文件最主要的作用就是保存数据,它既可以保
  • 一篇文章带你了解 CSS 属性选择器

    一篇文章带你了解 CSS 属性选择器

    属性选择器对带有指定属性的 HTML 元素设置样式。可以为拥有指定属性的 HTML 元素设置样式,而不仅限于 class 和 id 属性。一、了解属性选择器CSS属性选择器提供了一种简单而
  • 一个注解实现接口幂等,这样才优雅!

    一个注解实现接口幂等,这样才优雅!

    场景码猿慢病云管理系统中其实高并发的场景不是很多,没有必要每个接口都去考虑并发高的场景,比如添加住院患者的这个接口,具体的业务代码就不贴了,业务伪代码如下:图片上述代码有
  • 每天一道面试题-CPU伪共享

    每天一道面试题-CPU伪共享

    前言:了不起:又到了每天一到面试题的时候了!学弟,最近学习的怎么样啊 了不起学弟:最近学习的还不错,每天都在学习,每天都在进步! 了不起:那你最近学习的什么呢? 了不起学弟:最近在学习C
  • 信通院:小米、华为等11家应用商店基本完成APP签名及验签工作

    信通院:小米、华为等11家应用商店基本完成APP签名及验签工作

    中国信通院表示,目前,小米、华为、OPPO、vivo、360手机助手、百度手机助手、应用宝、豌豆荚和努比亚等9家应用商店,以及抖音和快手2家新型应用分发平
  • 2299元起!iQOO Pad开启预售:性能最强天玑平板

    2299元起!iQOO Pad开启预售:性能最强天玑平板

    5月23日,iQOO如期举行了新品发布会,除了首发安卓最强旗舰处理器的iQOO Neo8系列新机外,还在发布会上推出了旗下首款平板电脑——iQOO Pad,其搭载了天玑
  • OPPO K11搭载高性能石墨散热系统:旗舰同款 性能凉爽释放

    OPPO K11搭载高性能石墨散热系统:旗舰同款 性能凉爽释放

    日前OPPO官方宣布,将于7月25日14:30举办新品发布会,届时全新的OPPO K11将正式与大家见面,将主打旗舰影像,和同档位竞品相比,其最大的卖点就是将配备索尼
  • 英特尔Xe HPG游戏显卡:拥有512EU,单风扇版本

    英特尔Xe HPG游戏显卡:拥有512EU,单风扇版本

    据10 月 30 日外媒 TheVerge 消息报道,英特尔 Xe HPG Arc Alchemist 的正面实被曝光,不仅拥有 512 EU 版显卡,还拥有 128EU 的单风扇版本。另外,这款显卡 PCB
Top