之前测 SimdSkia 时,最刺眼的数字来自通用 x64 AOT:同一张 Avalonia 控件页,原版只要约 0.92 ms,我们却要 1.87 ms。只展示 AVX2 的好成绩没有解决用户默认发布程序的实际问题。
这次把普通 x64 当成主要优化对象,做了 SSE2 合成与采样、字形缓存、填充和分配优化,再与原版重新测量。结果是同一控件页 1.8722→0.7611 ms/帧,耗时降低 59.3%;同轮原版为 0.9159 ms/帧。
本文对应 SimdSkia 0.1.0-preview.8 的渲染实现,固定 Avalonia 12.1.4 / SkiaSharp 3.119.4 / HarfBuzzSharp 8.3.1.3。这是一个公开限制的实验性预览版:本机已测速度门槛通过,完整画质仍未通过,常驻内存也没有追平原版。 不能据此宣称任意 Avalonia 项目都能无差别替换。
代码、NuGet 包、使用示例及当前上架状态:GitHub:hnlyf168/SimdSkia、preview.8 发布记录。
先看结果与适用范围
| 测量 |
结果 |
不能据此推断的内容 |
| 通用 x64 AOT CPU 吞吐,三个真实控件场景 |
综合耗时低于原版 4.4%,最慢场景落后 3.8% |
任意操作、字体或硬件都更快 |
| CPU 含两组 60 FPS 节拍,共五场景 |
综合耗时低于原版 6.7%;相对 preview.7 各场景减少 50.2%–62.7% |
屏幕帧率提高相同比例 |
| Vulkan / Direct2D 完成帧 |
综合耗时低于每例较快原版 GPU 29.8% / 35.3% |
GPU 窗口呈现、AOT 或所有驱动都有同样收益 |
| 普通控件页进程工作集 |
SimdSkia 79.89 MiB,原版 60.91 MiB |
内存已经追平 |
| 60 FPS 滚动托管分配 |
preview.7 155.14 → 本轮 28.05 KiB/帧,原版 12.87 |
常驻内存会按相同比例下降 |
| 默认空白项目 AOT 体积,preview.7 历史实测 |
运行文件 36.53→19.51 MiB,少三个原生 DLL |
这是 preview.8、GPU 或业务项目的体积 |
表中的“综合”取各场景耗时比的几何平均数,CPU 和 GPU 分开计算。门槛为综合比 ≤1.00、每例 ≤1.10;不把耗时、CPU%、内存和画质混成一个分数。
如何测量,避免比较错对象
硬件为 Windows 11 / i7-8700K(6 核 12 线程)/ 31.93 GiB RAM / GTX 1050 Ti,SDK 10.0.402,.NET 10.0.13。正式测量日期为 2026-10-27。双方使用相同 Fluent 控件树、尺寸与 ZCOOL XiaoWei 中英文字体,包含按钮、文本框、边框、透明度、图片画刷和滚动列表。
- CPU:75 个串行进程。 原版、已发布 preview.7、本轮源码候选各五次;每次预热 2 秒、采样 5 秒。100%/150% DPI 控件页、滚动列表做吞吐测试,控件页和列表另外做 60 FPS 节拍测试。
- GPU:36 个串行进程。 原版 Vulkan、原版硬件 ANGLE/D3D11、SimdSkia Vulkan、SimdSkia Direct2D 各三次;预热 3 秒、采样 5 秒。每例以两个原版中较快者作速度门槛。
- 顺序逐轮轮换,正式采样期间不同时编译、生成图表或运行其他基准;图中柱和表格取独立进程中位数,原始数据保留所有进程。
CPU 使用通用 x64 Native AOT,实际检查 PE 与进程模块,没有 CLR/JIT,AVX2=false。原版的 Skia 原生 DLL 保留自己的正常指令集分派;不能把 C# 的 AVX2 标志解释成双方底层都关闭了 AVX2。
GPU 使用 .NET 10 JIT、离屏完整帧与 BGRA 回读。每帧包含 Avalonia 绘制、提交、等待 GPU 完成和全帧回读;不是只量提交调用,也不是显示器 FPS。两种生产后端中的 C# 字形图集、软件图层和回退照常计入,诊断计数保留。Direct2D 真窗口采用交换链,而本次为了共同可读的完整帧对照仍执行回读,因此不能套用为窗口性能结论。
原版 CPU 保留默认逐绘制透明度优化,SimdSkia 使用完整组透明度语义;原版的渲染策略没有为基准刻意改慢。两者现存的画质差距也保留,后文给出失败数据。
通用 x64:先把默认发布程序优化好
| 场景 |
原版 ms/帧 |
preview.7 |
本轮 |
本轮/原版 |
| 控件页 1× / 吞吐 |
0.9159 |
1.8722 |
0.7611 |
0.831× |
| 控件页 1.5× / 吞吐 |
1.7599 |
3.8125 |
1.7803 |
1.012× |
| 滚动列表 1× / 吞吐 |
0.4142 |
1.1543 |
0.4300 |
1.038× |
| 控件页 1× / 60 FPS |
1.2355 |
2.2607 |
1.0626 |
0.860× |
| 滚动列表 1× / 60 FPS |
0.8591 |
1.6252 |
0.8092 |
0.942× |
![image]()
普通控件页已经从明显落后变成较快;150% DPI 和吞吐滚动仍分别落后约 1.2%、3.8%。因此准确结论是这组场景综合达到目标,仍有个别场景稍慢。
这轮主要改了五处:
- 补齐 SSE2 热点。 通用 AOT 不能靠 AVX2 成绩兜底。字形覆盖率、图像透明合成与双线性采样增加 SSE2 路径,保留整数除法、浮点舍入、预乘 Alpha、stride 和短尾结果。
- 减少重复填充成本。 白色/透明连续缓冲走字节填充;适合的中等色块初始化后批量复制;超出受测范围的缓冲保留原路径。黑色字形检查实际目标像素后采用等价公式。
- 让缓存命中更便宜。 按不可变字形串身份查询,避免每帧复制大键、重排链表和反复写入 GC 引用;淘汰仍有明确预算。串预算从 2 MiB 调到 4 MiB,内存代价没有隐藏。
- 减少无用的状态工作。 opacity=1 且没有实际离屏层时减少脏区记账;纯平移字形不再计算未使用的逆矩阵。
- 控制帧内分配和复制。 画布缓存容器按需创建,紧密 framebuffer 整块复制,带 padding 的缓冲仍逐行处理。
没有以减少普通路径抗锯齿采样、忽略颜色处理或跳过控件绘制来获得这些成绩;本轮 CPU 页面捕获与 preview.7 逐字节相同。
CPU 占用和内存:速度提升没有解决所有问题
CPU% 使用 进程 CPU 时间 / 墙钟时间 / 12 × 100%。无限吞吐场景会尽量跑满一个逻辑核,两边都在约 8.3%,不能仅看这个百分比判断效率;还应看 CPU ms/帧和每帧处理器周期。
| 场景 |
实现 |
CPU ms/帧 |
整机 CPU% |
WS MiB |
Private MiB |
托管分配 KiB/帧 |
| 控件页 1× / 吞吐 |
原版 |
0.916 |
8.31 |
60.91 |
36.40 |
7.09 |
| 控件页 1× / 吞吐 |
preview.7 |
1.866 |
8.33 |
79.86 |
59.88 |
5.65 |
| 控件页 1× / 吞吐 |
preview.8 实现 |
0.761 |
8.33 |
79.89 |
59.96 |
5.37 |
| 控件页 1.5× / 吞吐 |
原版 |
1.760 |
8.31 |
64.16 |
39.93 |
7.09 |
| 控件页 1.5× / 吞吐 |
preview.7 |
3.775 |
8.25 |
89.93 |
69.96 |
5.66 |
| 控件页 1.5× / 吞吐 |
preview.8 实现 |
1.775 |
8.31 |
89.95 |
69.99 |
5.37 |
| 滚动列表 1× / 吞吐 |
原版 |
0.413 |
8.31 |
56.46 |
34.82 |
6.60 |
| 滚动列表 1× / 吞吐 |
preview.7 |
1.151 |
8.31 |
69.78 |
52.64 |
2.48 |
| 滚动列表 1× / 吞吐 |
preview.8 实现 |
0.429 |
8.31 |
69.92 |
52.71 |
2.48 |
| 控件页 1× / 60 FPS |
原版 |
1.198 |
0.60 |
60.36 |
35.91 |
7.71 |
| 控件页 1× / 60 FPS |
preview.7 |
2.969 |
1.48 |
77.83 |
57.83 |
6.28 |
| 控件页 1× / 60 FPS |
preview.8 实现 |
0.625 |
0.31 |
77.84 |
57.85 |
6.04 |
| 滚动列表 1× / 60 FPS |
原版 |
4.948 |
2.47 |
59.94 |
38.48 |
12.87 |
| 滚动列表 1× / 60 FPS |
preview.7 |
5.104 |
2.55 |
71.27 |
54.01 |
155.14 |
| 滚动列表 1× / 60 FPS |
preview.8 实现 |
3.594 |
1.79 |
70.00 |
52.79 |
28.05 |
![image]()
![image]()
![image]()
普通控件页工作集 79.89 vs 60.91 MiB,私有提交 59.96 vs 36.40 MiB,仍明显高于原版。滚动的每帧分配大幅下降,但常驻缓存仍存在,工作集没有按 82% 的分配降幅一起下降。
60 FPS 的进程 CPU 时间受 Windows 短时调度记账影响,逐进程数据另保留 QueryProcessCycleTime。例如普通控件页,原版/本轮约 4.958 / 4.341 百万周期/帧;不能把表中 CPU 时间的降幅直接当成相同比例的计算工作下降,更不能把偶发 0 当成没有开销。周期也不能直接换算成毫秒。Windows API 说明。
WS 是驻留工作集,Private 是私有提交,两者不相加。托管分配只描述 GC 管理的分配,不代表原版没有原生分配。两边的字体、缓存、运行时与原生资源均留在进程测量中;GPU 表不是独立显存统计。
Vulkan 与 Direct2D:等待实际完成后比较
| 场景 |
原版 Vulkan ms/完成帧 |
原版 ANGLE/D3D11 ms/完成帧 |
SimdSkia Vulkan ms/完成帧 |
SimdSkia Direct2D ms/完成帧 |
| 控件页 1× |
2.4374 |
1.9878 |
1.3587 |
1.5340 |
| 控件页 1.5× |
3.6374 |
3.8261 |
2.4821 |
2.7861 |
| 滚动列表 1× |
2.3085 |
1.7753 |
1.3173 |
0.8151 |
![image]()
Vulkan 综合耗时比为 0.7021,Direct2D 为 0.6474。每例都比较较快原版,未只挑慢的原版后端。原版 Vulkan 与原版 ANGLE 也不是同一种实现路径,完整表中分别保留。
| 场景 |
后端 |
CPU ms/帧 |
整机 CPU% |
WS MiB |
Private MiB |
托管分配 KiB/帧 |
| 控件页 1× |
原版 Vulkan |
1.774 |
6.15 |
148.56 |
148.17 |
5.13 |
| 控件页 1× |
原版 ANGLE/D3D11 |
2.249 |
9.42 |
139.56 |
119.93 |
5.13 |
| 控件页 1× |
SimdSkia Vulkan |
0.616 |
3.83 |
153.80 |
149.38 |
7.76 |
| 控件页 1× |
SimdSkia Direct2D |
2.041 |
11.01 |
145.71 |
128.85 |
5.57 |
| 控件页 1.5× |
原版 Vulkan |
2.923 |
6.69 |
160.72 |
166.35 |
5.13 |
| 控件页 1.5× |
原版 ANGLE/D3D11 |
4.085 |
8.88 |
151.48 |
137.61 |
5.13 |
| 控件页 1.5× |
SimdSkia Vulkan |
1.109 |
3.72 |
167.79 |
170.45 |
7.93 |
| 控件页 1.5× |
SimdSkia Direct2D |
3.432 |
9.97 |
155.66 |
144.20 |
5.57 |
| 滚动列表 1× |
原版 Vulkan |
1.691 |
5.96 |
138.72 |
143.87 |
4.17 |
| 滚动列表 1× |
原版 ANGLE/D3D11 |
1.996 |
9.34 |
124.59 |
110.09 |
4.17 |
| 滚动列表 1× |
SimdSkia Vulkan |
0.479 |
3.02 |
142.26 |
147.87 |
4.70 |
| 滚动列表 1× |
SimdSkia Direct2D |
1.185 |
12.11 |
129.89 |
121.01 |
2.43 |
![image]()
Direct2D 的无限吞吐 CPU% 在部分场景反而更高:它单位时间内完成的帧数更多,不能把较短帧时间自动写成较低 CPU%。Vulkan 的完成与回读成本仍存在;本次只是证明这组离屏完成帧较快,并没有完成真窗口/多驱动/跨平台的性能普查。
画质与功能兼容:预览版仍有硬伤
本轮精确回归通过 471,569 项,SSE2 限制与完全标量各 471,557 项;实际 Avalonia 9,741、Direct2D 344、Vulkan 内核 540、GPU 集成 12,174 项通过。通用 x64 的真实 Native AOT 另通过 460,050 项采样检查。
这些说明已实现功能、优化等价性和受测生命周期通过检查,不代表原版整页画质通过:CPU 0/5、GPU 0/12 组达到原先规定的最大通道误差 ≤32 且 RMSE ≤2。
| CPU 场景 |
最大通道误差 |
RMSE |
原版整页门槛 |
| 控件页 1× / 吞吐 |
96 |
2.4338 |
未通过 |
| 控件页 1.5× / 吞吐 |
109 |
2.3196 |
未通过 |
| 滚动列表 1× / 吞吐 |
92 |
3.0873 |
未通过 |
| 控件页 1× / 60 FPS |
96 |
2.4338 |
未通过 |
| 滚动列表 1× / 60 FPS |
92 |
3.0873 |
未通过 |
优化前后画面一样,但原有差异仍在。隔离调查已发现字体指令处理、字形轮廓和边缘覆盖规则的不同。实验中移除字体指令或回放原版轮廓有助于定位问题,但没有把这些诊断手段塞进生产路径,也没有修改正式测试字体来提高分数。
verify.ps1 -Reference -Gpu 仍返回质量失败状态;发布流水线验证构建、已实现功能和真实包消费,不把它的绿勾包装成全部画质全绿。本版是在披露限制后提供的预览更新。
| 功能 |
原版 Avalonia.Skia / SkiaSharp |
SimdSkia preview.8 |
| 绘图实现 |
Skia 原生引擎及托管绑定 |
C# CPU 光栅化、像素合成;不调用 Google Skia |
| 基础路径、填充、描边、AA、裁剪、渐变 |
成熟实现 |
引擎与 Avalonia 适配层已有实现;SK* 外观只覆盖子集,部分组合与画质仍有缺口 |
| 图片采样与透明合成 |
原版实现 |
最近邻、双线性、Mitchell 等受支持路径;AVX2/SSE/标量按硬件选择,部分采样组合有限制 |
| TrueType 与中文 |
原版字体/排版体系 |
纯 C# 静态 glyf TTF/TTC、基础中英文布局、系统字体发现;中文须有受支持字体 |
| 复杂文字、字体高级特性 |
支持范围广 |
完整复杂脚本、Bidi/mark、CFF、可变/彩色字体、hinting/LCD 等仍有缺口,部分请求明确报错 |
| 图片编解码 |
支持范围广 |
C# PNG、常见 JPEG 与受支持颜色处理;WebP/GIF、部分 JPEG/ICC 场景等未完成 |
| Windows / Linux / macOS |
官方平台实现 |
三平台 CPU 窗口已有验证;Linux 为 X11/XWayland,非完整原生 Wayland 后端 |
| Windows GPU |
原版图形栈 |
可选 Direct2D/D3D11 原生交换链,或 Vulkan;本次另测 JIT 离屏完成帧 |
| Linux GPU |
原版图形栈 |
可选 Vulkan 路径,当前有完成后 framebuffer 回读,部署硬件需验证 |
| macOS GPU |
原版后端支持 |
未实现 Metal/macOS GPU;CPU 窗口使用官方 libAvaloniaNative.dylib 宿主 |
| 自定义 Skia 控件 |
直接使用其原生 API |
依赖 SK*/GR 或 ISkiaSharpApiLeaseFeature 的控件需要迁移,不能保证直接工作 |
| Native AOT |
支持,仍需应用兼容 AOT |
本文两边均已真实 AOT 编译运行;动态反射与第三方控件仍需单独检查 |
SK* 公共 API 清单仍为 37 个类型、425/3235 个公共条目匹配,包含明确拒绝的 API,不等于 425 个功能已完整验收。SimdSkia.Avalonia 通过 Avalonia 平台服务接入,不要求实现全部 SkiaSharp API;依赖 ISkiaSharpApiLeaseFeature、SK*/GR 原生接口的自绘控件仍需迁移,不承诺二进制替换。
绘图、字体和编解码由 C# 实现,不代表系统窗口和驱动没有原生代码。macOS 保留官方 libAvaloniaNative.dylib 窗口桥;Vulkan / Direct2D 调用系统 API。生产 JPEG 使用 MIT 的纯托管 JpegLibrary,Avalonia 适配器使用 BSL-1.0 的托管 Clipper2。Skia/HarfBuzz/ANGLE 只在隔离原版对照程序中使用。依赖与许可证清单。
默认 Avalonia AOT 少了哪些文件
这里保留 2026-10-26 的 preview.7 历史体积实测,不冒充 preview.8 新数据。两边使用官方空白模板、Avalonia 12.1.4、Fluent 和 Inter、net10.0 / win-x64 / Native AOT。SimdSkia 默认 CPU;原版保留默认后端。这里只比较发布文件,不能用作 CPU/GPU 速度排名。
| 默认空白项目,preview.7 历史 |
原版 MiB |
SimdSkia MiB |
| 运行文件,不含符号 |
36.53 |
19.51 |
| EXE |
18.56 |
19.51 |
| 全部 DLL |
17.97 |
0.00 |
| 原始 publish,含 PDB |
201.27 |
86.62 |
![image]()
运行文件减少 46.59%;减少的是三个原生 DLL,EXE 本身反而增加约 0.95 MiB:
| 原版原生 DLL |
字节 |
MiB |
| av_libglesv2.dll |
5,394,096 |
5.14 |
| libHarfBuzzSharp.dll |
1,816,088 |
1.73 |
| libSkiaSharp.dll |
11,628,896 |
11.09 |
Native AOT 把用到的托管代码编译进本机程序,“没有 DLL”不等于引擎代码不占体积。PDB 是调试符号,上表同时列出含符号的原始 publish;运行体积排除符号但没有手动删绘图库或字体。.NET Native AOT 部署说明。
这些数字只属于该空白 CPU 配置。GPU 适配器、第三方控件、资源和业务逻辑都会改变结果,preview.8 的最终应用体积也应重新发布测量。历史逐文件证据。
怎么在自己的 Avalonia 项目中使用
Avalonia 系列包统一为 12.1.4,所有直接引用的 SimdSkia 包统一为 0.1.0-preview.8。
| NuGet 包 |
适用情况 |
SimdSkia.Avalonia.Desktop |
普通 Windows/Linux/macOS 桌面应用优先选这个,包含宿主选择和基础适配器 |
SimdSkia.Avalonia |
自行配置平台宿主时使用的绘图、字体和排版服务 |
SimdSkia.Avalonia.Gpu |
需要 GPU 时另加的可选适配器 |
SimdSkia / SimdSkia.Compatibility |
使用已实现 SK* 源码外观;普通 Avalonia 应用通常不需要 |
现有默认模板项目移除原版桌面后端,添加新包:
dotnet remove package Avalonia.Desktop
dotnet add package SimdSkia.Avalonia.Desktop --version 0.1.0-preview.8
如果项目直接引用了 Avalonia.Skia,也要移除。保留自己的 App、XAML、MVVM、Fluent 主题,将 .UsePlatformDetect() 改为 .UseSimdSkiaDesktop():
using System;
using Avalonia;
using SimdSkia.Avalonia;
internal static class Program
{
[STAThread]
public static void Main(string[] args)
=> BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UseSimdSkiaDesktop()
.LogToTrace();
}
以上使用项目原有的 App。新建项目所需的完整 .csproj 和 App/Window 双文件示例在 GitHub README,包内 README 也提供相同代码。无需下载源码重编译 Avalonia。
使用系统字体时移除 .WithInterFont() 和不再需要的 Avalonia.Fonts.Inter;也可以像历史体积对照一样保留二者。支持的已安装静态 TTF/TTC 自动发现,中文要求系统实际存在受支持的中文字体;不要假设 Linux/macOS 都有微软雅黑。必要时显式指定:
.UseSimdSkiaDesktop(options => options.FontFiles.Add(@"C:\Fonts\MyFont.ttf"))
Windows 想用 Direct2D,再加同版本 GPU 包:
dotnet add package SimdSkia.Avalonia.Gpu --version 0.1.0-preview.8
.UseSimdSkiaDesktop(options => options.UseGpu(SimdSkiaGpuBackend.Direct2D))
Windows/Linux Vulkan 改为 SimdSkiaGpuBackend.Vulkan,需要系统 Vulkan loader/驱动;当前窗口路径仍有回读。macOS GPU 未实现。现有不支持的画刷等默认明确报错;如显式允许 CPU 回退,应查看诊断计数,不把混合路径说成全 GPU 渲染。
Windows AOT 发布示例:
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<PublishAot>true</PublishAot>
<SelfContained>true</SelfContained>
</PropertyGroup>
dotnet publish -c Release -r win-x64 -o publish-win-x64
WinExe 避免额外控制台窗口;AOT 仍要求业务代码和第三方控件兼容裁剪。需要对应平台的原生编译工具链;Linux/macOS 应分别在对应系统构建。发布后检查依赖树与实际文件,第三方控件可能重新引入 Skia/HarfBuzz/ANGLE。
复现与资料
这一版最实在的变化,是默认通用 x64 不再依赖 AVX2 专用发布才能取得较好的控件绘制成绩。它仍需要把文字画质、常驻内存和更广的功能兼容补上;预览包的用途是让真实应用能够验证收益和暴露差距,而不是替代这些尚未完成的工作。