Понимание преимуществ семантики перемещения по сравнению с метапрограммированием шаблонов
Я прочитал некоторые описания семантики перемещения в C++11, и мне интересно, в каком контексте она может быть использована. В настоящее время многие математические библиотеки C++ используют метапрограммирование шаблонов для задержки оценки.
Если M = A + B + C*D, где M, A, B, C и D - матрицы, шаблонное метапрограммирование позволяет избежать ненужных копий. Является ли семантика перемещения более удобным способом делать подобные вещи?
Если нет, то в каком контексте используется семантика перемещения. Если да, каковы различия / ограничения по сравнению с метапрограммированием шаблонов для такого вида использования?
5 ответов
Я не эксперт по этим оптимизациям, но, насколько я понимаю, техники отложенной оценки, о которых вы говорите, работают, определяя арифметические операторы в типе матрицы, например, A+B+C*D
не возвращает матрицу, он возвращает прокси-объект, который может преобразовываться в матрицу. Это происходит, когда он назначен M
и код преобразования будет вычислять каждую ячейку матрицы результатов наиболее эффективными средствами, которые могут быть разработаны разработчиками библиотеки, избегая временных объектов матрицы.
Итак, предположим, что программа содержит M = A + B + C * D;
Если вы не сделали ничего умного, кроме реализации operator+
обычным способом, используя operator+=
, вы получите что-то вроде этого, как обычно, в E ++ в стиле элиминации копирования:
Matrix tmp1 = C;
tmp1 *= D;
Matrix tmp2 = A;
tmp2 += B;
tmp2 += tmp1;
M = tmp2;
С отложенной оценкой вы можете получить что-то похожее на:
for (int i = 0; i < M.rows; ++i) {
for (int j = 0; j < M.cols; ++j) {
/* not necessarily the best matrix multiplication, but serves to illustrate */
c_times_d = 0;
for (int k = 0; k < C.cols; ++k) {
c_times_d += C[i][k] * D[k][j];
}
M[i][j] = A[i][j] + B[i][j] + c_times_d;
}
}
в то время как "ничего не умный" код будет выполнять несколько отдельных циклов сложения и намного больше назначений.
Насколько я знаю, семантика перемещения не очень помогает в этом случае. Ничто из того, что вы написали, не позволяет нам отойти от A
, B
, C
или же D
Итак, мы собираемся в конечном итоге с эквивалентом:
Matrix tmp1 = C;
tmp1 *= D;
Matrix tmp2 = A;
tmp2 += B;
tmp2 += std::move(tmp1);
M = std::move(tmp2);
Таким образом, семантика перемещения не помогла ни с чем, кроме последнего бита, где, возможно, rvalue-версии операторов лучше, чем обычные. Там более доступно, если вы написали std::move(A) + std::move(B) + std::move(C) * std::move(D)
потому что нам не нужно было бы копировать с C
или же A
, но я все еще не думаю, что результат так же хорош, как код с отложенной оценкой.
По сути, семантика перемещения не помогает с некоторыми важными частями оптимизации, обеспечиваемыми отложенной оценкой:
1) при отсроченной оценке промежуточные результаты никогда не должны существовать в виде полных матриц. Семантика перемещения не спасает компилятор от создания полной матрицы A+B
в памяти в какой-то момент.
2) с отложенной оценкой, мы можем начать модификацию M
прежде чем мы закончили вычисление всего выражения. Семантика перемещения не помогает компилятору переупорядочивать модификации: даже если компилятор достаточно умен, чтобы определить потенциальную возможность, модификации в невременных файлах должны храниться в правильном порядке, если есть опасность возникновения исключения, потому что, если таковые имеются часть A + B + C * D
бросает, то M
должен быть оставлен, как это началось.
Я считаю, что более точный термин для того, что вы называете "метапрограммирование шаблонов" - это шаблоны выражений.
Если ваши матрицы распределяют свои данные динамически, семантика перемещения может помочь перенести эти данные из объекта в объект (включая и из временных), сгенерированные во время выражения, такого как:
M = A + B + C*D
Шаблоны выражений, с другой стороны, полностью устранят временные эффекты.
Если ваши матрицы не распределяют свои данные динамически (например, если они имеют фиксированный размер и малый размер), семантика перемещения вообще не повлияет на вашу производительность.
Применение шаблонов выражений к матричной библиотеке приведет к максимальной производительности. Это также очень сложный метод реализации. Семантику перемещения гораздо проще реализовать, и ее можно выполнять в дополнение к шаблонам выражений (если есть такие ресурсы, как память, которые можно передавать).
В итоге:
Семантика перемещения не устраняет временные эффекты, но вместо этого перераспределяет динамически распределенную память между временными.
Шаблоны выражений исключают временные.
Это два разных зверя. Семантика перемещения - это присвоение ресурсов из значения, которое будет уничтожено. При смешивании с шаблонами выражений, скажем, больших целых (которые требуют динамического выделения памяти), можно просто присвоить такую память вместо того, чтобы делать копию чего-то, что должно быть уничтожено.
Семантика перемещения также важна для объектов, которые по своей сути не подлежат копированию (например, fstreams), но имеет смысл сделать перемещаемыми.
Семантика перемещения является динамической, а шаблоны выражений - нет. Вы не можете выразить шаблонное выражение, которое распределено по нескольким операторам, и некоторые из них оцениваются, только если луна голубая, тогда как семантика перемещения может.
Семантика перемещения применима к ресурсам, управляемым внутри объектов, и используется, чтобы избежать ненужного получения / освобождения ресурсов при создании временных объектов (например, динамически выделяемая память является ресурсом).
Шаблон метапрограммирования работает со структурами, которые размещаются в стеке (так как он требует оценки времени компиляции операндов). Вы можете использовать его, чтобы избежать вычислений во время выполнения для операций, которые могут быть вычислены во время компиляции