【问题标题】:How to speed up the run time of of a time-consuming calculation如何加快耗时计算的运行时间
【发布时间】:2017-03-23 02:53:36
【问题描述】:

我正在尝试编写一个函数来将图像转换为 Windows 控制台的字符和颜色。目前,对于 700x700 像素的图像,计算大约需要 13 秒,但该时间是不可取的,尤其是当我计划使函数更复杂以考虑字符形状时。

有哪些方法可以加快 C++ 中的繁重计算和循环,如下所示?有人向我推荐了多线程、SIMD 和内联汇编,但我将如何使用这些方法改进如下所示的功能?

这是我正在使用的当前代码。

unsigned char characterValues[256] = { 0 };

// This operation can be done ahead of time when the program is started up
{
    ResourceInputStream in = ResourceInputStream();
    // This image is the font for the console. The background color is black while the foreground color is white
    in.open(BMP_FONT, 2); // 2 is for RT_BITMAP, BMP_FONT is a resource
    if (in.isOpen()) {
        auto bmp = readBitmap(&in, true);
        in.close();
        for (int x = 0; x < bmp->size.x; x++) {
            for (int y = 0; y < bmp->size.y; y++) {
                int charIndex = (x / 8) + (y / 12) * 16;
                if (bmp->pixels[x][y].r == 255)
                    characterValues[charIndex]++;
            }
        }
    }
}
// This operation is for asciifying the image
{
    FileInputStream in = FileInputStream();
    in.open(R"(image-path.bmp)");
    if (in.isOpen()) {
        auto bmp = readBitmap(&in, false);
        in.close();

        auto image = /* make default image here */
        Point2I imageSize = (Point2I)GMath::ceil((Point2F)bmp->size / Point2F(8.0f, 12.0f));
        int totalImageSize = imageSize.x * imageSize.y;
        image->resize(imageSize);
        auto palette = /* get palette of 16 colors here */

        // Iterate through each (character area)
        for (int imgx = 0; imgx < imageSize.x; imgx++) {
            for (int imgy = 0; imgy < imageSize.y; imgy++) {

                // Read image color value
                int r = 0, g = 0, b = 0;
                int totalRead = 0;
                // Read each pixel inside the bounds of a single character
                for (int px = 0; px < 8; px++) {
                    for (int py = 0; py < 12; py++) {
                        Point2I p = Point2I(imgx * 8 + px, imgy * 12 + py);
                        if (p < bmp->size) {
                            r += bmp->pixels[p.x][p.y].r;
                            g += bmp->pixels[p.x][p.y].g;
                            b += bmp->pixels[p.x][p.y].b;
                            totalRead++;
                        }
                    }
                }
                Color imageValue = Color(r / totalRead, g / totalRead, b / totalRead);

                // A combo of a character and foreground/background color
                Pixel closestPixel = Pixel();
                float closestScore = std::numeric_limits<float>().max();
                for (int col = 1; col < 255; col++) {
                    unsigned char f = getFColor(col);
                    unsigned char b = getBColor(col);
                    for (int ch = 1; ch < 255; ch++) {
                        // Calculate values
                        Color value = Color(
                            (palette[f].r * characterValues[ch] + palette[b].r * (TOTAL_CHARACTER_VALUE - characterValues[ch])) / TOTAL_CHARACTER_VALUE,
                            (palette[f].g * characterValues[ch] + palette[b].g * (TOTAL_CHARACTER_VALUE - characterValues[ch])) / TOTAL_CHARACTER_VALUE,
                            (palette[f].b * characterValues[ch] + palette[b].b * (TOTAL_CHARACTER_VALUE - characterValues[ch])) / TOTAL_CHARACTER_VALUE
                        );
                        // Add up score here
                        float score =
                            (float)((int)value.r - (int)imageValue.r) * (float)((int)value.r - (int)imageValue.r) +
                            (float)((int)value.g - (int)imageValue.g) * (float)((int)value.g - (int)imageValue.g) +
                            (float)((int)value.b - (int)imageValue.b) * (float)((int)value.b - (int)imageValue.b);
                        if (score < closestScore) {
                            closestPixel = Pixel((unsigned char)ch, (unsigned char)col);
                            closestScore = score;
                        }
                    }
                }
                // Set the character/color combo here
            }
        }
    }
}

【问题讨论】:

  • 这属于Code Review,如果你有工作代码并且正在寻求改进它。
  • @John Coleman 谢谢,我现在把它移到那里
  • 您是否尝试过使用 -O3 优化标志进行编译?在让 gcc 有机会首先改进它之前,我不会冒险编写代码。
  • @ScottK 我正在使用 Visual Studio。至于优化,我使用 /O2 指定最大化速度(似乎没有 O3 选项)

标签: c++ performance calculation


【解决方案1】:

你有一个 x 循环和一个嵌套的 y 循环,你确定这是内存中的字节顺序吗?可能是这样,但如果有帮助,您可以随时尝试其他方式。

