У меня есть приложение, где часть внутреннего цикла была в основном:
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 версия, которая является, почему исходные строки подпрыгивают.Спасибо.
Вот почему: многие общие архитектуры (включая 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, но я мог бы ошибаться).
Вот какой-то код, созданный 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]
Как вы можете видеть, преобразование без знака делает довольно много работы.