Wails 打包只有 8.70MB,但运行内存真的更小吗?
上一篇我写那个 Wails v3 本地开发者工具箱时,重点放在了打包体积上。 Windows 下出来的 exe 只有 8.70MB,这个数字确实很显眼。 结果文章发出去后,评论区里不少人都在问另一

Wails 打包只有 8.70MB,但运行内存真的更小吗?

发布时间:2026-05-06 (6天前)

上一篇我写那个 Wails v3 本地开发者工具箱时,重点放在了打包体积上。

Windows 下出来的 exe 只有 8.70MB,这个数字确实很显眼。

结果文章发出去后,评论区里不少人都在问另一件事:

包是小了,那运行起来到底吃多少内存?

这个问题我觉得很有必要测一下。

因为 8.70MB 只能说明分发体积小,不能直接说明运行时也更省。

所以这篇不聊源码,也不聊界面,我就只做一件事:把 Wails 和 Electron 的运行内存拉出来实测一遍。

先把结果放前面:

这次在我这台 Windows 机器上,用同一套多功能工具箱去测,Wails 的打包体积还是明显更小,但运行内存并没有比 Electron 更低。

而且这次测出来,Wails 还高一点。

这个结果我一开始也没预设到,但实测就是这样。

先说清楚:测试的标准都是一样的工具箱

上一篇那个 Wails 项目,本身是个多功能工具箱。

所以我用electron也写了个一样的的工具箱

让他们在同一个标准下进行测试

测试环境

先把环境放出来,免得别人把这组数据当成通用结论。

项目 版本 / 信息
系统 Windows 11 家庭中文版
CPU AMD Ryzen 7 8745H
Node v24.14.1
npm 11.11.0
Go 1.24.0
Wails v3.0.0-alpha.78
Electron 39.2.7

下面这些结果,只代表我当前这台机器、这组工具箱和这套打包配置。

我怎么测的

这次我没有手动开任务管理器看一眼就下结论,而是直接写脚本去跑。

命令是:

powershell -ExecutionPolicy Bypass -File .\measure-memory.ps1

脚本做的事情不复杂:

  • 应用启动后先等 6 秒
  • 连续测 3 次
  • Wails 不只看主进程,也把它拉起来的 msedgewebview2.exe 一起算进去
  • Electron 统计应用目录下的所有相关进程
  • 同时输出 Working SetPrivate Bytes

测试脚本如下

param(
  [int]$StartupDelaySeconds = 6,
  [int]$RepeatCount = 3
)

function Format-MB {
  param([Parameter(Mandatory = $true)][double]$Bytes)
  return "{0:N2} MB" -f ($Bytes / 1MB)
}

function Get-ProcessTree {
  param(
    [Parameter(Mandatory = $true)][int]$RootProcessId
  )

  $all = Get-CimInstance Win32_Process
  $queue = [System.Collections.Generic.Queue[int]]::new()
  $seen = [System.Collections.Generic.HashSet[int]]::new()
  $result = [System.Collections.Generic.List[object]]::new()

  $queue.Enqueue($RootProcessId)

  while ($queue.Count -gt 0) {
    $currentId = $queue.Dequeue()
    if (-not $seen.Add($currentId)) {
      continue
    }

    $proc = $all | Where-Object { $_.ProcessId -eq $currentId } | Select-Object -First 1
    if ($null -eq $proc) {
      continue
    }

    $result.Add($proc)

    $children = $all | Where-Object { $_.ParentProcessId -eq $currentId }
    foreach ($child in $children) {
      $queue.Enqueue([int]$child.ProcessId)
    }
  }

  return $result
}

function Stop-ProcessTree {
  param(
    [Parameter(Mandatory = $true)][System.Collections.IEnumerable]$Processes
  )

  $processList = @($Processes | Sort-Object ProcessId -Descending)
  foreach ($proc in $processList) {
    if (Get-Process -Id $proc.ProcessId -ErrorAction SilentlyContinue) {
      Stop-Process -Id $proc.ProcessId -Force -ErrorAction SilentlyContinue
    }
  }
}

function Measure-AppMemory {
  param(
    [Parameter(Mandatory = $true)][string]$Name,
    [Parameter(Mandatory = $true)][string]$ExePath,
    [string]$ProcessPathPrefix
  )

  if (-not (Test-Path -LiteralPath $ExePath -PathType Leaf)) {
    throw "Executable not found: $ExePath"
  }

  $root = Start-Process -FilePath $ExePath -PassThru
  Start-Sleep -Seconds $StartupDelaySeconds

  try {
    $tree = @(Get-ProcessTree -RootProcessId $root.Id)

    if ($ProcessPathPrefix) {
      $tree = @(
        $tree | Where-Object {
          $_.ExecutablePath -and (
            $_.ExecutablePath.StartsWith($ProcessPathPrefix, [System.StringComparison]::OrdinalIgnoreCase) -or
            $_.ExecutablePath -eq $ExePath
          )
        }
      )
    }

    $workingSet = ($tree | Measure-Object -Property WorkingSetSize -Sum).Sum
    $privateBytes = ($tree | Measure-Object -Property PrivatePageCount -Sum).Sum

    [pscustomobject]@{
      App = $Name
      ProcessCount = $tree.Count
      WorkingSetMB = [math]::Round($workingSet / 1MB, 2)
      PrivateMB = [math]::Round($privateBytes / 1MB, 2)
      WorkingSet = Format-MB $workingSet
      PrivateBytes = Format-MB $privateBytes
      Processes = ($tree | ForEach-Object { $_.Name } | Sort-Object | Get-Unique) -join ", "
    }
  }
  finally {
    $liveTree = Get-ProcessTree -RootProcessId $root.Id
    Stop-ProcessTree -Processes $liveTree
  }
}

