#c #c 11 #lambda #iteration
#c #c 11 #лямбда #итерация
Вопрос:
У меня есть две функции, functionA и functionB, которые выполняют итерацию по контейнеру (std::vector) и выполняют некоторую работу:
void functionA() {
// ...........
auto meshIterator = mMeshes.begin();
for (const Renderable amp;renderable : renderQueue) {
if (renderable.mMesh == INVALID_MESH_ID) {
JONS_LOG_ERROR(mLogger, "Renderable MeshID is invalid");
throw std::runtime_error("Renderable MeshID is invalid");
}
if (renderable.mMesh < meshIterator->first->mMeshID)
continue;
while (renderable.mMesh > meshIterator->first->mMeshID) {
meshIterator ;
if (meshIterator == mMeshes.end()) {
JONS_LOG_ERROR(mLogger, "Renderable MeshID out of range");
throw std::runtime_error("Renderable MeshID out of range");
}
}
const bool hasDiffuseTexture =
renderable.mDiffuseTexture != INVALID_TEXTURE_ID;
const bool hasNormalTexture =
renderable.mNormalTexture != INVALID_TEXTURE_ID;
mGeometryProgram.SetUniformData(
UnifGeometry(renderable.mWVPMatrix, renderable.mWorldMatrix,
hasDiffuseTexture, hasNormalTexture,
renderable.mTextureTilingFactor));
if (hasDiffuseTexture)
BindTexture2D(OpenGLTexture::TEXTURE_UNIT_GEOMETRY_DIFFUSE,
renderable.mDiffuseTexture, mTextures, mLogger);
if (hasNormalTexture)
BindTexture2D(OpenGLTexture::TEXTURE_UNIT_GEOMETRY_NORMAL,
renderable.mNormalTexture, mTextures, mLogger);
GLCALL(glBindVertexArray(meshIterator->second));
GLCALL(glDrawElements(GL_TRIANGLES, meshIterator->first->mIndices,
GL_UNSIGNED_INT, 0));
GLCALL(glBindVertexArray(0));
}
// ...........
}
void functionB() {
//....................
// both containers are assumed to be sorted by MeshID ascending
auto meshIterator = mMeshes.begin();
for (const Renderable amp;renderable : renderQueue) {
if (renderable.mMesh == INVALID_MESH_ID) {
JONS_LOG_ERROR(mLogger, "Renderable MeshID is invalid");
throw std::runtime_error("Renderable MeshID is invalid");
}
if (renderable.mMesh < meshIterator->first->mMeshID)
continue;
while (renderable.mMesh > meshIterator->first->mMeshID) {
meshIterator ;
if (meshIterator == mMeshes.end()) {
JONS_LOG_ERROR(mLogger, "Renderable MeshID out of range");
throw std::runtime_error("Renderable MeshID out of range");
}
}
const Mat4 wvp = lightVP * renderable.mWorldMatrix;
mNullProgram.SetUniformData(UnifNull(wvp));
GLCALL(glBindVertexArray(meshIterator->second));
GLCALL(glDrawElements(GL_TRIANGLES, meshIterator->first->mIndices,
GL_UNSIGNED_INT, 0));
GLCALL(glBindVertexArray(0));
}
// ...............
}
То, как они перебирают контейнер, очень похоже, но работа, которую они выполняют в теле, сильно отличается. Вместо этого я хотел бы объединить эти два (возможно, больше в будущем) в одну функцию, например:
void DrawModels(const std::function<
void(const Renderable amp;renderable)> amp;preDrawFunc) {
// both containers are assumed to be sorted by MeshID ascending
auto meshIterator = mMeshes.begin();
for (const Renderable amp;renderable : renderQueue) {
if (renderable.mMesh == INVALID_MESH_ID) {
JONS_LOG_ERROR(mLogger, "Renderable MeshID is invalid");
throw std::runtime_error("Renderable MeshID is invalid");
}
if (renderable.mMesh < meshIterator->first->mMeshID)
continue;
while (renderable.mMesh > meshIterator->first->mMeshID) {
meshIterator ;
if (meshIterator == mMeshes.end()) {
JONS_LOG_ERROR(mLogger, "Renderable MeshID out of range");
throw std::runtime_error("Renderable MeshID out of range");
}
}
preDrawFunc(renderable);
GLCALL(glBindVertexArray(meshIterator->second));
GLCALL(glDrawElements(GL_TRIANGLES, meshIterator->first->mIndices,
GL_UNSIGNED_INT, 0));
GLCALL(glBindVertexArray(0));
}
}
Я считаю, что предоставленная std::function может использоваться для выполнения некоторой произвольной работы в зависимости от вызывающего, например:
void functionD() {
auto preDrawRenderable = [amp;](const Renderable amp;renderable) {
const bool hasDiffuseTexture =
renderable.mDiffuseTexture != INVALID_TEXTURE_ID;
const bool hasNormalTexture =
renderable.mNormalTexture != INVALID_TEXTURE_ID;
mGeometryProgram.SetUniformData(
UnifGeometry(renderable.mWVPMatrix, renderable.mWorldMatrix,
hasDiffuseTexture, hasNormalTexture,
renderable.mTextureTilingFactor));
if (hasDiffuseTexture)
BindTexture2D(OpenGLTexture::TEXTURE_UNIT_GEOMETRY_DIFFUSE,
renderable.mDiffuseTexture, mTextures, mLogger);
if (hasNormalTexture)
BindTexture2D(OpenGLTexture::TEXTURE_UNIT_GEOMETRY_NORMAL,
renderable.mNormalTexture, mTextures, mLogger);
};
DrawModels(preDrawRenderable);
}
void functionE() {
auto preDrawRenderable = [amp;](const Renderable amp;renderable) {
const Mat4 wvp = lightVP * renderable.mWorldMatrix;
mNullProgram.SetUniformData(UnifNull(wvp));
};
DrawModels(preDrawRenderable);
}
Мои вопросы:
1) functionD и functionE должны выполняться примерно 60-100 раз в секунду. Влечет ли использование lambda и std::function какие-либо значительные потери производительности? Например, существуют ли какие-либо скрытые вызовы динамического выделения памяти или виртуальные запросы или что-то еще, что может снизить производительность? Я не знаю накладных расходов на использование лямбд и std::function.
2) Есть ли лучшая / более быстрая / более чистая альтернатива, чем мое наивное решение?
Комментарии:
1. Единственный разумный способ реализации
std::function
— это динамическое выделение памяти и вызов виртуальной функции. У лямбд нет этой проблемы.2. @nwp Эти два лямбда на самом деле ничего не фиксируют (несмотря на то, что им нечего фиксировать
[amp;]
), поэтому реализацияstd::function
может избежать выделения памяти и просто сохранить указатель на функцию.3. разве нет? Что такое переменные-члены «mGeometryProgram», «mTextures», «mLogger»?
Ответ №1:
void DrawModels(const std::function< void(const Renderable amp;renderable)> amp;preDrawFunc)
вместо этого сделайте это:
template<class RenderableFunc>
void DrawModels(RenderableFuncamp;amp; preDrawFunc)
и оставьте тело без изменений. Поместите его так, чтобы его могли видеть обе функции, которые вы заменяете.
Теперь у компилятора есть простая задача оптимизации для встраивания вашего лямбда.
std::function
это объект стирания типов, который творит волшебство, которое сбивает с толку оптимизаторы текущего поколения. Это не тип лямбда-выражения, это тип, который может преобразовать любой лямбда-выражение или указатель на функцию или вызываемый объект во внутренний объект и сохранить его для последующего выполнения.
Необработанный лямбда- это функциональный объект, сгенерированный компилятором с захваченными переменными и неоперационным virtual
() . Это намного легче.
Комментарии:
1. Вызов функции, подобной этой «DrawModels<decltype(preDrawRenderable)> (preDrawRenderable);» дает «Вы не можете привязать значение lvalue к ссылке rvalue» с помощью VS2013 — почему это?
2. @kaiserjohaan потому что я использовал универсальную ссылку, предназначенную для вывода с помощью аргумента. Если вам необходимо передать тип, передавайте
<decltype(preDrawRenderable) constamp;>
, если аргумент не являетсяmove
общим. Но на самом деле просто дайте ему быть выведенным.
Ответ №2:
Влечет ли использование lambda и std::function какие-либо значительные потери производительности?
Лямбды — это только анонимные функторы, созданные на месте. Поэтому не должно быть заметного снижения производительности по сравнению с вашими собственными функциями / функторами.
С другой стороны std::function
, применяется стирание типов, но обычно (это определяется реализацией) вместо полиморфизма используется приведение и отправка тегов. Поэтому могут быть некоторые потери производительности по сравнению с обычной функцией, но они могут быть незначительными. РЕДАКТИРОВАТЬ: как указал Якк в своем ответе, удаление типа может нарушить способность компилятора встроить функцию.
Есть ли лучшая / более быстрая / более чистая альтернатива, чем мое наивное решение?
Может быть более элементарным полагаться на стандартные алгоритмы ( <algorithm>
заголовок) вместо необработанных циклов. Это делает код более читаемым, но прямого улучшения производительности нет.
Обратите внимание, что некоторые советы, которые я написал здесь, могут рассматриваться как немного субъективные. Как и в любом вопросе, связанном с производительностью, не полагайтесь на «здравый смысл», измеряйте и выполняйте профилирование производительности. Например, как я уже сказал, написание кода на основе алгоритмов очищает код, но не может привести к прямому повышению производительности. Это сильно зависит от контекста, поэтому просто profile .