当前位置: 首页 > 知识库问答 >
问题:

用于memcpy的增强REP MOVSB

甘明朗
2023-03-14

我想使用增强的 REP 移动硬盘 (ERMSB) 来获得自定义内存的高带宽。

ERMSB是随着常春藤桥微架构引入的。如果您不知道什么是 ERMSB,请参阅英特尔优化手册中的“增强型 REP 移动硬盘和 STOSB 操作 (ERMSB)”部分。

我所知道的直接做到这一点的唯一方法是内联汇编。我从https://groups.google.com/forum/#!那里得到了以下函数topic/gnu.gcc.help/-Bmlm_EG_fE

static inline void *__movsb(void *d, const void *s, size_t n) {
  asm volatile ("rep movsb"
                : "=D" (d),
                  "=S" (s),
                  "=c" (n)
                : "0" (d),
                  "1" (s),
                  "2" (n)
                : "memory");
  return d;
}

但是,当我使用它时,带宽比memcpy要小得多。__movsb获得15 GB / s,memcpy获得26 GB / s与我的i7-6700HQ(天湖)系统,Ubuntu 16.10,DDR4@2400 MHz双通道32 GB,GCC 6.2。

为什么使用 REP 移动硬盘的带宽要低得多?我能做些什么来改进它?

这是我用来测试它的代码。

//gcc -O3 -march=native -fopenmp foo.c
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stddef.h>
#include <omp.h>
#include <x86intrin.h>

static inline void *__movsb(void *d, const void *s, size_t n) {
  asm volatile ("rep movsb"
                : "=D" (d),
                  "=S" (s),
                  "=c" (n)
                : "0" (d),
                  "1" (s),
                  "2" (n)
                : "memory");
  return d;
}

int main(void) {
  int n = 1<<30;

  //char *a = malloc(n), *b = malloc(n);

  char *a = _mm_malloc(n,4096), *b = _mm_malloc(n,4096);
  memset(a,2,n), memset(b,1,n);

  __movsb(b,a,n);
  printf("%d\n", memcmp(b,a,n));

  double dtime;

  dtime = -omp_get_wtime();
  for(int i=0; i<10; i++) __movsb(b,a,n);
  dtime += omp_get_wtime();
  printf("dtime %f, %.2f GB/s\n", dtime, 2.0*10*1E-9*n/dtime);

  dtime = -omp_get_wtime();
  for(int i=0; i<10; i++) memcpy(b,a,n);
  dtime += omp_get_wtime();
  printf("dtime %f, %.2f GB/s\n", dtime, 2.0*10*1E-9*n/dtime);  
}

我对代表movsb感兴趣的原因是基于这些评论。

