使用现代编译器在C ++中使用“memset”function的状态

语境:

不久之前,我偶然发现了Alexandrescu撰写的2001年DDJ文章: http : //www.ddj.com/cpp/184403799

它是关于比较各种方法来初始化缓冲区到某个值。 就像“memset”对单字节值的作用一样。 他比较了各种实现(memcpy,显式“for”循环,duff的设备),并没有真正找到所有数据集大小和所有编译器的最佳候选者。

引用:

所有这一切都有一个非常深刻和悲伤的认识。 我们是在2001年,即Space Odyssey的一年。 (…)只需跳出框架看看我们 – 50年后,我们仍然不擅长填充和复制记忆。

题:

  1. 有没有人有关于这个问题的最新信息? 最近的GCC和Visual C ++实现是否比7年前表现更好?
  2. 我正在编写具有5年(可能超过10年)的生命周期的代码,并且将处理数组的大小从几个字节到几百兆字节。 我不能假设我现在的选择在5年内仍然是最优的。 我该怎么办:
    • a)使用系统的memset(或等效的)并忘记最佳性能或假设运行时和编译器将为我处理这个问题。
    • b)在各种数组大小和编译器上一劳永逸地进行基准测试,并在几个例程之间在运行时切换。
    • c)在程序初始化时运行基准测试,并在运行时根据准确的(?)数据进行切换。

编辑:我正在研究图像处理软件。 我的数组项目是POD,每毫秒都很重要!

编辑2:感谢您的第一个答案,这里有一些额外的信息:

  • 缓冲区初始化可能占某些算法总运行时间的20%-40%。
  • 该平台可能在未来5年内有所不同,尽管它将保持“最快的CPU资金可以从DELL购买”类别。 编译器将是某种forms的GCC和Visual C ++。 雷达上没有嵌入式东西或异国情调的架构
  • 我想听听那些在MMX和SSE出现时不得不更新软件的人,因为当“SSE2015”可用时我将不得不这样做…… 🙂

DDJ文章承认memset是最好的答案,并且比他试图实现的要快得多:

关于C的内存操作函数memset,memcpy和memcmp有一些神圣不可侵犯的东西。 它们可能会被编译器供应商高度优化,只要编译器可能检测到对这些函数的调用并用内联汇编程序指令替换它们 – 这就是MSVC的情况。

因此,如果memset适合您(即您使用单个字节进行初始化),则使用它。

虽然每毫秒可以计数,但您应该确定在设置内存时丢失执行时间的百分比。 考虑到您还有很多有用的工作,它可能非常低(1或2%??)。 鉴于优化工作可能会在其他地方获得更好的回报率。

MASM论坛有很多令人难以置信的汇编语言程序员/爱好者,他们完全把这个问题打败了(看看实验室)。 结果很像克里斯托弗的反应:SSE对于大型,对齐的缓冲区而言是令人难以置信的,但是下降到最终会达到如此小的尺寸,以至于基本for循环速度同样快。

Memset / memcpy主要是用基本指令集编写的,因此可以通过专门的SSE例程来表现,而另一方面,它强制执行某些对齐约束。

但要将其减少到列表:

  1. 对于数据集<=几百千字节,memcpy / memset比你可以模拟的任何东西都要快。
  2. 对于数据集>兆字节,使用memcpy / memset的组合来获得对齐,然后使用您自己的SSE优化例程/回退来优化来自Intel等的例程。
  3. 在启动时强制对齐并使用您自己的SSE例程。

此列表仅适用于需要演奏的场合。 太小/或一次初始化的数据集不值得麻烦。

这是AMD的memcpy实现,我找不到描述代码背后概念的文章。

d)接受试图在初始化时玩“jedi mind tricks”会导致程序员工作时间比一些模糊但快速的方法与明显和明显的方法之间的累积毫秒差异更多。

这取决于你在做什么。 如果你有一个非常具体的案例,你通常可以大大超过memset和memcpy的系统libc(和/或编译器内联)。

