我有以下循环:
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());
问题是我必须经常在训练文件之间切换,因为我没有足够的内存来一次加载所有文件,否则我将只加载一次训练数据。
为什么会这样?我该如何解决?
答案 0 :(得分:3)
在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)
。在the documentation中,重点是我:
销毁训练数据并适当地分配所有关联数据。 请务必在使用完训练数据后调用此函数。
答案 1 :(得分: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_wrapper
被破坏时,fann_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
等),资源都会自动清理。