如何使用std::vector等动态数据结构并防止分页

How to use dynamic data structures like std::vector and prevent paging ?

本文关键字:分页 数据结构 何使用 std vector 动态      更新时间:2023-10-16

只有在足够的内存可用的情况下,我才会跟进这个问题在更一般的意义上重新表述这个问题。

考虑这个片段:

vector<double> v1;
cout << "pushing back ..." << endl;
while (true) {
try {
v1.push_back(0.0);
} catch (bad_alloc& ba){
cout << "bad_alloc caught: " << ba.what() << endl;
break;
}
}

关于上述代码片段,下列哪个语句是正确的?

1)最终到达catch块

2)你不能事先确定是否有足够的内存让push_back不抛出bad_alloc

catch块中涉及内存分配的每个操作都可能失败,因为没有剩余的内存

我做的第一件事是在Windows上运行这个程序,这导致观察到在任何分页发生之前,抛出bad_alloc,因为显然每个进程的内存量已经超过了。这个观察结果导致下一个语句:

4)在大多数操作系统上,bad_alloc将在分页发生之前抛出,但是没有确定的方法事先告诉。

经过一番研究,我对上述陈述有以下想法:

A1) True,捕获块将被到达,但可能不会在操作系统由于分页而执行密集的I/O操作之前到达。

A2)正确,至少不是以操作系统独立的方式

A3)真的,你必须预先分配内存,以便对迄今为止收集的向量中的数据做一些有用的事情(例如,如果你发现这有用,你自己做一些分页)

A4)正确,这取决于多个操作系统特定的参数,如每个进程的最大内存量,进程优先级,操作系统进程调度程序的策略等…

我不确定A1-A4是否正确,因此我的问题,但如果是这样,下面是下一个陈述:

5)如果你需要写一些算法,并确保不会有分页,不要使用像std::vector这样的动态数据结构。相反,使用Array,并使用特定于操作系统的函数确保它将留在内存中,例如mlockall(Unix)

如果5)为真,则导致最后一个语句:

6)没有独立于操作系统的方法来编写不会导致分页的程序。

提前感谢大家分享你对上述陈述的看法。

如果您的程序必须在Windows/Unix/OS X上运行,则创建一个包装函数:

bool lockMemoryRegion( void *addr, size_t size )
{
#ifdef WIN32
return VirtualLock( addr, size ) != 0;
#else
return mlock( addr, size ) == 0;
#endif
}
bool unlockMemoryRegion( void *addr, size_t size )
{
#ifdef WIN32
return VirtualUnlock( addr, size ) != 0;
#else
return munlock( addr, size ) == 0;
#endif
}

如果你需要锁定std::vector使用的内存:

std::vector<int> v( 1000 );
lockMemoryRegion( v.data(), v.capacity() * sizeof (int) );

只在需要的时候才使用内存锁。将页面锁定到内存中可能会降低系统的性能,因为这会减少可用的RAM,并迫使系统将其他关键页面交换到分页文件中。

真是个乱七八糟的问题。您仍然需要了解您真正感兴趣的操作系统上的现代内存分配。我建议你进行一些系统的背景阅读,因为对你那些大杂烩式的问题的回答不一定能给你一个全面的了解。

1)最终到达catch块

2)你不能事先确定是否有足够的内存让push_back不抛出bad_alloc

catch块中涉及内存分配的每个操作都可能失败,因为没有剩余的内存

这些都不一定是真的…操作系统可以分配虚拟地址空间,然后在进程被访问并且操作系统找不到物理内存来支持它时终止进程。此外,低内存进程杀手可能会认为您已经推得太远,并终止您或任何其他非关键进程。

对于3),标准明确地说实现可以使用单独的内存区域将抛出的对象传递给将处理它的catch语句——毕竟,在异常处理期间展开的堆栈上放置对象是没有意义的。因此,与动态内存分配(使用new或malloc)相比,这种内存分配的问题要少得多,但在非常罕见的情况下,仍然可能出现分页,从而导致进程终止。在内部抛出的对象进行动态内存分配(例如,在字符串或istringstream数据成员中存储描述)仍然是危险的。类似地,catch语句可以为变量、表达式求值、函数调用等分配堆栈空间——它们也可能导致失败,但比new/malloc更危险。

4)在大多数操作系统上,在分页发生之前会抛出bad_alloc,但是没有确定的方法可以预先告诉。

当然不是——那么分页有什么意义呢?

A1) True,捕获块将被到达,但可能不会在操作系统由于分页而执行密集的I/O操作之前到达。

如果碰巧有交换磁盘在使用,那么你应该在内存不足的情况发生之前进行分页,但这可能不会表现为异常。

A2)正确,至少不是以操作系统独立的方式

