在实时环境中使用C++预分配内存

7

我有一个函数,接收一个n字节的输入缓冲区,并需要一个n字节的辅助缓冲区来处理给定的输入缓冲区。

(我知道vector会在运行时分配内存,假设我正在使用一个使用静态预分配内存的vector。 想象一下这不是STL向量。)

通常的方法是

void processData(vector<T> &vec) {
    vector<T> &aux = new vector<T>(vec.size()); //dynamically allocate memory
    // process data
}
//usage:
processData(v)

由于我在实时环境中工作,我希望提前预分配所有需要的内存。

缓冲区仅在启动时分配一次。 我想每当我分配向量时,我的processData函数都会自动分配辅助缓冲区。

我可以使用模板函数来实现类似的功能。

static void _processData(vector<T> &vec,vector<T> &aux) {
     // process data
}
template<size_t sz>
void processData(vector<T> &vec) {
    static aux_buffer[sz];
    vector aux(vec.size(),aux_buffer); // use aux_buffer for the vector
    _processData(vec,aux);
}
// usage:
processData<V_MAX_SIZE>(v);

然而,大量使用模板并不是很有趣(现在让我们重新编译一切,因为我改了一条评论!),并且每当我使用此功能时都需要做一些簿记。

是否有更好的设计来解决这个问题呢?


1
强制性问题:您是否对代码进行了分析以证明动态内存分配确实是一个问题?我知道每个人都说你应该为实时内容预先分配所有内容,但这真的取决于您的系统。 - Michael Kristofik
2
根据需求,我过去所做的是动态分配预定大小的空间。然后将所需大小与已分配大小进行比较,如果空间不足,则重新分配。这将确保您始终拥有足够的空间,并且分配最终会停止,从而稳定系统。 - Gregor Brandt
@Kristo:“实时”意味着如果处理时间超过指定时间,则会出现错误。分析只能显示在分析运行期间发生的最坏情况,而不能显示理论上的最坏情况。只有当分配器可以保证时间上限时,动态分配才是安全的。 - Mike Seymour
1
@Mike,我会称之为“硬实时”。我目前写的是“软实时”代码(即,一些失败的帧不是问题),以谋生。我们随处动态分配内存,这不是问题。我们的硬件足够强大,可以满足我们需要做的事情。我只是试图消除OP不必要的过早优化。 - Michael Kristofik
@Kristo,首先公司政策不允许使用动态分配。但即使没有这个政策,在关键任务代码中,我需要向管理层验证我的代码是否正确。如果使用动态分配,我如何做到这一点?如何验证没有碎片化会破坏事情?我认为,对我来说,编写没有动态分配的代码比验证我的动态分配是否正确更容易。 - Elazar Leibovich
8个回答

3

我不认为你能够完全得到你所描述的东西。以下这种方式可能会是一个对你来说不错的折中方案。

void processData(vector<T>& vec)
{
    static vector<T> aux(vec.size());
    if (vec.size() > aux.size()) {
       aux.resize(vec.size());
    }
    ...
}

1
请注意,这将把内存预留从启动移动到第一次调用 - 这可能已经是一个问题。此外,两个版本通常不是线程安全的(不确定是否对OP是一个问题)。 - peterchen
这就是为什么我提到它并不完全是所要求的。这种解决方案的优点是:它没有针对每个可能大小的静态向量,这可能是一种浪费。 - AProgrammer

2
我认为您可以在启动时预分配并使用mlock()锁定足够大的内存池,然后使用带有内存池分配器的常规STL容器(例如BoostFSBA或您自己实现的)。
我曾经为我们的实时软件研究过这个问题,但测试表明,在我们的硬件上,内存分配对于我们的目的来说已经足够快了。

1
假设我正在使用一个使用静态预分配内存的向量。 那么你应该能够在编译时获取预分配内存的大小(或最大大小)。 如果这样的向量将其大小作为模板参数,则处理processData函数会更容易。
template<class T, size_t sz>
class vector 
{ 
 enum { size = sz } //either max size
...
}

template<class Vector>
static void _processData(Vector &vec,Vector &aux)
{
     // process data
}
template<class Vector>
void processData(Vector &vec) {
    static aux_buffer[Vector::size];
    //no need to pass size into constructor, as Vector type knows it already
    Vector aux(aux_buffer); // use aux_buffer for the vector
    _processData(vec,aux);
}
// usage:
vector<int, 30> v1;
vector<float, 40> v2;
//no need to specify template parameter explicitly
//every call uses its own function instance and its own buffer of different size
processData(v1);
processData(v2);

1

你需要担心的不是vector,而是内存分配器。

你可以完美地创建一个内存分配器,预先分配其内存,然后在构建向量时将其传递给它,这就是Alloc模板参数的作用!

为了确保内存没有“虚拟”分配,在分配时触摸它。

scoped_array<byte> buffer = new byte[SIZE];
memset(buffer.get(), 0, SIZE);

现在,您只需实现一个自定义分配器,它引用这个内存池并将其传递给向量实现即可 :)

自定义分配器如何知道我会调用它多少次? - Elazar Leibovich
自定义意味着您自己编写程序,因此可以根据需要进行任何功能扩展,例如记录分配和释放以供将来进行分析等。 - Matthieu M.

1

vector aux(vec.size(),aux_buffer); // 使用 aux_buffer 作为 vector 的底层数组

这是 STL 中的新特性吗?还是自定义扩展?

通常解决方案是使用自定义分配器。然而,这在代码中并不一定更美观。

一些介绍幻灯片(注意:PowerPoint!)
维基百科
谷歌


引用自己的话:“(我知道向量是在运行时分配内存,假设我正在使用一个使用静态预分配内存的向量。想象一下这不是STL向量。)” - Elazar Leibovich

1
即使您成功完成此操作,也可能无法实现您想要的效果。根据您使用的操作系统以及它如何实现虚拟内存,您可能会发现您获得了“惰性分配”,其中只有部分内存分配实际上被分配和映射,而进一步的页面将作为页面错误的结果稍后映射。如果您的操作系统具有mlock或等效功能,则可以绕过此问题。

页面错误?在实时关键任务系统中?我觉得不可能。如果没有磁盘,我们将在哪里存储内存页面?所有代码都应该适合内存并留在那里。或者我错过了什么吗? - Elazar Leibovich
@Elazar:你没有说你使用的是什么操作系统,所以我只能假设你使用的是像Linux这样的“普通”的操作系统。当然,如果你使用的是像VxWorks这样的(或者至少没有虚拟机的)操作系统,那就是另一回事了。 - Paul R
香草Linux是实时系统的“常规花园”吗?我认为VxWorks比Linux更值得拥有这个称号。 - Elazar Leibovich

0
你能否创建一个包含向量和同样大小缓冲区的小结构体?这样,无论向量去哪里,它都可以携带其处理缓冲区。如果通过引用或指针传递它,应该避免复制开销。以下是伪代码:
struct Container
{
    vector<T> vec;
    vector<T> buf;

    Container(int size)
    {
        vec.reserve(size);
        buf.reserve(size);
    }
};

任何当前接受向量参数的函数,现在将接受一个容器


0

你可能可以重载new和delete运算符,但是那样你就必须自己管理整个内存。你可以在开始时分配尽可能多的内存:

void* operator new (std::size_t size) throw (std::bad_alloc);
void* operator new[] (std::size_t size) throw (std::bad_alloc);
void operator delete (void* ptr) throw ();
void operator delete[] (void* ptr) throw ();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接