$root = Split-Path -Parent $MyInvocation.MyCommand.Path
$wailsExe = Join-Path $root "wails-json-tool\json-tool-wails\bin\json-tool-wails.exe"
$electronDir = Join-Path $root "electron-json-tool\release\JSON Tool Electron-win32-x64"
$electronExe = Join-Path $electronDir "JSON Tool Electron.exe"

$measurements = @()

for ($i = 1; $i -le $RepeatCount; $i++) {
  $measurements += Measure-AppMemory -Name "Wails v3" -ExePath $wailsExe | Select-Object *, @{Name = "Run"; Expression = { $i } }
  $measurements += Measure-AppMemory -Name "Electron" -ExePath $electronExe -ProcessPathPrefix $electronDir | Select-Object *, @{Name = "Run"; Expression = { $i } }
}

$measurements |
  Sort-Object App, Run |
  Format-Table App, Run, ProcessCount, WorkingSet, PrivateBytes -AutoSize

""

$summary = $measurements |
  Group-Object App |
  ForEach-Object {
    $workingSetAverage = ($_.Group | Measure-Object -Property WorkingSetMB -Average).Average
    $privateAverage = ($_.Group | Measure-Object -Property PrivateMB -Average).Average
    $processCountAverage = ($_.Group | Measure-Object -Property ProcessCount -Average).Average
    [pscustomobject]@{
      App = $_.Name
      AvgProcessCount = [math]::Round($processCountAverage, 2)
      AvgWorkingSet = "{0:N2} MB" -f $workingSetAverage
      AvgPrivateBytes = "{0:N2} MB" -f $privateAverage
      Processes = ($_.Group[0].Processes)
    }
  }

$summary | Format-Table App, AvgProcessCount, AvgWorkingSet, AvgPrivateBytes, Processes -AutoSize

这里要特意说一句。

如果只看主进程,结果很容易偏。

因为 Wails 在 Windows 下走的是 WebView2,启动后会带起一串 msedgewebview2.exe;Electron 本身也不是单进程程序。

所以这次我尽量按“这个应用真正启动后占了多少系统资源”来算,而不是只看一个 exe 漂不漂亮。

先看截图

这是我直接跑脚本后整理出来的结果:

实测结果

把 3 次结果和平均值整理成表格,大概是这样:

指标 Wails 工具箱 Electron 工具箱
进程数 7 4
平均 Working Set 403.96 MB 330.91 MB
平均 Private Bytes 219.77 MB 203.72 MB
打包产物 8.70 MB exe 341.93 MB 解包目录

单次结果波动也不算大:

应用 第 1 次 第 2 次 第 3 次
Wails Working Set 407.45 MB 402.92 MB 401.52 MB
Electron Working Set 330.88 MB 330.82 MB 331.03 MB

也就是说,至少在这轮测试里,结论挺直接:

Wails 的包体积更小,但这组工具箱的空载内存并没有更低。

这个结果为什么会这样

我觉得主要是两件事。

第一,安装包小,不等于运行时一定小

这个其实是最容易被顺手带过去的地方。

Wails 的优势之一,确实是分发出来的东西更小。

因为它没有像 Electron 那样把完整的 Chromium 一起打进包里,而是更多依赖系统已有的 WebView 运行时。

所以从安装包和分发角度看,Wails 还是轻的。

但这个“轻”,不能自动翻译成“运行内存也一定更低”。

第二,Wails 不是没有浏览器那层成本

有些人会下意识觉得,Wails 是 Go,Electron 是 Chromium,所以 Wails 运行起来应该天然更省。

这次实测至少说明,事情没这么简单。

Wails 在 Windows 下还是走 WebView2,也就是系统 WebView。

换句话说,它不是完全没有浏览器渲染层,只是这套运行时不跟着应用一起打包而已。

所以你会看到:

  • 包体积小很多
  • 运行起来依然会有 WebView2 相关进程

这两件事其实并不矛盾。

那是不是能说 Wails 比 Electron 更吃内存

我觉得也不能这么写死。

更稳的说法应该是:

在我这台 Windows 机器、这组同功能工具箱、当前版本下,Wails 的打包体积明显更小,但空载内存并没有比 Electron 更低。

这里面有几个变量都可能影响结果:

  • WebView2 版本
  • Electron 版本
  • 前端页面复杂度
  • 是否有更多窗口
  • 是否有更多本地能力调用
  • 不同系统环境下的进程表现

所以这篇更像是在纠正一个很容易被带偏的印象:

8.70MB 说的是包体积,不是运行时成本。

那 Wails 还值不值得用

我自己的答案还是:值。

只是卖点要说准。

如果你更在意这些:

  • 安装包别太大
  • 不想把 Chromium 一起塞进产物
  • 本身就是 Go 开发者
  • 做的是轻量工具、内部工具、本地工具

那 Wails 还是很有吸引力。

但如果你原本的预期是:

换成 Wails 以后,包更小,内存也一定更小

那至少这次这组工具箱实测没支持这个结论。

最后

这轮测试对我来说,反而把 Wails 的定位看得更清楚了一点。

它的优势更像是:

  • 分发轻
  • Go 接起来顺
  • 做小工具很舒服

而不是简单一句“它比 Electron 更小”。