使用模板重写的类会使程序变慢(在运行时)

A class rewritten with templates makes a program slower (in run-time)

本文关键字:运行时 程序 重写      更新时间:2023-10-16

我有一个串行存储器 2D 数组的类,它最初是一个 int 的数组。现在我需要另一种类型的类似数组,我用模板重写了类;唯一的区别是存储对象的类型:

template <class T>
class Serial2DArray
{
    ...
    T ** Content;
}

我有几个处理 Content 的测试函数,例如,一个使数组中的所有元素无效的函数(它们不是类成员,它们是处理Serial2DArray<int>对象的外部函数。我注意到现在它的工作速度慢了 1-2% - 类中的所有其他代码都保持不变,唯一的区别是早些时候它只是一个带有 int ** Content 的常规类,现在它是一个模板。

一个类似的问题:c++ 模板会让程序变慢吗? - 有意见认为只有编译变慢(我明白为什么,编译器为它在代码中找到的每个类生成类),但在这里我看到程序在运行时变慢 - 有什么合理的解释吗?

更新:问题在这里缩小了一点:https://stackoverflow.com/a/11058672/1200000

Upd2:如评论中所述,这是变慢的功能:

#include <windows.h>
#include <mmsystem.h>
...
int Size = G_Width * G_Height * sizeof(int);
DWORD StartTime = timeGetTime();
for(int i=0; i<100; ++i)
{
    FillMemory(TestArray.Content[0], Size, 0);
}
MeasuredTime = timeGetTime() - StartTime;

下面是实际的类模板:

#include <malloc.h>
template <class T>
class Serial2DArray
{
    public:
    Serial2DArray()
    {
        Content = NULL;
        Width = 0;
        Height = 0;
    }
    Serial2DArray(int _Width, int _Height)
    {
        Initialize(_Width, _Height);
    }
    ~Serial2DArray()
    {
        Deinitialize();
    }
    T ** Content;
    int GetWidth()
    {
        return Width;
    }
    int GetHeight()
    {
        return Height;
    }
    int Initialize(int _Width, int _Height)
    {
        // creating pointers to the beginning of each line
        if((Content = (T **)malloc(_Height * sizeof(T *))) != NULL)
        {
            // allocating a single memory chunk for the whole array
            if((Content[0] = (T *)malloc(_Width * _Height * sizeof(T))) != NULL)
            {
                // setting up line pointers' values
                T * LineAddress = Content[0];
                for(int i=0; i<_Height; ++i)
                {
                    Content[i] = LineAddress; // faster than Content[i] =
                    LineAddress += _Width;    // Content[0] + i * _Width;
                }
                // everything went ok, setting Width and Height values now
                Width = _Width;
                Height = _Height;
                // success
                return 1;
            }
            else
            {
                // insufficient memory available
                // need to delete line pointers
                free(Content);
                return 0;
            }
        }
        else
        {
            // insufficient memory available
            return 0;
        }
    }
    int Resize(int _Width, int _Height)
    {
        // deallocating previous array
        Deinitialize();
        // initializing a new one
        return Initialize(_Width, _Height);
    }
    int Deinitialize()
    {
        // deleting the actual memory chunk of the array
        free(Content[0]);
        // deleting pointers to each line
        free(Content);
        // success
        return 1;
    }
    private:
    int Width;
    int Height;
};

根据要求,二进制文件大小比较。

使用以下代码进行编码:

Serial2DArray<int> TestArray; 
Serial2DArray<int> ZeroArray;
  • 1 016 832 字节。

使用以下代码进行编码:

Serial2DArray TestArray; // NOT-template class with ints
Serial2DArray ZeroArray; // methods are in class declaration
  • 1 016 832 字节

使用以下代码进行编码:

Serial2DArray<int> TestArray;
Serial2DArray<int> ZeroArray;
Serial2DArray<double> AnotherArray;
Serial2DArray<double> YetAnotherArray;
  • 1 017 344 字节

是的 - 随机基准测试可变性,更不用说整个程序较慢的事实可能与这个特定的类完全无关。

在容器类中使用模板可能会导致模板代码膨胀的已知问题。大致上,它可能会导致程序中出现更多页面错误,从而降低性能。

那你为什么要问?因为模板将为模板的每个类实例生成类,而不是一个类实例,从而在二进制产品中生成更多页面,如果您愿意,还可以生成更多代码页。这在统计上可能会导致更多的页面错误,具体取决于您的运行时执行。

查看一个类模板实例的二进制文件的大小,以及两个必须最重的实例。它将使您掌握新实例引入的新代码大小。

这是关于该主题的维基百科文章:代码膨胀文章。当强制编译器内联程序中的每个函数和方法时,如果编译器可以使用它,则问题可能是相同的。该标准试图通过将inline关键字作为编译器每次都不得遵循的"请求"来防止这种情况。例如,GCC 以中间语言生成您的代码,以评估生成的二进制文件是否会导致代码膨胀,并可能因此丢弃内联请求。