C++枚举类型与整型相比使用速度更慢吗?

68

这是一个很简单的问题:

我正在编写一个Go程序。我应该用QVector<int>还是QVector<Player>来表示棋盘?

enum Player
{
    EMPTY = 0,
    BLACK = 1,
    WHITE = 2
};

我猜使用 Player 而不是整数会更慢。但我想知道差别有多大,因为我认为使用 enum 是更好的编码方式。

关于分配和比较 Player(而不是 int),我已经进行了一些测试。

QVector<int> vec;
vec.resize(10000000);
int size = vec.size();


for(int i =0; i<size; ++i)
{
    vec[i] = 0;
}


for(int i =0; i<size; ++i)
{
    bool b = (vec[i] == 1);
}


QVector<Player> vec2;
vec2.resize(10000000);
int size = vec2.size();


for(int i =0; i<size; ++i)
{
    vec2[i] = EMPTY;
}


for(int i =0; i<size; ++i)
{
    bool b = (vec2[i] == BLACK);
}

基本上,速度只慢了10%。在继续之前,我还应该知道什么吗?

谢谢!

编辑:10%的差异不是我幻想出来的,它似乎特定于Qt和QVector。当我使用std::vector时,速度相同。


47
在提问之前进行运行性能测试,这个做法值得点赞!这种情况很少见! - Björn Pollex
你在运行这些性能测试时是否仍然开启了调试信息?结果应该不会显示任何性能差异,因此可能是缺少编译器优化的问题。或者可能是你用来测试性能的方法有问题。 - Tommy Andersen
我曾经进行过类似的性能测试(创建一个大循环并执行一百万次简单操作),发现执行顺序可能会影响结果。也许这可以解释你的结果? - Kricket
1
真的很奇怪。我已经测试了开启和关闭优化,两个测试都显示出性能上的明显差异。我尝试检查汇编输出,但我唯一能够弄清楚的是代码非常不同。也许这与QVector用来找出如何处理特定类型的技巧有关。这与编译器处理枚举和整数的方式无关,纯粹是QVector的问题。虽然在现实生活中没有任何区别。 - Sergei Tachenov
你尝试过检查sizeof(Player)sizeof(int)之间的差异吗?编译器可能已经决定,由于Player中只有3个值,它可以用更小的类型表示,这可能会给你带来性能损失。 - Shahbaz
显示剩余8条评论
7个回答

85

枚举在编译时完全解析(枚举常量作为整数文字,枚举变量作为整数变量),因此使用它们不会有速度惩罚。

一般来说,普通的枚举类型底层类型不会比int更大(除非你放入非常大的常量)。实际上,在§7.2 ¶ 5中明确指出:

 

枚举的底层类型是可以表示定义在枚举中的所有枚举值的整数类型。 使用哪种整数类型作为枚举的基础类型是有实现定义的,但底层类型不得超过int除非枚举值无法适合于intunsigned int

当适用时应使用枚举,因为它们通常使代码易于阅读和维护(您是否曾经尝试调试充满“魔术数字”的程序?:S)。

至于您的结果:可能您的测试方法没有考虑到在“普通”计算机上运行代码时通常会出现的速度波动1; 您是否尝试运行测试多次(100+次)并计算时间的平均值和标准差?结果应该是兼容的:两个标准差的平方和的平方根,相差不应超过平均值的1到2倍2(假设通常情况下波动呈高斯分布)。

您可以进行另一个检查,即比较生成的汇编代码(使用g++可以使用-S开关获取它)。


  1. 在“普通”计算机上运行时,由于其他任务运行、缓存/RAM/VM状态等原因,会有一些不确定性波动。
  2. 根号平方和,两个标准差平方和的平方根。

17
我被教导说,魔法数字是法术“工作稳定”(3级)的主要组成部分。 - corsiKa
2
我目前正在开发一个充满魔术字符串的系统。例如'if (code.equals("A") && status.equals("T")'等等。我们正在逐步构建一组枚举,以便更好地理解它们。 - Jay
2
@Jay:这种东西通常在The Daily WTF上看到...可悲的是这样的东西确实存在。:( - Matteo Italia
@yes123:是的,我是 :) 呃...你是谁? - Matteo Italia
@yes123:现在你已经引起了我的好奇心,你在那边用什么昵称? - Matteo Italia

47

通常情况下,使用枚举对性能没有任何影响。你是如何测试的?

我刚刚自己进行了测试。这些差异纯属噪声。

刚才,我将两个版本编译成汇编代码。这里是每个版本的主要函数:

int

LFB1778:
        pushl   %ebp
LCFI11:
        movl    %esp, %ebp
LCFI12:
        subl    $8, %esp
LCFI13:
        movl    $65535, %edx
        movl    $1, %eax
        call    __Z41__static_initialization_and_destruction_0ii
        leave
        ret