请注意,在艾维布里奇和哈斯韦尔,使用大缓冲区以适应MLC,您可以使用代表移动来击败movntdqa;莫文特德卡将 RFO 引入有限责任公司,代表移动不会...当在艾维布里奇和哈斯韦尔上流式传输到内存时,rep movsb 比 movntdqa 快得多(但请注意,在艾维布里奇之前,它很慢!

这个memcpy实现中缺少/次优的是什么?

下面是我在tinymembnech的相同系统上的结果。

 C copy backwards                                     :   7910.6 MB/s (1.4%)
 C copy backwards (32 byte blocks)                    :   7696.6 MB/s (0.9%)
 C copy backwards (64 byte blocks)                    :   7679.5 MB/s (0.7%)
 C copy                                               :   8811.0 MB/s (1.2%)
 C copy prefetched (32 bytes step)                    :   9328.4 MB/s (0.5%)
 C copy prefetched (64 bytes step)                    :   9355.1 MB/s (0.6%)
 C 2-pass copy                                        :   6474.3 MB/s (1.3%)
 C 2-pass copy prefetched (32 bytes step)             :   7072.9 MB/s (1.2%)
 C 2-pass copy prefetched (64 bytes step)             :   7065.2 MB/s (0.8%)
 C fill                                               :  14426.0 MB/s (1.5%)
 C fill (shuffle within 16 byte blocks)               :  14198.0 MB/s (1.1%)
 C fill (shuffle within 32 byte blocks)               :  14422.0 MB/s (1.7%)
 C fill (shuffle within 64 byte blocks)               :  14178.3 MB/s (1.0%)
 ---
 standard memcpy                                      :  12784.4 MB/s (1.9%)
 standard memset                                      :  30630.3 MB/s (1.1%)
 ---
 MOVSB copy                                           :   8712.0 MB/s (2.0%)
 MOVSD copy                                           :   8712.7 MB/s (1.9%)
 SSE2 copy                                            :   8952.2 MB/s (0.7%)
 SSE2 nontemporal copy                                :  12538.2 MB/s (0.8%)
 SSE2 copy prefetched (32 bytes step)                 :   9553.6 MB/s (0.8%)
 SSE2 copy prefetched (64 bytes step)                 :   9458.5 MB/s (0.5%)
 SSE2 nontemporal copy prefetched (32 bytes step)     :  13103.2 MB/s (0.7%)
 SSE2 nontemporal copy prefetched (64 bytes step)     :  13179.1 MB/s (0.9%)
 SSE2 2-pass copy                                     :   7250.6 MB/s (0.7%)
 SSE2 2-pass copy prefetched (32 bytes step)          :   7437.8 MB/s (0.6%)
 SSE2 2-pass copy prefetched (64 bytes step)          :   7498.2 MB/s (0.9%)
 SSE2 2-pass nontemporal copy                         :   3776.6 MB/s (1.4%)
 SSE2 fill                                            :  14701.3 MB/s (1.6%)
 SSE2 nontemporal fill                                :  34188.3 MB/s (0.8%)

请注意,在我的系统上,预取的 SSE2 副本也比 MOVSB 复制更快。

在我最初的测试中,我没有禁用turbo。我禁用了turbo并再次测试,它似乎没有太大影响。然而,改变电源管理确实有很大的不同。

当我这样做的时候

sudo cpufreq-set -r -g performance

我有时看到< code>rep movsb的速度超过20 GB/s。

具有

sudo cpufreq-set -r -g powersave

我看到的最好的是大约17 GB/s。但是memcpy似乎对电源管理不敏感。

我检查了启用和未启用SpeedStep时的频率(使用< code>turbostat)、空闲时的< code >性能和< code >节能、1核html" target="_blank">负载和4核负载。我运行了英特尔的MKL密集矩阵乘法来创建一个负载,并使用< code>OMP线程数设置线程数。下面是结果表(数字单位为GHz)。

              SpeedStep     idle      1 core    4 core
powersave     OFF           0.8       2.6       2.6
performance   OFF           2.6       2.6       2.6
powersave     ON            0.8       3.5       3.1
performance   ON            3.5       3.5       3.1

这表明,即使禁用了速度步进,CPU的时钟仍会降至0.8 GHz的空闲频率。只有在没有速度步进的情况下,CPU才能以恒定的频率运行。

我使用例如 sudo cpufreq-set -r 性能(因为 cpufreq-set 给出了奇怪的结果)来更改电源设置。这会重新打开涡轮增压器,因此我不得不禁用涡轮增压器。

共有3个答案

孙宏扬
2023-03-14
匿名用户

你说你想要:

显示ERMSB何时有用的答案

但是我不确定它的意思是不是你想的那样。看看你链接到的3.7.6.1文档,它明确地说:

根据长度和对齐因素,使用ERMSB实现memcpy可能无法达到与使用256位或128位AVX替代方案相同的吞吐量水平。

因此,仅仅因为< code>CPUID表示支持ERMSB,并不能保证REP MOVSB将是复制内存的最快方法。这只是意味着它不会像以前的一些CPU那样糟糕。

但是,仅仅因为可能存在在某些情况下可以运行更快的替代方案并不意味着REP MOVSB是无用的。现在,此指令过去产生的性能损失已经消失,它可能再次成为有用的指令。

请记住,与我见过的一些更复杂的memcpy例程相比,它只是一小部分代码(2个字节!)。由于加载和运行大块代码也会受到惩罚(将一些其他代码从cpu的缓存中抛出),有时AVX等人的“好处”会被它对其余代码的影响所抵消。这取决于你在做什么。

你还会问:

为什么使用 REP 移动硬盘的带宽要低得多?我能做些什么来改进它?

不可能“做点什么”来使REP MOVSB运行得更快。它做它所做的事情。

如果你想从memcpy获得更高的速度,你可以找到它的来源。它就在某个地方。或者您可以从调试器中跟踪它,并查看实际采用的代码路径。我的期望是,它使用一些AVX指令,一次处理128或256位。

或者你可以……嗯,你让我们不要说。

佴涵蓄
2023-03-14

Ivy Bridge微架构(2012年和2013年发布的处理器)引入了增强型REP MOVSB(ERMSB)。我们仍然需要检查相应的位。ERMS旨在允许我们使用rep movsb快速复制内存。

最新处理器的最便宜版本——2017年发布的Kaby Lake Celeron和Pentium,没有可以用于快速内存复制的AVX,但仍然有增强型REP MOVSB。英特尔在2018年及以后发布的一些移动和低功耗架构,不是基于天湖的,使用REP MOVSB每个CPU周期复制的字节数比前几代微架构多两倍。

在具有快速短 REP MOV (FSRM) 的冰湖微体系结构之前的增强型 REP MOVSB (ERMSB) 仅比 AVX 复制或常规使用的寄存器副本(如果块大小至少为 256 字节)更快。对于低于64字节的块,它要慢得多,因为ERMSB中有一个很高的内部启动 - 大约35个周期。FSRM 功能在 128 字节之前的预期块也很快。

请参阅英特尔优化手册第3.7.6节增强的REP MOVSB和STOSB操作(ERMSB)http://www . Intel . com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-Optimization-Manual . pdf(适用于尚未采用FSRM的处理器):

    < li >启动成本为35个周期; < li >源地址和目标地址都必须与16字节的边界对齐; < li >源区域不应与目标区域重叠; < li >长度必须是64的倍数才能产生更高的性能; < li >方向必须是向前的(CLD)。

正如我前面所说的,当长度至少为256字节时,REP MOVSB(在FSRM之前的处理器上)开始优于其他方法,但是要看到相对于AVX拷贝的明显优势,长度必须超过2048字节。此外,应该注意,仅使用AVX (256位寄存器)或AVX-512 (512位寄存器)进行内存复制有时可能会产生可怕的后果,如AVX/SSE转换损失或降低turbo频率。因此,与AVX相比,REP MOVSB是一种更安全的内存复制方式。

关于REP MOVSB与AVX副本对齐的效果,英特尔手册提供了以下信息:

  • 如果源缓冲区未对齐,则与 128 位 AVX 相比,对 ERMSB 实现的影响是相似的;
  • 如果目标缓冲区未对齐,则对 ERMSB 实现的影响可能是 25% 降级,而相对于 16 字节对齐方案,内存复制的 128 位 AVX 实现可能仅降级 5%。

我在64位下的英特尔酷睿i5-6600上进行了测试,我比较了REP MOVSB memcpy()和一个简单的MOV RAX,[SRC];RAX MOV[DST]实施< b >当数据适合L1高速缓存时:

 - 1622400000 data blocks of  32 bytes took 17.9337 seconds to copy;  2760.8205 MB/s
 - 1622400000 data blocks of  64 bytes took 17.8364 seconds to copy;  5551.7463 MB/s
 - 811200000 data blocks of  128 bytes took 10.8098 seconds to copy;  9160.5659 MB/s
 - 405600000 data blocks of  256 bytes took  5.8616 seconds to copy; 16893.5527 MB/s
 - 202800000 data blocks of  512 bytes took  3.9315 seconds to copy; 25187.2976 MB/s
 - 101400000 data blocks of 1024 bytes took  2.1648 seconds to copy; 45743.4214 MB/s
 - 50700000 data blocks of  2048 bytes took  1.5301 seconds to copy; 64717.0642 MB/s
 - 25350000 data blocks of  4096 bytes took  1.3346 seconds to copy; 74198.4030 MB/s
 - 12675000 data blocks of  8192 bytes took  1.1069 seconds to copy; 89456.2119 MB/s
 - 6337500 data blocks of  16384 bytes took  1.1120 seconds to copy; 89053.2094 MB/s
 - 1622400000 data blocks of  32 bytes took  7.3536 seconds to copy;  6733.0256 MB/s
 - 1622400000 data blocks of  64 bytes took 10.7727 seconds to copy;  9192.1090 MB/s
 - 811200000 data blocks of  128 bytes took  8.9408 seconds to copy; 11075.4480 MB/s
 - 405600000 data blocks of  256 bytes took  8.4956 seconds to copy; 11655.8805 MB/s
 - 202800000 data blocks of  512 bytes took  9.1032 seconds to copy; 10877.8248 MB/s
 - 101400000 data blocks of 1024 bytes took  8.2539 seconds to copy; 11997.1185 MB/s
 - 50700000 data blocks of  2048 bytes took  7.7909 seconds to copy; 12710.1252 MB/s
 - 25350000 data blocks of  4096 bytes took  7.5992 seconds to copy; 13030.7062 MB/s
 - 12675000 data blocks of  8192 bytes took  7.4679 seconds to copy; 13259.9384 MB/s

因此,即使在128位块上,REP MOVSB(在FSRM之前的处理器上)也比循环中的简单MOV RAX拷贝(未展开)慢。ERMSB实现仅从256字节块开始就开始优于MOV RAX循环。

2019年9月推出的冰湖微体系结构推出了Fast Short REP MOV(FSRM)。此功能可以通过CPUID位进行测试。它用于128字节和更少字节的字符串也很快,但实际上,使用rep movsb时,64字节之前的字符串仍然比使用简单的64位寄存器复制慢。此外,FSRM仅在64位下实现,而不是在32位下实现。至少在我的i7-1065G7 CPU上,rep movsb只能快速处理64位以下的小字符串,但在32位字符串上必须至少为4KB,才能开始优于其他方法。

令人惊讶的是,以前的架构(Nehalem和后来的,直到,但不包括常春藤桥),还没有增强的REP REP MOVB,具有相对快速的REP MOVSD / MOVSQ(但不是REP MOVSB / MOVSW)实现大块,但不够大,无法超过L1缓存。

英特尔优化手册(2.5.6 REP字符串增强)给出了以下与Nehalem微架构相关的信息-英特尔酷睿i5、i7和至强处理器在2009年和2010年发布,以及后来的微架构,包括Sandy Bridge制造到2013年。

如果 ECX,则 MOVSB 的延迟为 9 个周期

  • 小字符串

引用自英特尔优化手册(2.5.6 REP字符串增强):

  • 短字符串

因此,根据英特尔的说法,对于非常大的内存块,REP MOVSW和REP MOVSD/MOVSQ一样快。无论如何,我的测试表明,只有REP MOVSD/MOVSQ速度快,而REP MOVSW在Nehalem和Westmer上甚至比REP MOVSB慢。

根据英特尔在手册中提供的信息,在以前的英特尔微体系结构(2008年之前)上,启动成本甚至更高。

结论:如果您只需要复制适合L1缓存的数据,只需4个周期即可复制64字节的数据非常棒,并且您无需使用XMM寄存器!

#REP MOVSD/MOVSQ 是通用解决方案,如果数据适合 L1 高速缓存,则可在所有英特尔处理器(无需 ERMSB)上运行良好#

以下是对REP MOVS*的测试,当源和目标位于一级缓存中时,测试的块足够大,不会受到启动成本的严重影响,但不会超过一级缓存的大小。资料来源:http://users.atw.hu/instlatx64/

Yonah(2006-2008)

    REP MOVSB 10.91 B/c
    REP MOVSW 10.85 B/c
    REP MOVSD 11.05 B/c

Nehalem(2009-2010年)

    REP MOVSB 25.32 B/c
    REP MOVSW 19.72 B/c
    REP MOVSD 27.56 B/c
    REP MOVSQ 27.54 B/c

威斯特米尔(2010-2011)

    REP MOVSB 21.14 B/c
    REP MOVSW 19.11 B/c
    REP MOVSD 24.27 B/c

Ivy Bridge (2012-2013) -具有增强的REP MOVSB(所有后续CPU也具有增强的REP MOVSB)

    REP MOVSB 28.72 B/c
    REP MOVSW 19.40 B/c
    REP MOVSD 27.96 B/c
    REP MOVSQ 27.89 B/c

天湖(2015-2016)

    REP MOVSB 57.59 B/c
    REP MOVSW 58.20 B/c
    REP MOVSD 58.10 B/c
    REP MOVSQ 57.59 B/c

卡比湖(2016-2017)

    REP MOVSB 58.00 B/c
    REP MOVSW 57.69 B/c
    REP MOVSD 58.00 B/c
    REP MOVSQ 57.89 B/c

我展示了SkyLake和Kaby Lake的测试结果,只是为了确认-这些架构具有相同的每指令周期数据。

坎农湖,莫比尔(2018年5月-2020年2月)

    REP MOVSB 107.44 B/c
    REP MOVSW 106.74 B/c
    REP MOVSD 107.08 B/c
    REP MOVSQ 107.08 B/c

瀑布湖,服务器(2019年4月)

    REP MOVSB 58.72 B/c
    REP MOVSW 58.51 B/c
    REP MOVSD 58.51 B/c
    REP MOVSQ 58.20 B/c
    

彗星湖,桌面,工作站,移动(2019年8月)

    REP MOVSB 58.72 B/c
    REP MOVSW 58.62 B/c
    REP MOVSD 58.72 B/c
    REP MOVSQ 58.72 B/c

冰湖,手机(2019年9月)

    REP MOVSB 102.40 B/c
    REP MOVSW 101.14 B/c
    REP MOVSD 101.14 B/c
    REP MOVSQ 101.14 B/c

特雷蒙特,低功耗(2020年9月)

    REP MOVSB 119.84 B/c
    REP MOVSW 121.78 B/c
    REP MOVSD 121.78 B/c
    REP MOVSQ 121.78 B/c

老虎湖,手机(2020年10月)

    REP MOVSB 93.27 B/c
    REP MOVSW 93.09 B/c
    REP MOVSD 93.09 B/c
    REP MOVSQ 93.09 B/c

如您所见,REP MOVS的实现因微架构而异。在一些处理器上,如Ivy Bridge-REP MOVSB是最快的,尽管只是比REP MOVSD/MOVSQ稍微快一点,但毫无疑问,在自Nehalem以来的所有处理器上,REP MOVSD/MOVSQ都运行得非常好——您甚至不需要“增强型REP MOVSB”,因为在Ivy Bridge(2013)上使用增强型REP MOVSB,REP MOVSD显示的每个时钟数据与Nehalem(2010)上没有增强型REP MOVSB的数据相同,而事实上REP MOVSB只是在SkyLake(2015)之后才变得非常快——是Ivy Bridge上的两倍。所以CPUID中的这个增强的REP MOVSB位可能会令人困惑——它只显示REP MOVSB本身是可以的,但不是任何REP MOVS*都更快。

最令人困惑的ERMSB实现是在常春藤桥微体系结构上。是的,在非常旧的处理器上,在ERMSB之前,用于大型块的REP MOVS*确实使用了常规代码(无RFO)无法使用的缓存协议功能。但该协议不再用于具有ERMSB的常春藤桥。根据安迪·格勒(Andy Glew)对“为什么复杂的记忆/记忆集更优越?”根据Peter Cordes的回答,一个普通代码无法使用的缓存协议功能曾在较旧的处理器上使用过,但在常春藤桥上不再使用。对于REP MOVS启动成本如此之高的原因有一个解释:“选择和设置正确方法的巨大开销主要是由于缺乏微码分支预测”。还有一个有趣的注意事项,1996年Pentium Pro(P6)通过64位微码加载和存储以及无RFO缓存协议实现了REP MOVS*,与Ivy Bridge中的ERMSB不同,它们没有违反内存顺序。

关于代表 movsbrep movsq,在某些处理器上,ERMSB 代表 movsb 的速度稍快(例如,至强 E3-1246 v3),在其他处理器上,movsq 更快(Skylake),而在其他处理器上,它的速度相同(例如 i7-1065G7)。但是,无论如何,我都会选择代表movsq而不是代表movsb

还请注意,此答案仅适用于源数据和目标数据适合一级缓存的情况。根据具体情况,应该考虑内存访问(缓存等)的特殊性。还请注意,此答案中的信息仅与Intel处理器有关,而与其他制造商(如AMD)的处理器无关,这些制造商可能有更好或更差的REP MOVS*指令实现。

以下是一些tinymembench结果,用于显示rep movsbrep mowsd

Haswell 微架构,ERMS,AVX-2,于 2014 年 9 月以 583 美元的价格发布,基本频率 3.5 GHz,最大睿频频率: 3.8 GHz(一核),L2 缓存 6 × 256 KB,L3 缓存 15 MB,支持高达 4×DDR4-2133,已安装 8 个 32768 MB DDR4 ECC reg 模块(总内存为 256GB)。

 C copy backwards                                     :   7268.8 MB/s (1.5%)
 C copy backwards (32 byte blocks)                    :   7264.3 MB/s
 C copy backwards (64 byte blocks)                    :   7271.2 MB/s
 C copy                                               :   7147.2 MB/s
 C copy prefetched (32 bytes step)                    :   7044.6 MB/s
 C copy prefetched (64 bytes step)                    :   7032.5 MB/s
 C 2-pass copy                                        :   6055.3 MB/s
 C 2-pass copy prefetched (32 bytes step)             :   6350.6 MB/s
 C 2-pass copy prefetched (64 bytes step)             :   6336.4 MB/s
 C fill                                               :  11072.2 MB/s
 C fill (shuffle within 16 byte blocks)               :  11071.3 MB/s
 C fill (shuffle within 32 byte blocks)               :  11070.8 MB/s
 C fill (shuffle within 64 byte blocks)               :  11072.0 MB/s
 ---
 standard memcpy                                      :  11608.9 MB/s
 standard memset                                      :  15789.7 MB/s
 ---
 MOVSB copy                                           :   8123.9 MB/s
 MOVSD copy                                           :   8100.9 MB/s (0.3%)
 SSE2 copy                                            :   7213.2 MB/s
 SSE2 nontemporal copy                                :  11985.5 MB/s
 SSE2 copy prefetched (32 bytes step)                 :   7055.8 MB/s
 SSE2 copy prefetched (64 bytes step)                 :   7044.3 MB/s
 SSE2 nontemporal copy prefetched (32 bytes step)     :  11794.4 MB/s
 SSE2 nontemporal copy prefetched (64 bytes step)     :  11813.1 MB/s
 SSE2 2-pass copy                                     :   6394.3 MB/s
 SSE2 2-pass copy prefetched (32 bytes step)          :   6255.9 MB/s
 SSE2 2-pass copy prefetched (64 bytes step)          :   6234.0 MB/s
 SSE2 2-pass nontemporal copy                         :   4279.5 MB/s
 SSE2 fill                                            :  10745.0 MB/s
 SSE2 nontemporal fill                                :  22014.4 MB/s

Haswell,ERMS,AVX-2,3.50GHz

 C copy backwards                                     :   6911.8 MB/s
 C copy backwards (32 byte blocks)                    :   6919.0 MB/s
 C copy backwards (64 byte blocks)                    :   6924.6 MB/s
 C copy                                               :   6934.3 MB/s (0.2%)
 C copy prefetched (32 bytes step)                    :   6860.1 MB/s
 C copy prefetched (64 bytes step)                    :   6875.6 MB/s (0.1%)
 C 2-pass copy                                        :   6471.2 MB/s
 C 2-pass copy prefetched (32 bytes step)             :   6710.3 MB/s
 C 2-pass copy prefetched (64 bytes step)             :   6745.5 MB/s (0.3%)
 C fill                                               :  10812.1 MB/s (0.2%)
 C fill (shuffle within 16 byte blocks)               :  10807.7 MB/s
 C fill (shuffle within 32 byte blocks)               :  10806.6 MB/s
 C fill (shuffle within 64 byte blocks)               :  10809.7 MB/s
 ---
 standard memcpy                                      :  10922.0 MB/s
 standard memset                                      :  28935.1 MB/s
 ---
 MOVSB copy                                           :   9656.7 MB/s
 MOVSD copy                                           :   9430.1 MB/s
 SSE2 copy                                            :   6939.1 MB/s
 SSE2 nontemporal copy                                :  10820.6 MB/s
 SSE2 copy prefetched (32 bytes step)                 :   6857.4 MB/s
 SSE2 copy prefetched (64 bytes step)                 :   6854.9 MB/s
 SSE2 nontemporal copy prefetched (32 bytes step)     :  10774.2 MB/s
 SSE2 nontemporal copy prefetched (64 bytes step)     :  10782.1 MB/s
 SSE2 2-pass copy                                     :   6683.0 MB/s
 SSE2 2-pass copy prefetched (32 bytes step)          :   6687.6 MB/s
 SSE2 2-pass copy prefetched (64 bytes step)          :   6685.8 MB/s
 SSE2 2-pass nontemporal copy                         :   5234.9 MB/s
 SSE2 fill                                            :  10622.2 MB/s
 SSE2 nontemporal fill                                :  22515.2 MB/s (0.1%)

Skylake、ERMS、AVX-512、2.1 GHz(Xeon Gold 6152,基频,无涡轮)

 MOVSB copy                                           :   4619.3 MB/s (0.6%)
 SSE2 fill                                            :   9774.4 MB/s (1.5%)
 SSE2 nontemporal fill                                :   6715.7 MB/s (1.1%)

Kaby Lake于2017年3月发布,售价339美元,基频3.8 GHz,最大涡轮频率4.2 GHz,L2缓存4×256 KB,L3缓存8 MB,4核(8线程),安装了16384 MB DDR4 ECC的4个RAM模块,但它只能使用2个内存通道。

 MOVSB copy                                           :  11720.8 MB/s
 SSE2 fill                                            :  15877.6 MB/s (2.7%)
 SSE2 nontemporal fill                                :  36407.1 MB/s

冰湖,AVX-512,ERMS,FSRM,1.37 GHz(在基频工作,涡轮模式禁用)

MOVSB copy                                           :   7322.7 MB/s
SSE2 fill                                            :   9681.7 MB/s
SSE2 nontemporal fill                                :  16426.2 MB/s

2017年6月发布,售价1075美元,基于Zen gen.1微架构,24个内核(48个线程),基频:2.0GHz,最大涡轮增压:3.0GHz(少数内核)或2.8(所有内核);缓存:L1-64 KB inst。

 MOVSB copy                                           :   7718.0 MB/s
 SSE2 fill                                            :  11233.5 MB/s
 SSE2 nontemporal fill                                :  34893.3 MB/s
 MOVSB copy                                           :   7444.7 MB/s
 SSE2 fill                                            :  11100.1 MB/s
 SSE2 nontemporal fill                                :  31019.8 MB/s
 MOVSB copy                                           :   7251.6 MB/s
 SSE2 fill                                            :  10691.6 MB/s
 SSE2 nontemporal fill                                :  31014.7 MB/s
 MOVSB copy                                           :   7429.1 MB/s
 SSE2 fill                                            :  10954.6 MB/s
 SSE2 nontemporal fill                                :  30957.5 MB/s

REP MOVSD/MOVSQ是一种通用解决方案,如果目标至少对齐64个字节,则它在所有英特尔处理器上运行得相对较好,适用于至少4KB的大型内存块(不需要ERMSB)。REP MOVSD/MOVSQ在更新的处理器上工作得更好,从Skylake开始。而且,对于Ice Lake或更新的微体系结构,它甚至可以完美地处理至少64字节的非常小的字符串。

滕胜涝
2023-03-14

这是一个非常贴近我内心的话题,也是最近的调查,所以我将从几个角度来看待它:历史、一些技术说明(主要是学术性的)、我盒子上的测试结果,最后尝试回答您的实际问题,即何时何地rep movsb可能有意义。

在某种程度上,这是一个共享结果的调用 - 如果您可以运行Tinymembench并共享结果以及CPU和RAM配置的详细信息,那就太好了。特别是如果你有一个4通道的设置,一个常春藤桥盒,一个服务器盒等。

快速字符串复制指令的性能历史有点像阶梯式事件,即性能停滞的时期交替出现大的升级,使其符合标准,甚至比竞争方法更快。例如,Nehalem(主要针对启动开销)和Ivy Bridge(主要针对大拷贝的总吞吐量)的性能都有了大幅提升。在这个线程中,您可以从一位英特尔工程师那里找到关于实现rep-movs指令的困难的十年真知灼见。

例如,在介绍常春藤桥之前的指南中,典型的建议是避免使用或非常小心地使用它们。

当前(嗯,2016年6月)指南有各种令人困惑且有些不一致的建议,例如2

实现的特定变体是在执行时根据数据布局、对齐方式和计数器 (ECX) 值选择的。例如,应使用具有 REP 前缀的 MOVSB/STOSB,计数器值应小于或等于 3 以获得最佳性能。

那么对于3字节或更少字节的拷贝呢?首先,您不需要rep前缀,因为声称的启动延迟约为9个周期,所以使用简单的DWORD或QWORDmovwith bit twidling to mask off the unused bytes(或者如果您知道大小正好是三个字节,可以使用2个显式字节,word s),几乎可以肯定会更好。

他们接着说:

字符串MOVE/STORE指令具有多个数据粒度。对于高效的数据移动,更大的数据粒度是可取的。这意味着可以通过将任意计数器值分解为多个双字加上计数值小于或等于3的单个字节移动来实现更高的效率。

这在当前带有ERMSB的硬件上似乎是错误的,其中rep movsb至少与movdmovq大副本变体一样快或更快。

一般而言,当前指南的该部分(3.7.5)包含合理和严重过时的建议。这是英特尔手册中常见的吞吐量,因为它们以增量方式针对每个架构进行更新(即使在当前手册中也声称涵盖了近二十年的架构),并且旧部分通常不会更新以替换或提供不适用于当前架构的条件建议。

然后,他们继续在第3.7.6节中明确介绍ERMSB。

我不会详尽无遗地讨论剩余的建议,但我会在下面的“为什么使用它”中总结好的部分。

该指南的其他重要声明是,在Haswell上,< code>rep movsb已经得到增强,可以在内部使用256位操作。

这只是从实现的角度对rep指令的潜在优点和缺点的简要总结。

> < li>

当发出< code>rep movs指令时,CPU知道要传输已知大小的整个块。这有助于它以离散指令无法实现的方式优化操作,例如:

  • 当它知道整个缓存行将被覆盖时,避免RFO请求。
  • 立即准确地发出预取请求。硬件预取在检测memcpy类模式方面做得很好,但仍需要几次读取才能启动,并且会“过度预取”复制区域末尾之外的许多缓存行。rep movsb确切地知道区域大小,并且可以准确地预取。

显然,在3内的商店之间,无法保证订购单个rep movs,这有助于简化一致性流量和简单的块移动的其他方面,而简单的movinstructions必须遵守相当严格的内存顺序4

原则上,< code>rep movs指令可以利用ISA中没有公开的各种架构技巧。例如,架构可能有更宽的内部数据路径,ISA公开< sup>5并且< code > rep mov 可以在内部使用这些路径。

>

  • rep movsb 必须实现一个特定的语义,该语义可能比底层软件要求更强。特别是,memcpy禁止重叠区域,因此可能会忽略这种可能性,但rep movsb允许它们并且必须产生预期的结果。在当前的实现中,主要影响启动开销,但可能不会影响大块吞吐量。同样,rep movsb 必须支持字节粒度拷贝,即使您实际使用它来拷贝大块,这些块是某个大次幂 2 的倍数。

    该软件可能包含有关对齐、拷贝大小和可能的混叠的信息,如果使用 rep movsb,这些信息将无法传达给硬件。编译器通常可以确定内存块6 的对齐方式,因此可以避免 rep movs 在每次调用时必须执行的大部分启动工作。

    以下是我的i7-6700HQ上的tinymembench在2.6 GHz下的许多不同复制方法的测试结果(太糟糕了,我有相同的CPU,所以我们没有得到新的数据点…):

     C copy backwards                                     :   8284.8 MB/s (0.3%)
     C copy backwards (32 byte blocks)                    :   8273.9 MB/s (0.4%)
     C copy backwards (64 byte blocks)                    :   8321.9 MB/s (0.8%)
     C copy                                               :   8863.1 MB/s (0.3%)
     C copy prefetched (32 bytes step)                    :   8900.8 MB/s (0.3%)
     C copy prefetched (64 bytes step)                    :   8817.5 MB/s (0.5%)
     C 2-pass copy                                        :   6492.3 MB/s (0.3%)
     C 2-pass copy prefetched (32 bytes step)             :   6516.0 MB/s (2.4%)
     C 2-pass copy prefetched (64 bytes step)             :   6520.5 MB/s (1.2%)
     ---
     standard memcpy                                      :  12169.8 MB/s (3.4%)
     standard memset                                      :  23479.9 MB/s (4.2%)
     ---
     MOVSB copy                                           :  10197.7 MB/s (1.6%)
     MOVSD copy                                           :  10177.6 MB/s (1.6%)
     SSE2 copy                                            :   8973.3 MB/s (2.5%)
     SSE2 nontemporal copy                                :  12924.0 MB/s (1.7%)
     SSE2 copy prefetched (32 bytes step)                 :   9014.2 MB/s (2.7%)
     SSE2 copy prefetched (64 bytes step)                 :   8964.5 MB/s (2.3%)
     SSE2 nontemporal copy prefetched (32 bytes step)     :  11777.2 MB/s (5.6%)
     SSE2 nontemporal copy prefetched (64 bytes step)     :  11826.8 MB/s (3.2%)
     SSE2 2-pass copy                                     :   7529.5 MB/s (1.8%)
     SSE2 2-pass copy prefetched (32 bytes step)          :   7122.5 MB/s (1.0%)
     SSE2 2-pass copy prefetched (64 bytes step)          :   7214.9 MB/s (1.4%)
     SSE2 2-pass nontemporal copy                         :   4987.0 MB/s
    

    一些关键要点:

    • rep-movs方法比所有其他非“非暂时”7方法都快,比“C”方法快得多,后者一次复制8个字节
    • “非暂时”方法的速度更快,比rep-movs方法快26%左右,但这比您报告的方法小得多(26 GB/s比15 GB/s=约73%)
    • 如果不使用非临时存储,那么从C使用8字节拷贝与128位宽的SSE加载/存储一样好。这是因为一个好的复制循环可以产生足够的内存压力,使带宽饱和(例如,2.6 GHz*1存储/周期*8字节=26 GB/s存储)
    • tinymembench中没有明确的256位算法(可能除了“标准”memcpy),但由于上面的注释,这可能并不重要
    • 非临时存储方法相对于临时存储方法增加的吞吐量约为1.45倍,这与NT消除三分之一传输(即NT的1读1写与2读1写)时预期的1.5倍非常接近。rep MOV方法位于在中间
    • 相当低的内存延迟和适度的2通道带宽相结合,意味着这个特定的芯片恰好能够从单个线程饱和其内存带宽,这会显著改变行为
    • rep-movsd似乎使用了与此芯片上的rep-movsbmovsb,并且在早期架构上的早期测试中,ERMSB显示movsb的执行速度比 快得多。这主要是学术性的,因为 movsbmovsd更普遍

    查看iwillnotexist在评论中提供的Haswell结果,我们看到了相同的总体趋势(提取了最相关的结果):

     C copy                                               :   6777.8 MB/s (0.4%)
     standard memcpy                                      :  10487.3 MB/s (0.5%)
     MOVSB copy                                           :   9393.9 MB/s (0.2%)
     MOVSD copy                                           :   9155.0 MB/s (1.6%)
     SSE2 copy                                            :   6780.5 MB/s (0.4%)
     SSE2 nontemporal copy                                :  10688.2 MB/s (0.3%)
    

    rep movsb方法仍然比非暂时性memcpy

    最后,试探一下你的实际问题:你应该什么时候或者为什么使用它?它借鉴了上述内容,并引入了一些新的想法。不幸的是,没有一个简单的答案:你必须权衡各种因素,包括一些你甚至可能无法确切了解的因素,例如未来的发展。

    请注意,代表 movsb 的替代方法可能是优化的 libc memcpy(包括由编译器内联的副本),也可能是手动滚动的 memcpy 版本。下面的一些好处仅适用于这些替代方案中的一种或另一种(例如,“简单性”有助于对抗手滚版本,但不适用于内置的memcpy),但有些适用于两者。

    在某些环境中,对某些指令或使用某些寄存器有限制。例如,在Linux内核中,通常不允许使用SSE/AVX或FP寄存器。因此,大多数优化的memcpy变体不能使用,因为它们依赖于SSE或AVX寄存器,并且在x86上使用基于64位的普通mov副本。对于这些平台,使用rep movsb允许优化的memcpy的大部分性能,而不会破坏对SIMD代码的限制。

    一个更一般的例子可能是必须针对多代硬件的代码,并且不使用特定于硬件的调度(例如,使用cpuid)。在这里,您可能被迫只使用较旧的指令集,这排除了任何AVX等。rep movsb可能是一个很好的方法,因为它允许在不使用新指令的情况下“隐藏”访问更广泛的负载和存储。如果您针对ERMSB之前的硬件,您必须查看那里的rep movsb性能是否可以接受…

    rep-movsb的一个很好的方面是,理论上,它可以利用未来架构的架构改进,而无需更改源代码,这是显式移动所不能做到的。例如,当引入256位数据路径时,rep movsb能够利用这些路径,而无需对软件进行任何更改。使用128位移动的软件(在Haswell之前是最佳的)必须进行修改和重新编译。

    因此,它既是软件维护的好处(无需更改源代码),也是现有二进制文件的好处(无需html" target="_blank">部署新的二进制文件来利用改进)。

    这有多重要取决于您的维护模型(例如,在实践中部署新二进制文件的频率)以及很难判断这些指令在未来可能有多快。不过,至少英特尔通过promise在未来至少实现合理的性能(15.3.3.6),在某种程度上指导了这一方向的使用:

    代表移动硬盘和代表 STOSB 将继续在将来的处理器上表现良好。

    当然,这种好处不会出现在普通的memcpy基准测试中,根据定义,它没有后续工作要重叠,因此必须在实际场景中仔细衡量好处的大小。要最大限度地发挥优势,可能需要围绕memcpy重新组织代码。

    英特尔在其优化手册(第11.16.3.4节)中指出了这一好处,并用他们的话来说:

    当已知计数至少为一千字节或更多时,使用增强的REP MOVSB/STOSB可以提供另一个优势来摊销非消耗代码的成本。启发式可以使用Cnt=4096和memset()的值来理解,例如:

    memset()的256位SIMD实现需要使用VMOVDQA发出/执行128个32字节存储操作实例,然后非消耗指令序列才能退出。

    ECX= 4096的增强型REP STOSB实例被解码为硬件提供的长微操作流,但作为一条指令失效。在使用memset()的结果之前,必须完成许多store_data操作。因为存储数据操作的完成与程序顺序引退是分离的,所以非消耗性代码流的实质部分可以通过发布/执行和引退来处理,如果非消耗性序列不竞争存储缓冲器资源,则基本上是无成本的。

    因此,英特尔表示,尽管在rep movsb发布了一些uop之后的代码,但尽管许多商店仍在运营,而且rep movesb作为一个整体还没有退役,但来自以下指令的uop在无序机器中比在复制循环之后的代码更能取得进展。

    来自显式加载和存储循环的uops实际上都必须按照程序顺序单独退役。这必须在ROB中为跟踪uops腾出空间。

    确切地说,关于rep-movsb这样的微码指令的工作时间有多长,似乎没有太多详细的信息。我们不知道微码分支如何从微码序列器请求不同的uop流,也不知道uop是如何失效的。如果单个uop不必单独退役,那么整个指令可能只占用ROB中的一个插槽?

    当为OoO机器提供信息的前端在uop缓存中看到rep-movsb指令时,它会激活微码序列器ROM(MS-ROM),将微码uop发送到为问题/重命名阶段提供信息的队列中。当rep movsb仍在发出时,任何其他uop可能都无法与之混合并发出/执行8,但后续指令可以在最后一个rep movesbuop发出后提取/解码并发出,而一些拷贝尚未执行。只有当您的后续代码中至少有一部分不依赖于memcpy的结果时,这才有用(这并不罕见)。

    现在,这种优势的大小是有限的:除了慢rep movsb指令之外,最多可以执行N条指令(实际上是uops),此时将暂停,其中N是ROB大小。当前ROB大小约为200(Haswell上为192,Skylake上为224),IPC为1的情况下,后续代码最多可免费工作约200个周期。在200个周期内,您可以以10 GB/s的速度复制大约800字节的内容,因此,对于这种大小的副本,您可以获得接近于复制成本的免费工作(在某种程度上使副本免费)。

    然而,随着副本大小变得越来越大,其相对重要性迅速下降(例如,如果您要复制80 KB,则免费工作仅为复制成本的1%)。尽管如此,对于中等大小的副本来说,它仍然非常有趣。

    复制循环也不会完全阻止后续指令的执行。英特尔没有详细说明好处的大小,也没有详细说明什么样的拷贝或周围的代码最有好处。(热或冷目标或源,高ILP或低ILP后的高延迟代码)。

    与典型的优化memcpy例程相比,执行的代码大小(几个字节)很小。如果性能受到i-cache(包括uop缓存)未命中的限制,那么减少代码大小可能会有好处。

    同样,我们可以根据副本的大小来确定这种好处的大小。我不会用数字来计算,但直觉是,将动态代码大小减少B字节最多可以节省C*B缓存未命中,对于某些常量来说。每次调用memcpy都会产生一次缓存未命中成本(或好处),但更高吞吐量的优势会随着复制的字节数而扩大。因此,对于大型传输,更高的吞吐量将主导缓存效应。

    同样,这不是在普通基准测试中显示的内容,其中整个循环无疑将适合uop缓存。您需要一个真实的现场测试来评估这种效果。

    您报告说,在您的硬件上,rep movsb比平台memcpy

    这完全是合理的,因为字符串移动操作似乎会周期性地受到欢迎,但不是每一代都会受到欢迎,所以它可能会更快,或者至少会与最新的体系结构捆绑在一起(在这一点上,它可能会因其他优势而获胜),但只会落后于后续硬件。

    引述Andy Glew的话,他在P6上实现这些之后应该对此有所了解:

    在微码中做快速字符串的最大弱点是[…]微码每一代都走调,越来越慢,直到有人设法修复它。就像图书馆的人拷贝走调一样。我想错过的机会之一可能是在128位加载和存储可用时使用它们,等等。

    在这种情况下,它可以被视为另一种“特定于平台”的优化,可以应用于您在标准库和JIT编译器中找到的memcpy例程一书中的典型每一个技巧:但只能用于更好的架构。对于JIT或AOT编译的东西,这很容易,但对于静态编译的二进制文件,这确实需要特定于平台的调度,但这通常已经存在(有时在链接时实现),或者可以使用mtune参数进行静态决策。

    即使在Skylake上,它似乎已经落后于绝对最快的非时态技术,但它仍然比大多数方法都快,而且非常简单。这意味着更少的验证时间、更少的神秘错误、更少的调整和更新monster memcpy实现的时间(或者相反,更少地依赖于标准库实现者的突发奇想,如果你依赖于它的话)。

    内存吞吐量限制算法实际上可以在两个主要的整体机制中运行:DRAM带宽限制或并发/延迟限制。

    第一种模式是您可能熟悉的模式:DRAM子系统具有一定的理论带宽,您可以根据通道数、数据速率/宽度和频率轻松计算。例如,我的双通道DDR4-2133系统的最大带宽为2.133*8*2=34.1 GB/s,与ARK上报告的相同。

    在插槽上的所有核心之间添加的DRAM的速率不会超过该速率(由于各种低效率,通常会稍低一些)(即,这是单插槽系统的全局限制)。

    另一个限制是内核实际可以向内存子系统发出多少并发请求。想象一下,如果一个内核一次只能有一个正在进行的请求,对于一个64字节的缓存行——当请求完成时,你可以发出另一个请求。还假设非常快的50ns内存延迟。然后,尽管DRAM带宽很大,但你实际上只能得到64字节/50 ns=1.28 GB/s,或者不到最大带宽的4%。

    在实践中,内核一次可以发出多个请求,但不是无限的数量。通常可以理解的是,L1和内存层次结构的其余部分之间每个内核只有10个行填充缓冲区,L2和DRAM之间可能有16个左右的填充缓冲区。预取竞争相同的资源,但至少有助于减少有效延迟。有关更多详细信息,请查看带宽博士就该主题撰写的任何精彩帖子,主要是在英特尔论坛上。

    尽管如此,大多数最新的CPU受到这个因素的限制,而不是RAM带宽。通常,它们每个内核的速度为12 - 20 GB/s,而RAM带宽可能为50 GB/s(在4通道系统上)。只有一些最近的第二代通道“客户端”内核,似乎有更好的非核,也许更多的行缓冲区可以达到单个内核上的DRAM限制,我们的Skylake芯片似乎就是其中之一。

    当然,英特尔设计具有50 GB/s DRAM带宽的系统是有原因的,同时只是为了维持

    为什么我一直在讨论这个问题?因为最好的memcpy实现通常取决于您在哪个系统中运行。一旦您的DRAM BW受到限制(就像我们的芯片显然是这样,但大多数都不在一个内核上),使用非时间写入就变得非常重要,因为它节省了通常浪费1/3带宽的读取所有权。您可以在上面的测试结果中看到这一点:不使用NT存储的memcpy实现会损失1/3的带宽。

    然而,如果并发性受到限制,则情况会均衡,有时甚至相反。您有多余的DRAM带宽,因此NT存储没有帮助,甚至可能会造成伤害,因为它们可能会增加延迟,因为线路缓冲区的切换时间可能比预取将RFO线路引入LLC(甚至L2),然后在LLC中完成存储以有效降低延迟的情况要长。最后,服务器uncore的NT存储往往比客户端存储慢得多(而且带宽高),这加剧了这种影响。

    因此,在其他平台上,您可能会发现NT商店不太有用(至少当您关心单线程性能时),也许rep movsb会在哪里获胜(如果它两全其美)。

    真的,最后一项是对大多数测试的呼吁。我知道NT存储在大多数arch(包括当前服务器arch)上失去了单线程测试的明显优势,但我不知道rep movsb将如何相对执行…

    其他未集成到上述内容中的良好信息来源。

    对< code>rep movsb与备选方案的比较研究。许多关于分支预测的好笔记,以及我经常为小块建议的方法的实现:使用重叠的第一次和/或最后一次读/写,而不是试图只写所需的字节数(例如,将9到16字节的所有副本实现为两个8字节的副本,这两个副本可能重叠多达7个字节)。

    1大概目的是将其限制在例如代码大小非常重要的情况下。

    < sup>2请参阅第3.7.5节:REP前缀与数据移动。

    3需要注意的是,这仅适用于单个指令本身中的各种存储:一旦完成,存储块仍然看起来相对于先前和后续存储是有序的。因此,代码可以看到rep movs中的存储相对于彼此是无序的,而不是相对于先前或后续存储(这是您通常需要的后一种保证)。只有当您使用复制目标的末尾作为同步标志而不是单独的存储时,才会出现问题。

    4请注意,非临时离散存储也避免了大多数订购要求,尽管实际上rep-movs有更多的自由度,因为WC/NT存储上仍有一些订购限制。

    < sup>5这在32位时代的后期很常见,当时许多芯片都有64位数据路径(例如,为了支持支持64位< code>double类型的fpu)。今天,Pentium或Celeron等“中性”芯片已经禁用了AVX,但想必< code>rep movs微码仍然可以使用256b加载/存储。

    例如,由于语言对齐规则、对齐属性或运算符、别名规则或在编译时确定的其他信息。在对齐的情况下,即使无法确定准确的对齐,他们至少可以将对齐检查从循环中提升出来,或者消除多余的检查。

    7我假设“标准”memcpy正在选择一种非时间方法,这很可能适用于这种大小的缓冲区。

    < sup>8这不一定是显而易见的,因为有可能是这样的情况,即由< code>rep movsb生成的uop流简单地独占调度,然后它看起来非常像显式的< code>mov情况。然而,它似乎并不是这样工作的——来自后续指令的微操作可能与来自微代码< code>rep movsb的微操作混合在一起。

    < sup>9,即那些可以发出大量独立内存请求并因此使可用的DRAM到内核带宽饱和的处理器,其中< code>memcpy将是一个典型的子处理器(与指针追踪等纯延迟负载相反)。

  •  类似资料:
    • 我正在逐个迭代字符串对象列表中的元素: 在这里,每次我调用list上的get()时,列表都会从其一端一直迭代到第i个元素——因此上面循环的复杂性是O(n^2)。 是a.)对于增强型for循环,与上面相同,还是b.)对于循环,将指针保持在最后一个指针所在的位置,因此下面循环的复杂性是O(n)? 如果上面的情况(b)——我想是这样的——在列表上使用迭代器有什么好处吗。这是简单的迭代--没有回头路 蒂亚

    • memcpy(拷贝内存内容) 相关函数 bcopy,memccpy,memcpy,memmove,strcpy,strncpy 表头文件 #include<string.h> 定义函数 void * memcpy (void * dest ,const void *src, size_t n); 函数说明 memcpy()用来拷贝src所指的内存内容前n个字节到dest所指的内存地址上。与strc

    • memcpy 拷贝内存内容 相关函数 bcopy,memccpy,memcpy,memmove,strcpy,strncpy 表头文件 #include<string.h> 定义函数 void *memcpy(void *dest, const void *src, size_t n); 函数说明 memcpy()用来拷贝src所指的内存内容前n个字节到dest所指的内存地址上。与strcpy

    • 主要内容:Java11 增强String的API 的示例Java 11 为 String 引入了多项增强功能。 String.repeat(int) : 重复给定次数的字符串。返回连接的字符串。 String.isBlank() :检查字符串是否为空或只有空格。 String.strip() : 删除前导和尾随空格。 String.stripLeading() : 删除前导空格。 String.stripTrailing() : 删除尾随空格。 Str

    • 我可以使用vuforia的增强现实并在应用商店和PlayStore上传吗?

    • ZGC 或 Z 垃圾收集器是在 Java 11 中引入的,作为一种低延迟垃圾收集机制。ZGC 确保垃圾收集暂停时间不依赖于堆大小。无论堆大小是 2MB 还是 2GB,它都不会超过 10 毫秒。 但是 ZGC 在将未使用的堆内存返回给操作系统方面存在限制,例如 G1 和 Shenandoah 等其他 HotSpot VM GC。以下是使用 Java 13 完成的增强功能: ZGC 默认将未提交的内存