c++ - 如何优化具有已知瓶颈的计算密集型 C++ 程序?

标签 c++ performance visual-c++ optimization

我正在为我的大学开发一些科学软件。它是在 Windows (VS2008) 上用 C++ 编写的。该算法必须为大量矩阵对计算一些值,也就是说,在核心处有一个循环遍历矩阵,收集一些数据,例如:

sumA = sumAsq = sumB = sumBsq = diffsum = diffsumsq = return = 0;
for (int y=0; y < height; ++y)
{
    for (int x=0; x < width; ++x)
    { 
        valA = matrixA(x,y);
        valB = matrixB(x,y);
        sumA+=valA;
        sumAsq+=valA*valA;
        sumB+=valB;
        sumBsq+=valB*valB;
        diffsum+=valA-valB;
        diffsumsq+=(valA-valB)*(valA-valB);
    }
}
return = sumA + sumB / sumAsq + sumBsq * diffsum * diffsumsq

此例程针对不同的矩阵 A、矩阵 B 对执行了数百万次。我的问题是这个程序非常慢,在 Release模式下编译并激活所有优化。使用“忙碌时暂停并检查”调试器技术,我确定该程序几乎每次 都位于该循环内,尽管如您所料,该例程被一大堆条件和控制分支。最让我困惑的是,在基于双处理器 Xeon 的系统上执行期间,该程序使用了 4 个内核之一(毫不奇怪,它现在是单线程的)但最多只能达到其限制的 25% ,并且具有相对较大的振荡,我希望在程序终止之前稳定的 100% 负载。

当前版本实际上是一个重写,在创建时考虑到了优化性能。当我发现它实际上比原来的慢时,我很震惊。之前的版本使用了 Boost 矩阵,我将其替换为 OpenCV 矩阵,在确定它们在比较两个 1000x100 矩阵相乘的执行时间时快了 10 倍以上之后。我通过手动取消引用指向其数据的原始指针来访问矩阵,我希望这能提高我的性能。我将计算例程设为多行#define 宏以强制其内联并避免函数调用和返回。我改进了计算背后的数学,以便通过一次矩阵计算最终值(旧版本需要两次)。我期望获得巨大的 yield ,但事实恰恰相反。我与旧程序的效率相去甚远,更不用说用于特定应用程序的商业软件了。

我想知道这是否与矩阵数据是 8 位字符有关,我曾经看到在我的旧程序中访问 float 实际上比访问 double 慢,也许字符甚至更慢,因为处理器检索32 位 block 中的数据(这个 Xeon 可能甚至抓取 64 位)。我还考虑过将矩阵转换为 vector 以避免循环内循环构造,以及某种形式的向量化,例如在单个循环迭代中计算 4(更少?更多?)连续矩阵单元的数据。还有其他想法吗?

编辑:基于 OpenCV 的新版本中的实际代码:

const char *Aptr, *Bptr;
double sumA = 0, sumB = 0, sumAsq = 0, sumBsq = 0, diffsum = 0, diffsumsq = 0;
char Aval, Bval;

for (int y=0; y < height; ++y)
{
    Aptr = (char*)(AMatrix.imageData + AMatrix.widthStep * y);
    Bptr = (char*)(BMatrix.imageData + BMatrix.widthStep * y);
    for (int x=0; x < width; ++x)
    {
        Aval = Aptr[x];
        Bval = Bptr[x];

        sumA+=Aval;
        sumB+=Bval;
        sumAsq+=Aval*Aval;
        sumBsq+=Bval*Bval;
        diffsum+=Aval-Bval;
        diffsumsq+=(Aval-Bval)*(Aval-Bval);
    }
}

最佳答案

各种想法:

  • 您说您只能设法实现大约 25% 的 CPU 负载。我可以想到两个原因:
    1. 你在交换。你的矩阵的大小是多少?它们是否完全适合物理内存?查看您的应用程序的内存使用情况和工作集大小。
    2. 应用程序的其余代码在 I/O 上阻塞。核心例程周围的代码是否执行任何 I/O?它可能会在那里阻塞很长一段时间,但当然你不会看到使用“忙碌时暂停并检查”技术,因为每当进程再次解除阻塞时,它都会直接返回到你的计算密集型核心例行公事。
  • 查看核心例程的汇编代码。看起来合理吗?
  • 你真的需要在循环中计算diffsum吗?看起来好像您可以在循环外执行 diffsum=sumA-sumB —— 但可能存在数值上的考虑因素阻止您这样做。
  • 正如 renick 已经评论的那样,这看起来像是 SSE 优化的主要目标。同样,您应该确保编译器生成合理的汇编代码(如果您使用的是内部函数而不是自己编写汇编代码)。
  • 如果您不想自己编写 SSE 代码,至少要确保设置了编译器的 SSE 标志。这将允许编译器使用 SSE 单元而不是 FPU 进行标量浮点运算,这本身将提高性能,因为众所周知 x86 上基于堆栈的 FPU 不适合编译器代码生成。

关于c++ - 如何优化具有已知瓶颈的计算密集型 C++ 程序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3361678/

相关文章:

algorithm - 分组算法 - 锦标赛

python - 将日期时间字符串快速转换为秒 (Python3)

c++ - Visual Studio 2010 和内核级编程!

c++ - CMutex::Lock 与 CSingleLock::Lock

c++ - 在QT中为QPainter设置填充颜色(不是描边颜色)

c++ - 无需访问类的自动转换

performance - 为什么 atan2(y,x) 的计算速度比 arcsin 或 arccos 快?

visual-studio-2010 - OpenCV cvblob - 渲染 Blob

C++ 二叉树 : Return the number of descendants of a node. 叶子有零。如果未找到 TreeData,则返回 -1

c++ - 未使用的链接器输入文件 c++ g++ make 文件