Принудительная очистка временных переменных
(Пожалуйста, извините, если этот вопрос является дубликатом, я новичок и понятия не имел, какие термины искать, чтобы найти ответ!)
Я пытаюсь написать библиотеку линейной алгебры с некоторым очень элементарным управлением памятью для C++ - по сути, используя shared_ptrs; как только счетчик ptr достигает 0, он возвращает память менеджеру. Массивы, с которыми я имею дело, довольно большие по размеру, и память стоит дороже, поэтому я пытаюсь заставить код запустить очистку избыточных переменных памяти как можно скорее.
Рассматривать:
a, b, c, d, e НЕ являются временными переменными, ~5 ГБ каждая.
z = a + b + c + d + e
или в качестве альтернативы
z = add(a,add(b,add(c,add(d,e)))) - это то, как я на самом деле кодировал это до сих пор, без перегрузки операторов.
Я обнаружил, что временные переменные (по одной для каждой операции) выходят из области видимости только после завершения операции равенства, и поэтому shared_ptr по-прежнему считает, что память используется. В результате происходит очистка рабочего пространства одновременно, когда теоретически один кусок памяти, содержащий "d + e", может быть отброшен после сохранения "c + d + e" и т. Д.
Очевидно, что это приводит к огромному увеличению объема используемой памяти, так как вместо 5 ГБ в пустом пространстве, он занимает 20 ГБ.
Чтобы обойти это до настоящего времени, я должен был сделать следующее
z = d + e
z = c + z
z = b + z
z = a + z
Это позволяет предыдущей временной переменной выходить из области видимости в каждой новой строке, но в некоторых частях моего кода это вызывает довольно неприятное раздувание.
Есть ли способ заставить код вернуть временную память обратно менеджеру раньше, чем в конце строки? Я думаю, что я мог бы что-то сделать с общим указателем или передать по ссылке, а не по значению, но я не могу понять, как это сделать.
Чтобы уточнить, что я ищу:
выделить память для (d+e), выполнить расчет
выделить память для (c+d+e), выполнить расчет, освободить (d + e)
выделить память для (b+c+d+e), выполнить расчет, освободить (c + d + e)
выделить память для (a+b+c+d+e), выполнить расчет, освободить (b + c + d + e)
назначить (a + b + c + d + e) указатель на z
#include <iostream>
class Foo {
public:
int fooid;
Foo(int fi) {
fooid = fi;
std::cout << "Creating array " << fooid << std::endl;
}
~Foo() {
std::cout << "Cleanup array " << fooid << std::endl;
}
};
Foo mult(const Foo &a, const Foo &b)
{
//std::cout << "Constructing new foo" << std::endl;
Foo out(a.fooid*b.fooid);
return out;
}
int main()
{
Foo twos(2); //placeholders for huge non-temporary arrays
Foo threes(3);
Foo fives(5);
Foo sevens(7);
Foo elevens(11);
std::cout <<"Method 1" << std::endl;
Foo vx = mult(mult(mult(mult(twos,threes), fives),sevens),elevens);
//std::cout << vx.fooid << std::endl;
//system("pause");
std::cout << std::endl <<"Method 2" << std::endl;
//Alternative, over 3 lines, forces destructors earlier than above, more scratch space for the later operations
//Note array 30 is deleted before array 210 is constructed, unlike Method 1
Foo vx1 = mult(twos, threes);
vx1 = mult(vx1, fives);
vx1 = mult(vx1, sevens);
vx1 = mult(vx1, elevens);
std::cout << std::endl <<"End" << std::endl;
return 0;
}
Вывод, как показано ниже:
Creating array 2
Creating array 3
Creating array 5
Creating array 7
Creating array 11
Method 1
Creating array 6
Creating array 30
Creating array 210
Creating array 2310
Cleanup array 210
Cleanup array 30
Cleanup array 6
Method 2
Creating array 6
Creating array 30
Cleanup array 30
Creating array 210
Cleanup array 210
Creating array 2310
Cleanup array 2310
End
Cleanup array 2310
Cleanup array 2310
Cleanup array 11
Cleanup array 7
Cleanup array 5
Cleanup array 3
Cleanup array 2
Основное различие между методами, описанными выше, заключается в том, что метод 2 обладает способностью освобождать старые временные переменные перед выполнением следующего шага вычисления, тогда как первый метод сохраняет всю свою память до полного завершения вычисления. Я надеюсь найти способ, которым я мог бы получить результат метода 2, в то же время кодируя больше в соответствии с методом 1.
Извините за частые правки. Хотел сделать это правильно.
1 ответ
Самый простой ответ, начиная с C++11 (и, по крайней мере, частично реализованный хорошими компиляторами до этого), - принимать ваши аргументы по значению. Это кажется нелогичным, поскольку это звучит как большее количество копий, но на практике право собственности на существующие данные передается в функцию конструкторами перемещения (и / или магией компилятора).
Затем вы обновляете полученную "копию" на месте и возвращаете ее, где она вполне может перевести свои ресурсы в другой вызов функции для повторного использования / обновления. В конце полного выражения будет запущено несколько деструкторов (до C++17, который запускает их, когда каждая функция возвращает из-за "обязательного исключения из копии "), но большинство будет для объектов с удаленным доступом, ресурсы которых уже были повторно использованы и / или выпущен.
Конечно, каждая функция аргументов одинакового размера нуждается не более чем в одной из них, чтобы сохранить свой результат; Поскольку большинство операторов левоассоциативны, очевидным выбором для параметра по значению является первый. Вы можете определить варианты, скажем, вычитания со вторым операндом, переданным по значению, чтобы поймать такие случаи, как z=a-(b*c)
,
Я пытаюсь написать библиотеку линейной алгебры с очень элементарным управлением памятью для C++ - по сути, используя shared_ptrs; как только счетчик ptr достигает 0, он возвращает память менеджеру.
...
a, b, c, d, e НЕ являются временными переменными, каждая размером ~5 ГБ.
Если вы создаете a linear algebra library
практиковать свои навыки программирования, тогда хорошо.
Но вы писали оvariables, ~5GB each
! Значит, вы решаете какую-то серьезную проблему. На вашем месте я бы использовал готовую библиотекуgmp
например. Причина проста. Эта библиотека была создана и оптимизирована многими умными людьми в течение тысяч часов. Таким образом вы уберете одну проблему из "уравнения".
Этот очень большой размер матрицы предполагает, что вам следует прочитать еще о разреженной матрице. Не исключено, что это сделает ваши данные более компактными и ускорит вычисления.
Когда вы используете библиотеку, очистка памяти уже используемой переменной будет простой (вызов некоторой функции, называемой чем-то вроде clean
). Так что ваш вопрос не будет проблемой.
Я подозреваю, что ваш вопрос связан с проблемой XY, рассмотрите возможность предоставления дополнительной информации, которая носит менее общий характер