关于为什么新代码会减慢光线跟踪器的速度的任何想法
Any ideas as to why the new code slows down the ray-tracer?
我正在进行光线跟踪器,并实现了透视校正,使用x和y轴上的当前像素值计算要发射的光线的位置,以计算当前光线方向。这里的代码片段:
float fov = 60;
float invWidth = 1/float(image.getWidth());
float invHeight = 1/float(image.getHeight());
float angle = (fov * M_PI * 0.5/180 );
float aspectratio = image.getWidth()/float(image.getHeight());
point camera = scene.getCamera();
for (int y=0; y<image.getHeight(); y++) {
for (int x=0; x<image.getWidth(); x++) {
......
......
float xx = (((x*invWidth) *2)-1) * angle * aspectratio;
float yy = (((y*invHeight)*2)-1) * angle;
Ray viewRay = { {camera.x, camera.y, camera.z}, {xx, yy, 1.0f}};
到目前为止,效果非常好。然而,我意识到xx和yy(像素的方向)的值不需要为每个像素计算,只需要几倍于图像的宽度和长度。所以我用这种方式重写了这些部分:
float fov = 60;
float invWidth = 1/float(image.getWidth());
float invHeight = 1/float(image.getHeight());
float angle = (fov * M_PI * 0.5/180 );
float aspectratio = image.getWidth()/float(image.getHeight());
float rays_x [image.getWidth()], rays_y [image.getHeight()];
for (int y=0; y<image.getHeight(); y++)
rays_y [y] = (((y*invHeight)*2)-1) * angle;
for (int x=0; x<image.getWidth(); x++)
rays_x [x] = (((x*invWidth) *2)-1) * angle * aspectratio;
point camera = scene.getCamera();
for (int y=0; y<image.getHeight(); y++) {
float yy = rays_y[y];
for (int x=0; x<image.getWidth(); x++) {
......
......
Ray viewRay = { {camera.x, camera.y, camera.z}, {rays_x[x], yy, 1.0f}};
我基本上预先计算了光线的方向,并将它们存储到阵列中。我预计性能会有一些小的改进,在悲观的情况下可能不会,但我从未想过会变得最糟糕。以前我渲染一个场景需要1.67秒,现在需要1.74秒!这并不是一个巨大的下降,但令人惊讶的是,我本以为现在会做更少的工作。我禁用了编译器优化(-O3和-fast math),并使用这两种方法进行了测试。之前它在9.03和9.05之间嘟嘟作响,现在在9.06和9.15之间
那么我该如何调查此事呢?我唯一想到的是,由于每次循环迭代都访问rays_x[x],每1024次迭代访问rays_y[y],所以缓存命中率更低,尽管我永远不会怀疑这一点,因为总共只有1024*4=4096+(768*4)=7168个字节。任何想法都将不胜感激。
编译器将意识到这一点:
float yy = (((y*invHeight)*2)-1) * angle;
是常量数据,每个循环只需要计算一次。
因此,预先计算的yy是对性能的浪费。
不过,预先计算的xx可能会有所帮助,但如果表达式保持许多恒定数据(即invWidth*2和angle*aspectratio),则性能可能不会提高,甚至可能因缓存未命中而变得更差。
float xx = (((x*invWidth) *2)-1) * angle * aspectratio;
预先计算方向将加速跟踪器。但是,首先创建查找表显然会产生开销。在代码中,您正在堆栈上创建表,并重新计算每一帧的方向。这将稍微慢一点,因为您必须从以前没有读取的数组中读取,并且由于内存分配开销。相反,我建议您在堆上创建查找数组(作为方法外的指针),并只预计算一次方向。方向取决于帧之间不变的值,因此不需要计算每帧的方向。
类似这样的东西:
float *rays_x, *rays_y;
void compute_directions()
{
rays_x = new float[image.getWidth()];
rays_y = new float[image.getHeight()];
for (int y=0; y<image.getHeight(); y++)
rays_y[y] = (((y*invHeight)*2)-1) * angle;
for (int x=0; x<image.getWidth(); x++)
rays_x[x] = (((x*invWidth) *2)-1) * angle * aspectratio;
}
void render()
{
float fov = 60;
float invWidth = 1/float(image.getWidth());
float invHeight = 1/float(image.getHeight());
float angle = (fov * M_PI * 0.5/180 );
float aspectratio = image.getWidth()/float(image.getHeight());
point camera = scene.getCamera();
for (int y=0; y<image.getHeight(); y++) {
float yy = rays_y[y];
for (int x=0; x<image.getWidth(); x++) {
......
......
很明显,你必须将角度和方位移动到其他地方,这样你就可以在compute_directions中访问它们。此外,如果您不再需要指针,请记住使用delete[]删除指针,以防止内存泄漏。
根据您的描述判断,您似乎通过预计算一些似乎计算得很快的值(将计算转移到一些可能不会带来任何性能改进的内存查找上,这也是一种预感!)来优化。
优化的一些基本规则:
- 在尝试优化任何内容之前:配置文件
- 在优化任何东西之后:配置文件
在知道程序实际花费的时间之前,您不能指望通过优化获得任何性能增益。
在Linux上,您可以使用GC-pg开关和gprof。您还可以使用perf和valgrind(例如callgrind来了解对特定函数的调用次数)。
还可以查看perfwiki。
- 为什么在读取文件大小时文件IO速度会发生变化
- 光线跟踪器灯光反射错误
- 人脸跟踪arduino代码的优化
- 为什么std::condition_variable notify_all的工作速度比notify_one快(对于随机请
- 文件系统:复制功能的速度秘诀是什么
- 跟踪滚动条上的鼠标事件
- 学习多线程C++:添加线程不会使执行速度更快,即使它看起来应该
- 如何使用新运算符跟踪在循环中创建的 QLabel
- 在C++中使用并行化的预期速度是多少(不是 OpenMp,而是 <thread>)
- 漫反射材质的奇怪光线跟踪行为
- C++光线跟踪器-只有一个对象出现在场景中
- 跟踪gcc编译以及哪些代码会减慢编译速度
- c++中一个简单光线跟踪器的问题
- 光线跟踪器-实现阴影
- 光线跟踪矢量
- 使用GLSL进行光线跟踪
- 为速度关键系统设计跟踪/日志
- 使用3个位置和法线对光线跟踪插值三角形曲面的最佳方法
- 在Microsoft Visual C++中,光线跟踪器应该是什么类型的项目
- 关于为什么新代码会减慢光线跟踪器的速度的任何想法