x86 Assembly Force缓存存储

时间:2013-09-02 17:38:00

标签: c caching assembly x86

我有一项任务,我需要测量访问L1,L2和L3缓存中的数据的延迟,以及主内存。这将在C中完成。

我花了几个小时研究测量缓存延迟的方法,并且调整得非常少。我已经下载了一些基准测试工具,这些工具给了我缓存访问时间,但在我自己的代码中实现它时,我还没有得到任何结论。我知道缓存中发生的事情在C中不适合我。

我的下一个想法是,如果我可以强制使用来自x86程序集的东西填充缓存(首先想到)然后只对我刚加载的数据做一个clock(),access(),clock(),假设时间会是准确的(ish)访问时间,因为我知道它应该在缓存中找到,因为我只是用我的内联asm或类似的方法把它放在那里...

如果有人能够在这里为我的作业提供见解,那就太棒了。是否告诉我因为想要使用asm在缓存中加载某些东西而疯狂,或者向我介绍可能对我有帮助的东西。

非常感谢!

3 个答案:

答案 0 :(得分:8)

根本没有理由使用汇编进行此分配。您的解决方案不需要程序集C也可以正常工作。我假设您在操作系统之上运行,因此这会妨碍您的测量,既可以执行您认为自己知道的位置,也可以测量您认为自己正在测量的内容。

关于采用这些测量的缓存基础知识......假设有四层内存。 L1,最快,但也最贵,最小。 L2然后。较慢,不那么昂贵,可能比L1大。 L3更便宜,更慢,更大,然后主内存最慢,最便宜,最大。

让我们说我们有四块内存,我们将使用A,B,C和D.L1一次只能容纳一个块。 L2一次两个,L3中的三个和主存全部四个。

如果我们进行读取,它首先通过L1,如果有未命中则L2,如果未命中则L3,如果未命中,则它将始终在主存储器中。理解虽然这些数据在返回途中被缓存,因此L3,L2和L1都将包含刚刚读取的数据,必要时进行驱逐(并非总是如此,但假设这个简单的缓存模型可以理解如何完成任务)。因此,如果我们读取块A,那么L1,L2和L3都将包含块A.现在在这个假设模型中,如果我们读取块B,那么L1将包含B,驱逐A.L2将包含A和b,l3将包含A和B.读C和L1将包含C,驱逐B,假设L2选择驱逐A,并包含B和C,L3包含A,B和C.读D和L1将包含C让我们说L2驱逐B并且包含C和D,并且说L3驱逐A并且包含B,C和D.

假设我们并不完全知道每个缓存如何选择要驱逐的内容以及要保留的内容。但是假设我们知道或者可以从主板规格或其他来源中找出每个缓存有多大。如果上述例子按此顺序发生且L1具有D,则L2具有C和D,L3具有B,C和D,并且main具有全部四个a,b,c,d。然后,如果在那个状态下我们读取所有块A和时间它我们理论上从主存储器中读取它,它不仅仅是读取该存储器的时间,而且如果任何被驱逐的存储器已经改变它必须写在上游可能的命中一路。但理想情况下,如果你大部分时间只是阅读,那么你将主要阅读时间。

让我们说我们发现自己处于大块D位于l1中的情况,c和d位于l3中的l2,b,c,d中,我们读取了所有的块B并计时。这不是衡量访问L3的时间吗?使用相同的起始条件然后读取C将给我们l2时间。使用相同的起始条件然后读取D将是l1时序对吗?

诀窍是让自己进入这些条件。缓存的大小可能不会使l2的大小是l1的两倍,依此类推,因此要完全控制L1中的内容,您需要读取足够的数据来填充L1。此外,如果您要读取L3大小的数据量,那么理论上L3具有所有数据,L2具有该数据的最后L2量,而L1具有该数据的最后L1量。

使用数据缓存比指令缓存更容易,但您可以这样做,在主内存中至少需要L3大小的指令,大量的nops。执行线性指令块与读取线性内存块没有什么不同。就读周期而言。就启用和使用I缓存而言,指令更容易。根据您的操作系统以及如何管理内存,启用数据缓存可能也可能不简单。

答案 1 :(得分:1)

您应该能够通过查看编译器的汇编器输出来避免汇编程序,以了解实际操作。

即使您获得了高分辨率时钟,在运行基准测试时,您也无法通过操作系统进行预占。您需要执行许多运行才能获得有意义的结果。

不是试图将指令放入缓存中,而是允许处理器在运行时加载它们可能更容易。如果您在程序中放置了不同数量的填充程序,则可以将缓存行对齐到您想要的位置。

答案 2 :(得分:1)

你真的不需要在线粒度的基础上看这个,维护起来相当复杂(正如dwelch指出他非常好的答案),并且几乎不可能测量,除非重复足够的次数(反过来可以复杂维持正确的条件以强制达到某个缓存级别。)

相反,您可以从编写一个驻留在连续物理空间中的简单数组开始(如果您的操作系统具有精心设置的页面分配机制,则可能需要进行一些调整)。将数据填入此数组(每个缓存行一次访问就足够了),然后重复开始读取。 如果数组大小足够小以适合L1(例如,对于32k,你可以分配32k或更少,并访问每第64个元素),经过足够的重复,你应该获得大部分访问。您可能会遇到其他缓存行干扰的一些极端情况,例如页面映射条目,堆栈或其他堆变量,但在大多数情况下,您会获得L1命中,因此它可以很好地均衡。即使像上下文切换这样的事件(如果你无法控制你的系统以防止这种情况),如果你重复足够的次数以获得稳定的结果,也会逐渐消失。

然后,逐渐开始增加数据集大小。一旦你通过L1大小,你应该能够看到每次访问的时间明显减少(总时间除以#访问)。请注意,缓存使用LRU的方式,你以线性顺序访问你的数组的事实意味着它足够大,不适合L1,你不应该获得任何部分利益,因为最后的元素可能会驱逐第一个元素及时防止下一次迭代在那里找到它们(虽然你仍然可以享受在现代CPU中负载可能无序的事实)。 更进一步,一旦你或多或少地达到L2大小(如果你的系统中的L2不是严格包含在内,你可以从L1 + L2获得一点点好处,但所描述的访问模式应该几乎完全阻止)。然后再次击中L3尺寸。

请注意,某些硬件功能可能会使问题复杂化 - 首先是硬件预取程序,它几乎被授予启动并获取前面的行。如果可能的话你应该通过BIOS禁用它,否则你可以跳进更大的步幅(128或甚至256而不是64) - 缓存最有可能直接映射(具有一些关联性),所以这会产生强调每个只有一个的效果2-4集,其余为空,这很好(只要你记得按新的访问次数划分时间)。它也会打破流量足以让你获得实际的时间而不是预取辅助的时间(你可能也有一个基于步幅的预取器,但它通常没有流光强)。