Различие в скорости между использованием международного и неподписанного интервала при смешивании с удваивается

У меня есть приложение, где часть внутреннего цикла была в основном:

double sum = 0;
for (int i = 0; i != N; ++i, ++data, ++x) sum += *data * x;

Если x является неподписанным интервалом, то код берет в 3 раза более долго, чем с интервалом!

Это было частью большей кодовой базы, но я свалил ее к основам:

#include <iostream>                                      
#include <cstdlib>                                       
#include <vector>
#include <time.h>

typedef unsigned char uint8;

template<typename T>
double moments(const uint8* data, int N, T wrap) {
    T pos = 0;
    double sum = 0.;
    for (int i = 0; i != N; ++i, ++data) {
        sum += *data * pos;
        ++pos;
        if (pos == wrap) pos = 0;
    }
    return sum;
}

template<typename T>
const char* name() { return "unknown"; }

template<>
const char* name<int>() { return "int"; }

template<>
const char* name<unsigned int>() { return "unsigned int"; }

const int Nr_Samples = 10 * 1000;

template<typename T>
void measure(const std::vector<uint8>& data) {
    const uint8* dataptr = &data[0];
    double moments_results[Nr_Samples];
    time_t start, end;
    time(&start);
    for (int i = 0; i != Nr_Samples; ++i) {
        moments_results[i] = moments<T>(dataptr, data.size(), 128);
    }
    time(&end);
    double avg = 0.0;
    for (int i = 0; i != Nr_Samples; ++i) avg += moments_results[i];
    avg /= Nr_Samples;
    std::cout << "With " << name<T>() << ": " << avg << " in " << (end - start) << "secs" << std::endl;
}


int main() {
    std::vector<uint8> data(128*1024);
    for (int i = 0; i != data.size(); ++i) data[i] = std::rand();
    measure<int>(data);
    measure<unsigned int>(data);
    measure<int>(data);
    return 0;
}

Компиляция без оптимизации:

luispedro@oakeshott:/home/luispedro/tmp/so §g++  test.cpp    
luispedro@oakeshott:/home/luispedro/tmp/so §./a.out
With int: 1.06353e+09 in 9secs
With unsigned int: 1.06353e+09 in 14secs
With int: 1.06353e+09 in 9secs

С оптимизацией:

luispedro@oakeshott:/home/luispedro/tmp/so §g++  -O3  test.cpp
luispedro@oakeshott:/home/luispedro/tmp/so §./a.out
With int: 1.06353e+09 in 3secs
With unsigned int: 1.06353e+09 in 12secs
With int: 1.06353e+09 in 4secs

Я не понимаю почему такие значительные различия в скорости. Я пытался изобразить его из сгенерированного блока, но я не добрался нигде. У кого-либо есть какие-либо мысли?

Это - что-то, чтобы сделать с аппаратными средствами, или действительно ли это - ограничение оборудования для оптимизации gcc? Я ставлю второе.

Моей машиной является Intel под управлением Ubuntu 9.10 на 32 бита.

Править: Так как Stephen спросил, вот декомпилируемый источник (от-O3 компиляции). Я полагаю, что получил основные циклы:

международная версия:

40: 0f b6 14 0b             movzbl (%ebx,%ecx,1),%edx
     sum += *data * pos;
44: 0f b6 d2                movzbl %dl,%edx
47: 0f af d0                imul   %eax,%edx
      ++pos;
4a: 83 c0 01                add    $0x1,%eax
      sum += *data * pos;
4d: 89 95 54 c7 fe ff       mov    %edx,-0x138ac(%ebp)
      ++pos;
      if (pos == wrap) pos = 0;
