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_wrapper
时 fann_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
等),资源都会自动清理。
我有以下循环:
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_wrapper
时 fann_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
等),资源都会自动清理。