【问题标题】:Understanding C++ constexpr Performance了解 C++ constexpr 性能
【发布时间】:2021-03-31 03:57:16
【问题描述】:

我最近使用带有 C++17 的 constexpr 函数编写了一个编译时光线追踪器。完整源代码可见here。这个问题的相关代码如下所示:

constexpr auto image = []() {
        StaticImage<image_width, image_height> image;

        Camera camera{Pointf{0.0f, 0.0f, 500.0f},
                      Vectorf{0.0f},
                      Vectorf{0.0f, 1.0f, 0.0f},
                      500.0f};

        std::array<Shapes, 1> shapes_list{Sphere{Pointf{0.0f}, 150.0f}};
        std::array<Materials, 1> materials_list{DefaultMaterial{}};
        ShapeContainer<decltype(shapes_list)> shapes{std::move(shapes_list)};
        MaterialContainer<decltype(materials_list)> materials{
            std::move(materials_list)};

        SphereScene scene;
        scene.set_camera(camera);

        Renderer::render(scene, image, shapes, materials);
        return image;
    }();

此处显示的每个类(StaticImageCameraShapesMaterialsShapeContainerMaterialContainerSphereScene)完全由 constexpr 函数组成。 Renderer::render 也是 constexpr,负责遍历图像中的每个像素,将光线射入场景,并设置相应的颜色。

使用此当前设置和 512x512 的映像,在发布模式下使用 MSVC 16.9.2,编译器大约需要 35 分钟才能完成生成映像。在此过程中,它的内存使用率上升到最终使用近 64GB RAM 的程度。

所以,我的问题是:为什么编译时间和内存使用率这么高?

我的理论是编译时间的部分原因是调用堆栈的复杂性(即大量模板、CRTP 和深度),所以我尝试通过删除几个模板来简化调用堆栈( Vector 类不再模板化)并设法将编译时间减少到 32 分钟,内存使用量减少到 61GB。更好,但仍然很高。问题是我不太明白为什么它这么慢。我明白评估所有constexpr 函数是一个非常复杂的过程(因为编译器必须检查UB、类型推断等),但我没想到它会这么慢。我也对高内存使用感到困惑。图像数组本身使用的内存不超过 4MB (512 * 512 * 3 * sizeof(float)),那么额外的内存从何而来?

【问题讨论】:

  • 您的问题是关于特定编译器的。您应该为您询问的编译器添加标签。
  • 您在MaterialContainer&lt;decltype(materials_list)&gt; 中将图像本身(像素值)作为模板参数传递,我说得对吗?如果是这样,那么您不应该这样做,因为模板参数应该非常小,这可能是问题的原因。如果你想传递数据,那么可能从 C++20 甚至 C++17 开始,你可以创建结构并将结构的值作为函数的参数传递。这种结构也可以是 constexpr。例如std::array&lt;&gt; 可以很容易地在 constexpr 函数中使用。但绝对应该将数据作为函数参数传递。
  • @Arty 同意。它可能正在记忆所有模板化的实例。
  • 您正在将编译器变成图形处理器并期望它具有高性能?我认为你在滥用编译器。无论如何,我怀疑原因与大量模板实例有关。
  • @FrançoisAndrieux 我以 MSVC 为例,因为它是我可以访问的唯一编译器,但问题本身更笼统。

标签: c++ c++17 constexpr raytracing


【解决方案1】:

编译时执行的效率将低于运行时执行。编译器必须做更多的工作才能执行相同的代码。编译时执行的重点是执行您在运行时无法执行的计算。有时,为了编译时缓存更简单的计算。

编写一个仅在编译时存在的完整的、非平凡的应用程序并不是一件快速完成的事情。

至于细节,成本增加的主要原因是编译时执行必须检测所有未定义的行为。这意味着许多可能只是偏移指针的事情必须更加复杂。堆栈变量不能只是偏移堆栈指针;他们必须明确地跟踪对象的生命周期。以此类推。

编译时执行基本上是解释 C++。没有太多理由让它成为一个特别快速的解释器。大多数编译时操作处理基于类型和简单值的计算,而不是复杂的数据结构。这就是编译器的主要优化目标。

我记得最近有一些噪音通过更好的解释来改进 Clang 的 constexpr 执行。但不知道赚了多少。

【讨论】:

  • 没错,我知道它会慢得多,但我没想到它会慢得多。也就是说,我没有考虑将指针偏移到数组等中的成本。这是否也与内存使用有关?
猜你喜欢
  • 1970-01-01
  • 2019-07-25
  • 1970-01-01
  • 2013-07-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-30
  • 2016-09-12
相关资源
最近更新 更多