53: 31 d2                   xor    %edx,%edx
55: 3d 80 00 00 00          cmp    $0x80,%eax
5a: 0f 94 c2                sete   %dl
  T pos = 0;
  double sum = 0.;
  for (int i = 0; i != N; ++i, ++data) {
5d: 83 c1 01                add    $0x1,%ecx
      sum += *data * pos;
60: db 85 54 c7 fe ff       fildl  -0x138ac(%ebp)
      ++pos;
      if (pos == wrap) pos = 0;
66: 83 ea 01                sub    $0x1,%edx
69: 21 d0                   and    %edx,%eax
  T pos = 0;
  double sum = 0.;
  for (int i = 0; i != N; ++i, ++data) {
6b: 39 f1                   cmp    %esi,%ecx
      sum += *data * pos;
6d: de c1                   faddp  %st,%st(1)
  T pos = 0;
  double sum = 0.;
  for (int i = 0; i != N; ++i, ++data) {
6f: 75 cf                   jne    40

неподписанная версия:

50: 0f b6 34 13             movzbl (%ebx,%edx,1),%esi
      sum += *data * pos;
54: 81 e6 ff 00 00 00       and    $0xff,%esi
5a: 31 ff                   xor    %edi,%edi
5c: 0f af f0                imul   %eax,%esi
      ++pos;
5f: 83 c0 01                add    $0x1,%eax
      if (pos == wrap) pos = 0;
62: 3d 80 00 00 00          cmp    $0x80,%eax
67: 0f 94 c1                sete   %cl
  T pos = 0;
  double sum = 0.;
  for (int i = 0; i != N; ++i, ++data) {
6a: 83 c2 01                add    $0x1,%edx
      sum += *data * pos;
6d: 89 bd 54 c7 fe ff       mov    %edi,-0x138ac(%ebp)
73: 89 b5 50 c7 fe ff       mov    %esi,-0x138b0(%ebp)
      ++pos;
      if (pos == wrap) pos = 0;
79: 89 ce                   mov    %ecx,%esi
7b: 81 e6 ff 00 00 00       and    $0xff,%esi
      sum += *data * pos;
81: df ad 50 c7 fe ff       fildll -0x138b0(%ebp)
      ++pos;
      if (pos == wrap) pos = 0;
87: 83 ee 01                sub    $0x1,%esi
8a: 21 f0                   and    %esi,%eax
  for (int i = 0; i != N; ++i, ++data) {
8c: 3b 95 34 c7 fe ff       cmp    -0x138cc(%ebp),%edx
      sum += *data * pos;
92: de c1                   faddp  %st,%st(1)
  for (int i = 0; i != N; ++i, ++data) {
94: 75 ba                   jne    50

Это-O3 версия, которая является, почему исходные строки подпрыгивают.Спасибо.

19
задан Stephen Canon 5 May 2011 в 01:01
поделиться

2 ответа

Вот почему: многие общие архитектуры (включая x86) имеют аппаратное инструкцию для преобразования подписания int, чтобы удваиваться, но не имеют аппаратного преобразования из unsignied, чтобы удвоить, поэтому компилятор должен синтезировать Преобразование в программное обеспечение. Кроме того, единственное неподписанное умножение на Intel - это полная ширина, умножна, тогда как подписанные умножки могут использовать подписанную умножую низкую инструкцию.

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

Предполагая, что умный компилятор, разница должна быть намного меньше на 64-битной системе, поскольку 64-битное значение Integer -> двойное преобразование может быть использовано для эффективного преобразования 32-битной без знака.

Редактировать: , чтобы проиллюстрировать, это:

sum += *data * x;

, если целочисленные переменные подписаны, следует скомпилировать во что-то вдоль этих строк:

mov       (data),   %eax
imul      %ecx,     %eax
cvtsi2sd  %eax,     %xmm1
addsd     %xmm1,    %xmm0

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

    mov       (data),   %eax
    mul       %ecx            // might be slower than imul
    cvtsi2sd  %eax,     %xmm1 // convert as though signed integer
    test      %eax,     %eax  // check if high bit was set
    jge       1f              // if it was, we need to adjust the converted
    addsd     (2^32),   %xmm1 // value by adding 2^32
1:  addsd     %xmm1,    %xmm0

, который был бы «приемлемым» кодегеном для беззнаков -> двойной преобразования; Это может быть легко хуже.

Все это предполагает генерацию кода с плавающей точкой в ​​SSE (я считаю, что это значение по умолчанию на инструментах Ubuntu, но я мог бы ошибаться).

33
ответ дан 30 November 2019 в 03:34
поделиться

Вот какой-то код, созданный VC ++ 6.0 - без оптимизации:

4:        int x = 12345;
0040E6D8   mov         dword ptr [ebp-4],3039h
5:        double d1 = x;
0040E6DF   fild        dword ptr [ebp-4]
0040E6E2   fstp        qword ptr [ebp-0Ch]
6:        unsigned int y = 12345;
0040E6E5   mov         dword ptr [ebp-10h],3039h
7:        double d2 = y;
0040E6EC   mov         eax,dword ptr [ebp-10h]
0040E6EF   mov         dword ptr [ebp-20h],eax
0040E6F2   mov         dword ptr [ebp-1Ch],0
0040E6F9   fild        qword ptr [ebp-20h]
0040E6FC   fstp        qword ptr [ebp-18h]

Как вы можете видеть, преобразование без знака делает довольно много работы.

3
ответ дан 30 November 2019 в 03:34
поделиться