
粗略地说,我们的处理器分为两类:手机中使用的ARM处理器和英特尔及AMD生产的x64处理器。过去,最好的服务器处理器都出自英特尔之手。但如今,英特尔越来越难以跟上时代的步伐。
最近,亚马逊推出了最新的AMD微架构(Zen 5)。具体来说,如果您启动一个r8a实例,您将获得一颗AMD EPYC 9R45处理器。而对应的英特尔版本( r8i )则配备了英特尔至强6975P-C处理器。这款英特尔处理器属于Granite Rapids系列(2024年发布)。
Phoronix 的 Michael Larabel 发表了两篇关于 AMD 新处理器的文章。其中一篇题为《AMD EPYC 9005 带来惊人性能》,非常值得一读。他发现,与之前的 AMD 处理器(采用 Zen 4 微架构)相比,AMD EPYC 9R4 的速度提升了 1.6 倍。在另一篇文章中,Michael 将 AMD 处理器与相应的 Intel 处理器进行了比较,结果显示 AMD 处理器的速度也比 Intel 处理器快 1.6 倍。
我决定试用一下。碰巧我当时正在开发新版的simdutf 库。simdutf 库除了其他功能外,还支持 UTF-8、UTF-16 和 UTF-32 编码之间的快速转码。主流浏览器和 JavaScript 运行时环境(例如 Node.js 或 Bun)都使用它。一个常见的重要操作是从 UTF-16 到 UTF-8 的转换。JavaScript 内部依赖于 UTF-16,因此大多数字符占用 2 个字节,而互联网默认使用 UTF-8,其中字符占用 1 到 4 个字节。
UTF-16 是一种可变长度的 Unicode 编码,它使用单个 16 位代码单元(值范围从0x0000到0xd7ff和0xe000到0xffff )来表示大多数常用字符,但通过使用代理对,它可以扩展到U+FFFF以外的完整 Unicode 范围:一个高位代理( 0xd800到0xdbff )后跟一个低位代理( 0xdc00到0xdfff ),它们共同编码一个附加字符,该字符映射到四个 UTF-8 字节。因此,我们可以认为代理对中的每个元素在 UTF-8 中占用两个字节。范围为0x0000到0x007f (ASCII)的非代理代码单元变为一个字节, 0x0080到0x07ff变为两个字节, 0x0800到0xffff (不包括代理)变为三个字节。
我的基准测试代码首先确定需要多少输出内存,然后再进行转码。
size_t utf8_length = simdutf :: utf8_length_from_utf16 ( str . data (), str . size ()); if ( buffer . size () < utf8_length ) { buffer . resize ( utf8_length ); } simdutf :: convert_utf16_to_utf8 ( str . data (), str . size (), buffer . data ());在最新的处理器上,转码代码并不简单(size_t utf8_length = simdutf :: utf8_length_from_utf16 ( str . data (), str . size ()); if ( buffer . size () < utf8_length ) { buffer . resize ( utf8_length ); } simdutf :: convert_utf16_to_utf8 ( str . data (), str . size (), buffer . data ());
Clausecker 和 Lemire,2023 )。然而,从 UTF-16 数据计算 UTF-8 长度要简单一些。
这些英特尔和AMD处理器支持AVX-512指令集:与我们通常使用的64位寄存器相比,AVX-512指令集可以操作高达64字节的寄存器。它是SIMD(单指令多数据流)的一种应用。借助AVX-512,您可以一次加载和处理32个UTF-16单元的数据。我们的主程序如下所示。
__m512i input = _mm512_loadu_si512 ( in ); __mmask32 is_surrogate = _mm512_cmpeq_epi16_mask ( _mm512_and_si512 ( input , _mm512_set1_epi16 ( 0xf800 )), _mm512_set1_epi16 ( 0xd800 )); __mmask32 c0 = _mm512_test_epi16_mask ( input , _mm512_set1_epi16 ( 0xff80 )); __mmask32 c1 = _mm512_test_epi16_mask ( input , _mm512_set1_epi16 ( 0xf800 )); count += count_ones32 ( c0 ); count += count_ones32 ( c1 ); count -= count_ones32 ( is_surrogate );该代码使用以下方式处理从内存加载的 512 位 UTF-16 代码单元向量:__m512i input = _mm512_loadu_si512 ( in ); __mmask32 is_surrogate = _mm512_cmpeq_epi16_mask ( _mm512_and_si512 ( input , _mm512_set1_epi16 ( 0xf800 )), _mm512_set1_epi16 ( 0xd800 )); __mmask32 c0 = _mm512_test_epi16_mask ( input , _mm512_set1_epi16 ( 0xff80 )); __mmask32 c1 = _mm512_test_epi16_mask ( input , _mm512_set1_epi16 ( 0xf800 )); count += count_ones32 ( c0 ); count += count_ones32 ( c1 ); count -= count_ones32 ( is_surrogate );
_mm512_loadu_si512 。它首先通过按位与运算( _mm512_and_si512 )将每个代码单元与0xf800进行掩码,仅保留最高五位,然后将结果( _mm512_cmpeq_epi16_mask )与0xd800进行比较,从而识别代理代码单元;这将生成一个 32 位掩码,其中代理范围( 0xd800到0xdfff )内的任何代码单元的相应位都会被置位,指示潜在的 UTF-16 代理对,这些代理对在 UTF-8 中不应增加额外的长度。接下来,我们使用按位测试( _mm512_test_epi16_mask )将每个代码单元与0xff80的掩码进行比较,对于任何非 ASCII 代码单元, c0位都会被置位。类似地,另一个针对0xf800 _mm512_test_epi16_mask函数会为 UTF-8 编码中需要 3 个字节的代码单元(代理对除外)设置c1中的位。最后,代码会将c0和c1中被设置位的数量累加到一个计数器中,然后减去代理掩码的弹出计数。总的来说,我们可以使用十几条指令处理大约 32 个 UTF-16 单元。(感谢 Wojciech Muła 的精辟设计,也感谢 Yagiz Nizipli 在相关优化方面提供的帮助。)
使用 AMD 处理器的大型 Amazon 实例每小时费用为 0.13892 美元,而使用 Intel 处理器的实例每小时费用为 0.15976 美元。我使用 Amazon Linux 启动了这两个实例。然后,我在 shell 中运行了以下命令。
sudo yum install cmake git gcc sudo dnf install gcc14 gcc14-c++ git clone https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog.git cd Code-used-on-Daniel-Lemire-s-blog/2025/11/15 CXX = gcc14-g++ cmake -B build cmake --build build ./build/benchmark我得到以下结果。sudo yum install cmake git gcc sudo dnf install gcc14 gcc14-c++ git clone https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog.git cd Code-used-on-Daniel-Lemire-s-blog/2025/11/15 CXX = gcc14-g++ cmake -B build cmake --build build ./build/benchmark
| 处理器 | GB/s | GHz | Ins/Byte | 注射/周期 |
|---|---|---|---|---|
| AMD | 11 | 4.5 | 1.7 | 4.0 |
| 英特尔 | 6 | 3.9 | 1.7 | 2.6 |
基准测试结果表明,在UTF-16到UTF-8转码过程中,AMD处理器的吞吐量几乎是Intel处理器的两倍(10.53 GB/s对比5.96 GB/s),这部分得益于其更高的运行频率。两款处理器每字节所需的指令数均为1.71条,但AMD处理器的每周期指令数显著更高(3.98条/周期对比2.64条/周期),这表明其在AVX-512指令流水线中具有更优的执行效率。其中一个原因在于执行单元的数量。AMD处理器拥有四个能够对512位寄存器进行计算的单元,而Intel处理器通常只有两个这样的执行单元。
我的基准测试范围比 Larabel 的更窄,这有助于证明 AMD 在使用 AVX-512 指令集时比 Intel 具有巨大优势。考虑到 AVX-512 指令集是由 Intel 发明的,而 AMD 对其的支持起步较晚,这一点尤其引人注目。可以说,AMD 在 Intel 擅长的领域击败了它。
延伸阅读:Robert Clausecker、Daniel Lemire, 《使用 AVX-512 指令转码 Unicode 字符》 ,软件:实践与经验 53 (12),2023 年。
原文: https://lemire.me/blog/2025/11/16/amd-vs-intel-a-unicode-benchmark/