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

时间:2008-10-05 12:46:16

标签: c++ c optimization memory

上下文:

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

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

引用:

  

所有这一切都有一个非常深刻,悲伤的认识。我们是在2001年,即Space Odyssey的一年。 (...)开箱即用,看看我们 - 50年后,我们仍然不擅长填写和复制记忆。

问题:

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

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

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

  • 缓冲区初始化可能占某些算法总运行时间的20%-40%。
  • 平台可能在未来5年内有所不同,但它将保留在“可以从DELL购买的最快CPU资金”类别中。编译器将是某种形式的GCC和Visual C ++。
  • 我想听听那些在MMX和SSE出现时不得不更新软件的人,因为当“SSE2015”变成“SSE2015”时,我必须这样做可用...... :)

12 个答案:

答案 0 :(得分:10)

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

  

有一些神圣不可侵犯的东西   C的内存操作函数   memset,memcpy和memcmp。他们是   可能会被高度优化   编译器供应商,在某种程度上   编译器可能会检测到的调用   这些功能并替换为   内联汇编程序指令 - 这个   是MSVC的情况。

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

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

答案 1 :(得分:8)

MASM Forum有许多令人难以置信的汇编语言程序员/爱好者,他们将这个问题彻底打死(看看实验室)。结果非常像克里斯托弗的反应:SSE对于大型对齐缓冲区来说是令人难以置信的,但是下降到最终会达到如此小的尺寸,基本的for循环也同样快。

答案 2 :(得分:5)

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

但是要把它减少到一个列表:

  1. 对于数据集< =几百千字节memcpy / memset比你可以模拟的任何东西都要快。
  2. 对于数据集>兆字节使用memcpy / memset的组合来获得对齐,然后使用您自己的SSE优化例程/回退来优化来自Intel等的例程。
  3. 在启动时强制对齐并使用您自己的SSE例程。
  4. 此列表仅适用于需要演奏的场合。太小/或一次初始化的数据集不值得麻烦。

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

答案 3 :(得分:4)

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

答案 4 :(得分:4)

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

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

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

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

答案 5 :(得分:2)

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

http://liboil.freedesktop.org/

答案 6 :(得分:1)

这一切都取决于你的问题领域和你的规格,你是否遇到了性能问题,未能满足时间期限和精确定位的memset是所有邪恶的根源?如果是这样你就可以考虑进行一些memset调整。

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

我曾经遇到同样的memcmpt,其中对齐开销导致了一些问题,通常这不会导致奇迹,只有很小的改进,如果有的话。如果您错过了一个令人满意的订单,那么这将不会让您更进一步。

答案 7 :(得分:1)

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

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

HTH

答案 8 :(得分:1)

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

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

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

答案 9 :(得分:1)

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

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

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

答案 10 :(得分:1)

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

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

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

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

答案 11 :(得分:0)

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