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

3

我有一个序列式内存二维数组的类,最初是一个 int 类型的数组。现在我需要一个类型相似的数组,但存储的对象类型不同,因此我使用了模板重写了这个类;唯一的区别就在于存储对象的类型:

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

我有一些处理内容的测试函数,例如将数组中所有元素置空的函数(它们不是类成员,而是用于与Serial2DArray<int>对象一起工作的函数)。我注意到现在它的速度变慢了1-2%——类中的所有其他代码都没有改变,唯一的区别是以前它只是一个带有int ** Content 的普通类,现在它是一个模板。
类似的问题:C++模板会使程序变慢吗? - 有人认为仅编译变慢(我能看出来,编译器为其在代码中找到的每个类型生成类),但我看到程序在运行时变慢了——是否有任何合理的解释?
更新:问题在此处被缩小了一点:https://stackoverflow.com/a/11058672/1200000 更新2:如评论所述,下面是变慢的函数:
#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字节

这是哪个编译器?看起来这只会给编译器提供更多信息(并不会强制进行任何负优化,比如总是内联)。 - David Stone
Embarcadero RAD Studio 2010 C++ Builder 我想我们已经找到了问题所在。并不是一个好的编译器... - ildjarn
1
@yvesBaumes嗯,还有一个额外的信息,我作为答案发布了(并被删除了) - 那个在类声明中具有方法的非模板类也很慢,而将方法放在单独的.cpp文件中的非模板类(并在类声明中使用.h头文件)则很快。否则这两个版本是相同的(只是将实际方法从/到类声明中移动)。所以我开始觉得也许这根本不是模板的问题?.. - Fy Zn
1
从我的角度来看,有关在类内部声明的慢速NOT-Template类,原因可能是:编译器会把在类内部声明的方法隐式视为开发者请求进行内联。就像通常的getter和setter访问器一样,您不需要放置inline关键字来请求内联。那么它将加入代码膨胀问题,但这也意味着您的编译器跟随了请求,而实际上它没有跟随(...) - yves Baumes
1
等一下,如果是2.9对比2.8或者29对比28,并且完全可持续,那么为什么原问题会说“我注意到现在它的速度变慢了1-2%”?如果误差范围足够大,以至于将3.5%称为1-2%是合适的,那么这个测量结果就不是很有用了。 - abarnert
显示剩余12条评论
2个回答

2

是的,随机基准测试的变异性,更不用说整个程序速度变慢可能与这个特定类别无关。


1
这是我编写的一个小程序,用于测试特定类,它只有一个该类的对象和两个不被修改的将元素转换为0的函数。在不同的机器上和许多后续测试中,这1-2%的减速是一致的。 - Fy Zn
你是否都使用了优化编译两个版本? - Eitan T
是的,并且它们两个具有相同的设置。 - Fy Zn
@fynjyzn:这不是问题所在。两个版本都可能是以调试模式编译的。 - ildjarn
@ildjarn 我明白了,两者都是在发布模式下。 - Fy Zn

-2

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

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

看一下只有一个类模板实例和两个实例的二进制文件大小,后者必须是最重的。这将让您了解新实例引入的新代码大小。

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


2
已知的非问题,更像是。首先,编译器可以折叠具有相同汇编程序的模板实例。其次,更多的代码只意味着如果您在其中跳来跳去,则会导致更多的页面错误。 - Puppy
@yves:对于第一点,至少7年——并非最近。 - ildjarn
@ildjarn:我不知道地球上的每个编译器实现。请随意测试并向我报告供应商、编译器问题以及测试结果。我很感兴趣。但是,我最近使用 GCC 3.4 进行的测试显示出与 OP 相同类型的性能问题,非常简单的测试程序也是如此。 - yves Baumes
@yves:考虑到原帖没有实际发布任何代码,测试有点困难。;-]也许如果他们发布了一个SSCCE…… - ildjarn
1
@yves:真讽刺,因为是你在散布关于代码膨胀的错误信息。 - ildjarn
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接