FANN:使用从多个文件读取的数据训练 ANN 时发生内存泄漏

FANN: Memory leak when training ANN using data read from multiple files

我有以下循环:

for (int i = 1; i <= epochs; ++i) {
    for (std::vector<std::filesystem::path>::iterator it = batchFiles.begin(); it != batchFiles.end(); ++it) {
        struct fann_train_data *data = fann_read_train_from_file(it->string().c_str());
        fann_shuffle_train_data(data);
        float error = fann_train_epoch(ann, data);
    }
}

ann是网络。
batchFiles 是一个 std::vector<std::filesystem::path>.

此代码遍历文件夹中的所有训练数据文件,每次都使用它来训练 ANN,次数由 epochs 变量确定。

以下行导致内存泄漏:

struct fann_train_data *data = fann_read_train_from_file(it->string().c_str());

问题是我必须不断地在训练文件之间切换,因为我没有足够的内存来一次加载它们,否则我只会加载一次训练数据。

为什么会这样?我该如何解决这个问题?

在 C++ 中,当管理它的对象超出范围时,内存会自动释放。 (假设 class 写得正确。)这就是所谓的 RAII

但是 FANN 提出了 C API,而不是 C++ API。在 C 中,您需要在用完内存后手动释放内存。推而广之,当 C 库为您创建一个对象时,它通常需要您在使用完该对象时告诉它。库没有很好的方法来自行确定何时应该释放对象的资源。

惯例是,每当 C API 给你一个像 struct foo* create_foo() 这样的函数时,你应该寻找一个像 void free_foo(struct foo* f) 这样的对应函数。是对称的。

对于您的情况,正如 PaulMcKenzie 最初指出的那样,您需要 void fann_destroy_train_data(struct fann_train_data * train_data)。来自 the documentation,强调我的:

Destructs the training data and properly deallocates all of the associated data. Be sure to call this function after finished using the training data.

由于需要调用 fann_destroy_train_data,您可以使用以下包装器使用 C++ 和 RAII:

struct fann_wrapper
{
   fann_train_data *td;
   fann_wrapper(fann_train_data* p) : td(p) {}
   ~fann_wrapper() { fann_destroy_train_data(td); }
};
//...
for (int i = 1; i <= epochs; ++i) {
    for (std::vector<std::filesystem::path>::iterator it = batchFiles.begin(); it != batchFiles.end(); ++it) {
        struct fann_train_data *data = fann_read_train_from_file(it->string().c_str());

        // the next line ensures that fann_destroy_train_data is called
        fann_wrapper fw(data);

        fann_shuffle_train_data(data);
        float error = fann_train_epoch(ann, data);
    }  // when this curly brace is encountered, the fann_destroy_train_data is always called
}  

fann_wrapper 简单地持有 fain_train_data 指针,并且在销毁 fann_wrapper 时, fann_train_data 也被销毁。

这比原始 C 方法安全得多的原因是在可能抛出异常的情况下(无论出于何种原因)。如果抛出异常,那么在使用 fann_wrapperfann_train_data 总是 被销毁。 C 方法无法保证这一点,因为异常会完全跳过任何包含 fann_destroy_train_data.

的行

示例:

for (int i = 1; i <= epochs; ++i) {
    for (std::vector<std::filesystem::path>::iterator it = batchFiles.begin(); it != batchFiles.end(); ++it) {
        struct fann_train_data *data = fann_read_train_from_file(it->string().c_str());
        fann_shuffle_train_data(data);
        float error = fann_train_epoch(ann, data);

        fann_destroy_train_data(data); // this line is not executed if an exception is thrown above, thus a memory leak
    }
}  

这就是为什么 RAII 是 C++ 中的一个重要概念。无论退出可执行代码块的原因如何(抛出异常,完成 return 等),资源都会自动清理。