【问题标题】:Simplify container iteration using lambdas使用 lambda 简化容器迭代
【发布时间】:2014-06-14 11:50:20
【问题描述】:

我有两个函数,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) 有没有比我幼稚的解决方案更好/更快/更清洁的替代方案?

【问题讨论】:

  • 实现std::function 的唯一合理方法是使用动态内存分配和虚函数调用。 Lambda 没有这个问题。
  • @nwp 这两个 lambdas 实际上并没有捕获任何东西(尽管有 [&amp;],但它没有任何东西可以捕获),因此 std::function 的实现可以避免内存分配并简单地存储一个函数指针。
  • 没有吗?什么是成员变量“mGeometryProgram”、“mTextures”、“mLogger”?

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


【解决方案1】:
void DrawModels(const std::function< void(const Renderable &renderable)> &preDrawFunc)

不是这样,而是这样做:

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

并保持正文不变。将其放置在您要替换的两个函数都可以看到的地方。

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

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

原始 lambda 是编译器生成的函数对象,其中包含捕获的变量和非virtual 操作符()。它的重量要轻得多。

【讨论】:

  • 像这样调用函数“DrawModels(preDrawRenderable);”用 VS2013 给出“你不能将左值绑定到右值引用” - 为什么会这样?
  • @kaiserjohaan b因为我使用了一个旨在通过参数推断的通用引用。如果你必须传递一个类型,传递&lt;decltype(preDrawRenderable) const&amp;&gt;,除非参数是moved in。但真的只是让它被推导。
【解决方案2】:

使用 lambda 和 std::function 是否会导致显着的性能损失?

Lambda 只是就地创建的匿名函子。因此,与您自己的函数/函子相比,不应有任何明显的性能损失。

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

有没有比我幼稚的解决方案更好/更快/更清洁的替代方案?

依赖标准算法(&lt;algorithm&gt; 标头)而不是原始循环可能会更优雅。这使代码更具可读性,但没有直接的性能优势。

请注意,我在这里写的一些建议可能会被视为有点主观。就像在任何与性能相关的问题中一样,不要依赖“常识”,测量并进行性能分析。例如,正如我所说,基于算法编写代码可以清除代码,但不能带来直接的性能优势。这在很大程度上取决于上下文,因此只需配置文件即可。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-03
    • 2020-09-30
    • 2013-08-01
    • 2014-10-28
    • 2017-04-20
    • 2014-09-10
    相关资源
    最近更新 更多