例如,对于我工作的程序,我写了一个16字节对齐的memcpy和memset,专为小数据量而设计。 memcpy仅适用于大于或等于64的多个16个大小(数据对齐为16),而memset仅适用于128个大小的多个。 这些限制使我获得了巨大的速度,并且由于我控制了应用程序,我可以根据需要定制function,并定制应用程序以对齐所有必要的数据。

memcpy的执行速度是Windows本机memcpy的大约8-9倍,将460字节的副本缩减到仅仅50个时钟周期。 memset的速度提高了2.5倍,非常快速地填充了一堆零。

如果您对这些function感兴趣,可以在这里找到它们; 对于memcpy和memset,下拉到600行左右。 他们相当微不足道。 注意它们是为那些应该在缓存中的小缓冲区而设计的; 如果你想在绕过缓存的同时在内存中初始化大量数据,那么你的问题可能会更复杂。

您可以查看liboil,它们(尝试)提供相同function的不同实现,并在初始化时选择最快。 Liboil拥有相当自由的许可证,因此您也可以将其用于专有软件。

http://liboil.freedesktop.org/

那么这一切都取决于你的问题领域和你的规范,你是否遇到了性能问题,未能满足时间期限和精确设置memset作为所有邪恶的根源? 如果是这样的话,那就是你可以考虑进行一些memset调整的唯一情况。

那么你还应该记住,memset无论如何都会因运行平台的硬件而有所不同,在这五年中,软件是否会在同一平台上运行? 在相同的架构上? 您可以尝试“滚动自己的”memset,通常使用缓冲区对齐,确保一次性取消32位值,具体取决于您的架构中性能最高的值。

我曾经遇到同样的memcmpt,其中对齐开销导致了一些问题,通常这不会导致奇迹,只有一点点改进,如果有的话。 如果您错过了您的要求,那么这将不会让您更进一步。

如果内存不是问题,那么预先创建一个你需要的大小的静态缓冲区,初始化为你的值。 据我所知,这两个编译器都在优化编译器,所以如果你使用一个简单的for循环,编译器应该生成最佳的汇编命令来复制缓冲区。

如果内存有问题,请使用较小的缓冲区并将sizeof(..)偏移量的副本复制到新缓冲区中。

HTH

我总是会选择一个初始化方法,它是我正在使用的运行时或操作系统(memset)的一部分(更糟糕的是选择一个属于我正在使用的库的一部分)。

原因:如果您正在实现自己的初始化,那么现在最终可能会得到一个稍微好一点的解决方案,但很可能在几年内运行时间得到了改进。 而且你不想做那些维护运行时的人所做的工作。

如果运行时间的改善是微不足道的,那么这一切都是有效 如果你在memset和你自己的初始化之间有一个数量级的差异,那么让你的代码运行是有意义的,但我真的怀疑这种情况。

如果你必须分配你的内存以及初始化它,我会:

  • 使用calloc而不是malloc
  • 将尽可能多的默认值更改为零(例如:让我的默认枚举值为零;或者如果布尔变量的默认值为’true’,则将其存储在结构中的反向值)

原因是calloc为你初始化内存。 虽然这将涉及归零内存的开销,但大多数编译器可能会对此例程进行高度优化 – 通过调用memcpy对malloc / new进行更优化。

与这些类型的问题一样,问题受到控制之外的因素的限制,即内存带宽。 如果主机操作系统决定开始分页内存,那么事情会变得更糟。 在Win32平台上,内存被分页,页面仅在首次使用时分配,这将在每个页面边界产生大的暂停,同时操作系统找到要使用的页面(这可能需要将另一个进程页面分页到磁盘)。

然而,这是有史以来写的绝对最快的memset

 void memset (void *memory, size_t size, byte value) { } 

不做某事总是最快的方式。 是否有任何方法可以编写算法来避免初始memset ? 您使用的算法是什么?

这一年不再是2001年了。 从那时起,Visual Studio的新版本出现了。 我花时间研究那些memset。 他们将使用SSE进行memset(当然,如果可用的话)。 如果您的旧代码是正确的,从统计上来说现在是否更快。 但你可能会遇到一个不幸的角落。 虽然我没有研究过代码,但我希望GCC能够做到这一点。 这是一个相当明显的改进,也是一个开源编译器。 有人会创建补丁。