使用lambdas简化容器迭代

时间:2014-06-14 11:50:21

标签: c++ c++11 lambda iteration

我有两个函数,functionA和functionB,它们遍历容器(std :: vector)并执行一些工作:

void functionA() {
  // ...........

  auto meshIterator = mMeshes.begin();
  for (const Renderable &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 &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 &renderable)> &preDrawFunc) {
  // both containers are assumed to be sorted by MeshID ascending
  auto meshIterator = mMeshes.begin();
  for (const Renderable &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 = [&](const Renderable &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 = [&](const Renderable &renderable) {
    const Mat4 wvp = lightVP * renderable.mWorldMatrix;
    mNullProgram.SetUniformData(UnifNull(wvp));
  };

  DrawModels(preDrawRenderable);
}

我的问题:

1)functionD和functionE都需要每秒运行大约60-100次。使用lambda和std :: function会导致任何重大的性能损失吗?例如,是否存在任何隐藏的动态内存分配调用或虚拟查找等可能导致性能下降的内容?我不知道使用lambdas和std :: function的开销。

2)是否有比我天真的解决方案更好/更快/更清洁的替代方案?

2 个答案:

答案 0 :(得分:4)

void DrawModels(const std::function< void(const Renderable &renderable)> &preDrawFunc)

而不是那样做,这样做:

template<class RenderableFunc>
void DrawModels(RenderableFunc&& preDrawFunc)

并保持身体不变。将它放在您要替换的两个功能都可以看到的位置。

现在编译器有一个简单的优化问题来内联你的lambda。

std::function是一种类型擦除对象,可以使魔术混淆当前的生成优化器。它不是lambda的类型,它是一种类型,可以将任何lambda或函数指针或可调用对象转换为内部对象并存储它以供以后执行。

原始lambda是编译器生成的函数对象,具有捕获的变量和非virtual operaror()。重量轻得多。

答案 1 :(得分:2)

  

使用lambda和std :: function会导致任何重大的性能损失吗?

Lambdas只是在适当的位置创建的匿名仿函数。因此,与您自己的函数/仿函数相比,不应该有任何明显的性能损失。

另一方面,std::function应用类型擦除,但通常(这是实现定义)使用转换和标记分派而不是多态。与正常功能相比,可能会有一些性能命中,但这些可以忽略不计。 编辑:正如Yakk在答案中指出的那样,类型擦除可能会破坏编译器内联函数的能力。

  

是否有比我天真的解决方案更好/更快/更清洁的替代方案?

依靠标准算法(<algorithm>标题)而不是原始循环可能更加优雅。这使代码更具可读性,但没有直接的性能优势。

请注意,我在这里写的一些建议可能会被视为有点主观。与任何与绩效相关的问题一样,不要依赖常识&#34;来衡量和做绩效评估。例如,正如我所说的基于算法的写代码清除了代码,但无法带来直接的性能优势。这在很大程度上取决于上下文,所以只是简介。