玩家

LFB1774:
        pushl   %ebp
LCFI10:
        movl    %esp, %ebp
LCFI11:
        subl    $8, %esp
LCFI12:
        movl    $65535, %edx
        movl    $1, %eax
        call    __Z41__static_initialization_and_destruction_0ii
        leave
        ret

仅凭微基准测试来做出有关性能的任何陈述都具有风险,因为存在太多干扰数据的外部因素。


测试已经运行了大约10次。整数的时间在399到421毫秒之间。枚举类型的时间在429到438毫秒之间。这种差异的原因让我感到好奇。 - B. Decoster
27
+1 是用于比较汇编本身的。演示两个程序具有相同的汇编代码已经超越了基准测试的需求。 - Brian
有时候我希望写一篇文章:不要编写微基准测试,因为你做不到 :) - bestsss
1
你在这里使用了一个优化编译器,它已经删除了所有的代码。使用问题中发布的代码,但使用std::vector,在发布版本中消除了第二个for循环。将第二个for循环的内容替换为if (vec[i] == 1) break;可以防止优化器删除循环。 - Skizz
@Skizz:同意,但这样做就没有同样的影响力了,不是吗?;-) - Marcelo Cantos

18

枚举不应该慢。它们被实现成整数。


3
如果您使用Visual Studio,例如,您可以创建一个简单的项目,在此项目中,您将拥有:
     a=Player::EMPTY;

如果您右键单击“转到反汇编”,则代码将如下所示:

mov         dword ptr [a],0

因此,编译器替换枚举的值,通常不会产生任何开销。


3

好的,我做了一些测试,发现整数和枚举形式之间没有太大差异。我还添加了一个字符形式,速度始终快了约6%(这并不奇怪,因为它使用的内存更少)。然后我只是使用了一个字符数组而不是一个向量,速度快了300%!由于我们没有得到QVector是什么,它可能是对数组的包装,而不是我使用的std :: vector。

以下是我使用的代码,在Dev Studio 2005中使用标准发行选项进行编译。请注意,我已经稍微修改了定时循环,因为问题中的代码可以被优化为无效代码(您必须检查汇编代码)。

#include <windows.h>
#include <vector>
#include <iostream>

using namespace std;

enum Player
{
    EMPTY = 0,
    BLACK = 1,
    WHITE = 2
};


template <class T, T search>
LONGLONG TimeFunction ()
{
  vector <T>
    vec;

  vec.resize (10000000);

  size_t
    size = vec.size ();

  for (size_t i = 0 ; i < size ; ++i)
  {
      vec [i] = static_cast <T> (rand () % 3);
  }

  LARGE_INTEGER
    start,
    end;

  QueryPerformanceCounter (&start);

  for (size_t i = 0 ; i < size ; ++i)
  {
    if (vec [i] == search)
    {
      break;
    }
  }

  QueryPerformanceCounter (&end);

  return end.QuadPart - start.QuadPart;
}

LONGLONG TimeArrayFunction ()
{
  size_t
    size = 10000000;

  char
    *vec = new char [size];

  for (size_t i = 0 ; i < size ; ++i)
  {
      vec [i] = static_cast <char> (rand () % 3);
  }

  LARGE_INTEGER
    start,
    end;

  QueryPerformanceCounter (&start);

  for (size_t i = 0 ; i < size ; ++i)
  {
    if (vec [i] == 10)
    {
      break;
    }
  }

  QueryPerformanceCounter (&end);

  delete [] vec;

  return end.QuadPart - start.QuadPart;
}

int main ()
{
  cout << "   Char form = " << TimeFunction <char, 10> () << endl;
  cout << "Integer form = " << TimeFunction <int, 10> () << endl;
  cout << " Player form = " << TimeFunction <Player, static_cast <Player> (10)> () << endl;
  cout << "  Array form = " << TimeArrayFunction () << endl;
}

2
编译器应该将enum转换为整数。它们在编译时被内联,因此一旦程序编译完成,使用整数本身应该与使用enum完全相同。
如果您的测试结果不同,则可能是测试本身出了问题。或者,您的编译器行为异常。

2

这是依赖于实现的,枚举和整数可能具有不同的性能,而且会生成相同或不同的汇编代码,虽然这可能意味着编译器不够优化。一些获取差异的方法包括:

  • QVector 可能针对你的枚举类型进行专门处理而产生意外结果。
  • 枚举不会被编译成 int 而是 "某个不大于 int 的整数类型"。int 类型的 QVector 和某个整数类型的 QVector 可能有不同的专门处理方式。
  • 即使 QVector 没有被专门处理,编译器在内存中对齐 int 类型的表现可能比对齐某个整数类型的表现更好,在循环枚举或某个整数类型的向量时可能导致更多的高速缓存缺失率。

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