.Net 中的页面错误和编程
我正在读一本关于操作系统的书,它给出了一些我最理解的 C 语言示例。我现在看到的示例显示了两段几乎相同的代码,它们将在虚构的系统上运行...
int i, j;
int [128] [128] data;
for (j = 0; j < 128; j++)
for (i = 0; i < 128; i++)
data [i] [j] = 0;
而第二段代码
int i, j;
int [128] [128] data;
for (i = 0; i < 128; i++)
for (j = 0; j < 128; j++)
data [i] [j] = 0;
在这个特定的系统上,第一部分代码将导致 16k 页面错误,而第二部分代码将导致 16k 页面错误结果只有 128。
如果这是一个愚蠢的问题,我很抱歉,但根据我使用 .NET 的经验,我一直在很大程度上不了解内存。我只是创建一个变量,它在“某处”,但我不知道在哪里,我不在乎。
我的问题是,.NET 如何与这个虚构系统中的这些 C 示例进行比较(页面大小为 128 个字,数组的每一行占用一个整页。在第一个示例中,我们在第 1 页设置一个 int,然后在第 1 页设置一个 int在第 2 页上,等等....而第二个示例在第 1 页上设置所有整数,然后在第 2 页上设置所有整数,等等...)
另外,虽然我认为我理解为什么代码会产生不同级别的分页,但有吗我能做的任何有意义的事情用它做什么?页面的大小不是取决于操作系统吗?是否可以得出这样的结论:作为尽可能连续地访问数组中的内存的一般经验法则?
I'm reading a book about Operating Systems and it gives some examples in C that I mostly understand. The example I'm looking at now shows two nearly identical pieces of code that will run on a fictitious system...
int i, j;
int [128] [128] data;
for (j = 0; j < 128; j++)
for (i = 0; i < 128; i++)
data [i] [j] = 0;
And the second piece of code
int i, j;
int [128] [128] data;
for (i = 0; i < 128; i++)
for (j = 0; j < 128; j++)
data [i] [j] = 0;
On this particular system the first section of code would result in 16k page faults, while the second would result in only 128.
My apologies if this is a silly question, but in my experiences with .NET I've always been largely unaware of memory. I just create a variable and it is 'somewhere' but I don't know where and I don't care.
My question is, how would .NET compare to these C examples in this fictional system (pages are 128 words in size, each row of the array takes one full page. In the first example we set one int on page 1, then one int on page 2, etc....while the second example sets all ints on page 1, then all ints on page 2, etc...)
Also, while I think I understand why the code produces different levels of paging, is there anything meaningful I can do with it? Isn't the size of the page dependent on the operating system? Is the take away that, as a general rule of thumb to access memory in an array as contiguously as possible?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
发布评论
评论(3)
您的两个示例是相同的,但大概您想要的是:
int i, j;
int [128] [128] data;
for (i = 0; i < 128; i++)
for (j = 0; j < 128; j++)
data [i] [j] = 0; // <--- i,j
第二段代码
int i, j;
int [128] [128] data;
for (i = 0; i < 128; i++)
for (j = 0; j < 128; j++)
data [j] [i] = 0; // <--- j,i
因此,假设页面大小为 128 * sizeof(int):
在一种情况下,您将按顺序迭代数组。在这种情况下,最坏情况下的页错误数是数组的大小(以页为单位)= 1。
在另一种情况下,每次迭代都会跳转到新页面,因此在最坏的情况下,您可能会出现页错误在循环的每次迭代中。
.NET 中的情况完全相同。
页面错误的数量取决于您访问的不在内存中的页面数量。
堆栈上的页面更有可能位于内存中,除非这是第一次以这种程度使用堆栈。
假设您的页面大小为 4 KB(在许多系统上都是如此),并且没有页面位于内存中,那么您将访问 128*128*4 字节或 64 KB,即 16 个页面。注意:如果该结构不是从页边界开始,则该结构可能跨越 17 页,在这种情况下,第一页将位于内存中。
只要您访问每个页面,如何访问它们或以什么顺序访问它们并不重要。
您可能正在谈论缓存未命中,并且访问数据的顺序可能会产生影响。在这种情况下,最好沿着最后一个索引增量访问数据。例如,对于 a[i][j]
您希望内部循环使用 j
但是,如果您的缓存足够大,这可能并不重要。对于不适合更快缓存的大型结构来说,它可以产生很大的不同。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
虚构的操作系统对于演示原理很有用,但据我所知,没有一个真实的操作系统实际上是这样工作的。如果页面在使用后立即取消映射,则只会出现 16K 页面错误。虽然这种情况在技术上是可能发生的,但机器必须处于高负载状态,RAM 严重不足,无法支持正在运行的进程集。到那时你就不再关心性能了,无论如何,机器都会慢下来。其技术术语是“抖动”。
这段代码中发生了一些您没有提到的更重要的事情。 1 级 CPU 缓存对于访问数组元素的速度起着重要作用。第一次访问时,缓存行(通常为 64 字节)会从内存中填充。访问下一个内存地址处的下一个元素非常便宜,数据已经在缓存中。访问外部索引首先发生变化的数组非常昂贵,它需要另一个缓存行填充,这可能需要数百个周期,最坏的情况。由于驱逐也包含数组数据的现有高速缓存行的风险相当大,因此第一级高速缓存不是很大。正确实现这一点需要了解运行时如何对内存中的数组元素进行排序。
Fictitious operating systems can be useful to demonstrate principles, but no real operating system I know actually works this way. You could only get 16K page faults if the page immediately gets unmapped after use. While it is technically possible for this to happen, the machine would have to be under major load, being desperately out of RAM to support the set of running processes. At that point you just don't care about perf anymore, the machine will have slowed down to a crawl anyway. The technical term for that is "thrashing".
There's something much more important going on in this code that you didn't mention. The level 1 CPU cache plays a major role in how quickly you can access array elements. A cache line (typically 64 bytes) gets filled from memory on the first access. Accessing the next element, at the next memory address, is very cheap, the data is already in the cache. Accessing the array with the outer index changing first is very expensive, it requires another cache line fill which can take hundreds of cycles, worst case. With the considerable risk of evicting an existing cache line that also contains array data, the 1st level cache is not very big. Getting this right requires knowing how the runtime orders array elements in memory.