0

我有以下循环:

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());

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

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

4

2 回答 2

2

在 C++ 中,当管理它的对象超出范围时,会自动释放内存。(假设类被正确编写。)这就是所谓的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). 从文档中,强调我的:

销毁训练数据并正确释放所有相关数据。 请务必在使用完训练数据后调用此函数。

于 2019-03-04T03:41:32.383 回答
2

由于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_wrapperfann_train_data被销毁。

这比原始方法更安全的原因C是在可能引发异常的情况下(无论出于何种原因)。如果抛出异常,fann_train_data那么使用fann_wrapper. 该方法无法保证该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无论退出可执行代码块的原因(抛出异常,完成等),资源都会自动清理。

于 2019-03-04T16:31:30.703 回答