Являются ли перечисления С++ медленнее, чем целые?

Это действительно простая проблема:

Я программирую программу Go. Должен ли я представить плату с QVector<int> или QVector<Player>, где

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

Я предполагаю, что, конечно, использование Player вместо целых будет медленнее. Но я удивляюсь, насколько больше, потому что я считаю, что использование enum - лучшее кодирование.

Я провел несколько тестов относительно назначения и сравнения игроков (в отличие от 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, скорость те же

Ответ 1

Перечисления полностью разрешаются во время компиляции (константы перечисления как целочисленные литералы, переменные перечисления как целочисленные переменные), при использовании их нет ограничения скорости.

В общем случае среднее перечисление не будет иметь базовый тип, превышающий int (если вы не вложите в него очень большие константы); в фактах, в §7.2 ¶ 5 он прямо сказал:

Основной тип перечисления является интегральным типом, который может представлять все значения перечисления, определенные в перечислении. Определяется реализация, который является интегральным типом в качестве базового типа для перечисления , за исключением того, что базовый тип не должен превышать int, если значение перечислителя не может быть помещено в int или unsigned int.

Вы должны использовать перечисления, когда это уместно, потому что они обычно делают код более легким для чтения и поддержания (вы когда-нибудь пробовали отлаживать программу, полную "магических чисел"? :S).

Что касается ваших результатов: возможно, ваша методология тестирования не учитывает нормальные колебания скорости, которые вы получаете при запуске кода на "нормальных" машинах 1; вы пытались запустить тест на многие (100+) раз и вычислить среднее и стандартное отклонение от времени? Результаты должны быть совместимы: разница между средствами не должна превышать 1 или 2 раза от RSS 2 двух стандартных отклонений (предполагая, как обычно, распределение Гаусса для флуктуации).

Еще одна проверка, которую вы можете сделать, это сравнить сгенерированный код сборки (с g++ вы можете получить его с помощью переключателя -S).


  1. На "нормальных" ПК у вас есть некоторые неопределенные колебания из-за других запущенных задач, состояния кэша/ОЗУ/VM,...
  2. Root Sum Squared, квадратный корень из суммы квадратов стандартных отклонений.

Ответ 2

В общем, использование перечисления не должно иметь абсолютно никакого значения для производительности. Как вы это протестировали?

Я только что проверил тесты. Различия - чистый шум.

Только сейчас я скомпилировал обе версии для ассемблера. Здесь основная функция от каждого:

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

Это опасно для основания любых заявлений о производительности на микро-тестах. Слишком много посторонних факторов, искажающих данные.

Ответ 3

Перечисления должны быть не медленнее. Они реализованы как целые числа.

Ответ 4

если вы используете Visual Studio, например, вы можете создать простой проект, в котором вы

     a=Player::EMPTY;

и если вы щелкните правой кнопкой мыши "перейти к разборке", код будет

mov         dword ptr [a],0

Итак, компилятор заменит значение перечисления, и обычно он не будет создавать никаких служебных данных.

Ответ 5

Хорошо, я сделал несколько тестов, и не было большой разницы между целыми и перечисляемыми формами. Я также добавил форму char, которая была примерно на 6% быстрее (что не удивительно, поскольку она использует меньше памяти). Тогда я просто использовал char массив, а не вектор, и это было на 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;
}

Ответ 6

Компилятор должен преобразовать enum в целые числа. Они вставляются во время компиляции, поэтому, как только ваша программа скомпилирована, она должна быть точно такой же, как если бы вы использовали сами целые числа.

Если ваше тестирование дает разные результаты, может быть что-то происходит с самим тестом. Либо это, либо ваш компилятор ведет себя странно.

Ответ 7

Это зависит от реализации, и вполне возможно, что перечисления и ints имеют разную производительность и одинаковый или различный ассемблерный код, хотя это, вероятно, признак субоптимального компилятора. некоторые способы получить различия:

  • QVector может специализироваться на вашем типе перечисления, чтобы сделать что-то неожиданное.
  • enum не скомпилируется в int, а в "некоторый интегральный тип не больше, чем int". QVector от int может быть специализирован по-разному от QVector некоторого_интегрального_типа.
  • даже если QVector не является специализированным, компилятор может лучше выполнять выравнивание ints в памяти, чем выравнивание some_integral_type, что приводит к большей пропускной способности кеша при прокрутке вектора перечислений или some_integral_type.