// could be faster, depending on data structure
 for (int y = 0; y < bmp->size.y; y++) {
     for (int x = 0; x < bmp->size.x; x++) {

但由于 bmp 索引为 [x][y],因此它看起来像是列优先数据,这很奇怪。

您的内部循环中也存在代价高昂的划分。您可以在每个循环之外进行任何循环不变的计算:

 for (int x = 0; x < bmp->size.x; x++) { 
     int charIndex_x = (x / 8);
     for (int y = 0; y < bmp->size.y; y++) {
          int charIndex = charIndex_x + (y / 12) * 16;
          // other stuff

它仍然可以进一步改进,但您只是避免对 256x256 位图执行几乎 65536 次除法运算。

此外,您的内部循环中有一个二维数组取消引用,这些操作成本高昂。您可以记录一个指向列开头的指针,然后递增指针:

 for (int x = 0; x < bmp->size.x; x++) {
     int charIndex_x = (x / 8);
     auto current_pixel = &bmp->pixels[x][0];
     for (int y = 0; y < bmp->size.y; y++) {
          int charIndex = charIndex_x + (y / 12) * 16;
          if (*current_pixel.r == 255)
                characterValues[charIndex]++;
          ++current_pixel;

并在内部循环中增加它。实际上,您也可以将 current_pixel 设置移动到 x 循环之外,但我遇到过这种情况较慢,因为它必须让更多变量在内存中保持活动状态。通常,如果可能,您希望内部循环中的局部变量。将计算移到循环之外会加快速度,但会使用更多的 CPU 内存,这意味着它可能会因为处理更多存储值而变慢。

最后要注意的是,每次通过内部循环时,您都在检查 y 值是否小于“bmp->size.y”,这包括查找 bmp 然后引用 size,然后引用 size.y,这是三个操作,对于 256x256 位图发生 65536 次。您可以在需要之前将 y 大小记录在局部变量中:

 for (int x = 0; x < bmp->size.x; x++) {
     int charIndex_x = (x / 8);
     auto current_pixel = &bmp->pixels[x][0];
     int bmp_size_y = bmp->size.y;
     for (int y = 0; y < bmp_size.y; y++) {
          int charIndex = charIndex_x + (y / 12) * 16;
          if (*current_pixel.r == 255)
                characterValues[charIndex]++;
          ++current_pixel;

您可以将它完全移到 x 循环之外,以避免将值设置为 256 次,因为 bmp->size.y 永远不会改变,但是为此节省的空间非常小,甚至可能会减慢速度用完和额外的寄存器,这可能意味着程序需要在内存中处理更多的东西。

CPU 内存就像您的 Windows PC 上的虚拟内存。如果使用太多,事情会变慢,因为它会将内容分页到磁盘,但是在内存中拥有更多内容也可以加快速度,因为它不需要不断地从磁盘上查找内容。编码是相似的,因为局部变量可以只存储在 CPU 中,避免从内存中查找它们,但是过多的局部变量会使 CPU 过载,这意味着它需要像虚拟内存一样不断地处理它们做。因此,尽可能将局部变量设为“本地”以避免过度使用它们。您应该始终分析您所做的任何更改,看看它们是否真的有帮助。

~~~

至于你的另一个循环,你在内部循环中有许多复杂的重复计算:

bmp->pixels[p.x][p.y]

计算了 3 次,这包括一个指针解引用、两个成员解引用(p.x 和 p.y)然后是 2D 数组解引用(最多是乘法和加法,然后是指针解引用)。那里至少有 6 次原子计算,只是为了每次都获得对该像素的引用。

你可以去:

auto current_pixel = bmp->pixels[p.x][p.y];

更好的是,您正在计算 Point2I,然后检查其 x 和 y 值是否在 bmp 大小范围内。您根本不需要 Point2I,只需计算 x 和 y 大小并分别与 bmp x 和 y 大小进行比较。

计算外部循环中的 x 边界,在那里对 x 进行 if-check,如果 x 超出边界,则完全避免碰到内部循环。将其与避免在内部循环中创建或索引结构相结合,您会得到:

           for (int px = 0; px < 8; px++) {
                int p_x = imgx * 8 + px;
                if(p_x < bmp->size.x) {
                    for (int py = 0; py < 12; py++) {
                        int p_y = imgy * 12 + py;
                        if (p_y < bmp->size.y) {
                            auto pixel = bmp->pixels[p_x][p_y];
                            r += pixel.r;
                            g += pixel.g;
                            b += pixel.b;
                            totalRead++;
                        }
                    }
                }
            }

【讨论】:

  • 位图在 readBitmap 函数中被读取,并以 [x][y] 的形式存储在 bmp->pixels 2D 数组中,这是一个更易读的顺序,因此在那里是正确的。当my example is run 时,输出图像看起来已经正确。
  • 那个“可读命令”是愚蠢的命令。按行打包数据几乎总是更快。您当前的订单仅对愚蠢的人更具“可读性”,按列打包数据的速度较慢。因为像素在几乎所有图形系统中都是逐行打包的,这意味着您的 x/y 数据与内存颗粒背道而驰。这可能会破坏 CPU 内存预加载系统,将读/写速度降低 10 倍;.
  • 究竟是什么使得将数组存储为 [x][y] 比 [y][x] 慢。如果图像被对角翻转,它会得到相同的结果。
  • 您正在读取的数据是什么格式,屏幕是什么格式?两者都使用 y/x 打包,因此如果您还使用 y/x 打包,您可以在输入和输出端节省大量的减速。
【解决方案2】:
for (int x = 0; x < bmp->size.x; x++) {
    for (int y = 0; y < bmp->size.y; y++) {

从最大值开始这两个循环,即分别为bmp-&gt;size.x-1bmp-&gt;size.y-1,然后将它们向下运行到零。这样,您只需在每个循环而不是每次迭代中评估一次边界条件。

int charIndex = (x / 8) + (y / 12) * 16;

除非你打算使用它,否则不要计算它,即将它放入下面的 if 块中。

【讨论】:

    最近更新 更多