不…这一开始就不是真的。

A3)正确,你必须预先分配内存,以便对迄今为止收集的向量中的数据做一些有用的事情(例如,如果你发现这有用,你自己做一些分页)

你不需要预先分配任何东西…这将通过构造函数参数或resize来完成…这是可选的,但可能允许您处理更多的数据而不会遇到内存不足的情况,因为当数据被移动到更大的内存块时,不太需要暂时增加内存使用。所有这些都与您是否"做一些有用的事情"无关,而且我不知道您对"自己执行一些分页"的想法是什么。如果访问vector元素,则可能需要对它们进行分页。如果你有一段时间没有使用它们,它们可能会被换掉。操作系统缓存算法决定了这一点。你可能至少需要了解这种类型的一个简单算法,比如least Recently Used (LRU)。

A4)正确,这取决于多个操作系统特定的参数,如每个进程的最大内存量,进程优先级,操作系统进程调度程序的策略等…

您可以设置每个进程的内存分配限制,但是您认为只有超过该限制才会进行分页的概念是错误的。分页可以发生在进程的任何部分——动态分配、堆栈、可执行映像、静态数据、线程特定数据等——只要操作系统看到它有一段时间没有被使用,并且需要将物理内存用于其他更紧迫的目的。

你的问题清楚地表明,以下假设是以前面的假设的真实性为条件的,但我将迅速解决它们,因为它们具有真实性和/或相关性的元素....

5)如果你需要写一些算法,并确保不会有分页,不要使用像std::vector这样的动态数据结构。相反,使用Array并确保它将使用特定于操作系统的函数(例如mlockall (Unix)

)保留在内存中。

你使用哪种类型的数据类型/容器是无关紧要的-操作系统甚至不知道或关心你把它授予进程的内存的不同部分用于什么用途。因此,这样的函数可以应用于数组或动态分配的内存——例如,如果您已经填充了一个向量,那么您可以使用.data()来获得一个指向存储数据的实际内存区域的指针,然后将其锁定到物理RAM中。当然,如果你强迫vector去寻找一个不同的内存区域(例如,在capacity()之外添加元素),那么它仍然会寻找更多的内存,并且在物理内存中锁定一些已删除的内存区域可能会对进程和系统性能产生不利影响。

如果5)为真,则导致最后一条语句:

6)没有独立于操作系统的方法来编写不会导致分页的程序。

不,没有。分页意味着对进行分页的进程是透明的,并且进程很少需要避免分页。

1,2,3都是正确的,假设2指的是可移植的方式。您可以根据特定于操作系统的进程内存使用报告功能做出适当的猜测。它们不那么准确,也不便携,但它们确实提供了相当好的猜测。

至于4,这是不正确的。它是物理内存量与进程的虚拟地址空间大小的函数。X64的地址空间比物理内存大得多。x86现在要小得多,但几年前那些2GB或1GB内存的老机器会更大。

如果你需要写一些算法,并确保没有分页,不要使用像std::vector这样的动态数据结构。而不是使用Array并确保它将保留在使用特定于操作系统的内存中函数,例如mlockall (Unix)

废话。您可以保留vector来分配所需的所有内存,然后调用mlock

但是,几乎可以肯定,没有一种独立于操作系统的方法可以编写一个不会导致分页的程序。分页是c++使用的平面内存模型的一个实现细节,当然没有与此实现细节相关的标准功能,将来也不会有。

1)最终到达catch块

这个"最终"并不意味着"当你分配到字节时",而是更多(虚拟内存映射-如果存在-也必须耗尽)。

大约十年前,我看到一个linux进程调度程序有一个习惯,它会杀死行为不端的应用程序。我认为这个应用程序符合条件(也就是说,它可能在到达catch块之前被操作系统终止)。

3) catch块中涉及内存分配的每个操作都可能失败,因为没有剩余的内存

理论上正确,实际上可能错误。向量将继续分配越来越大的连续块。当它这样做时,它可能不再能够分配一个大块,但是以前较小的分配已经释放了。如果在catch块中可能有一些可用的空闲内存

4)在大多数操作系统上,bad_alloc会在分页发生之前被抛出,但是没有确定的方法提前告诉。

既然没有办法事先知道,唯一现实的方法就是测量它。

5)如果你需要写一些算法,并确保不会有分页,不要使用像std::vector这样的动态数据结构。相反,使用Array并确保它将使用特定于操作系统的函数(例如mlockall (Unix)

)保留在内存中。

这是不正确的。向量是分配的连续内存块上的安全包装器。你也可以使用vector和内存锁定函数。

For(6):分页取决于硬件、操作系统和应用程序(您可以在两个不同的系统上运行相同的应用程序,并对其进行不同的分页)。