почему std :: равно намного медленнее, чем циклический цикл для двух маленьких std :: array?

Я профилировал небольшой фрагмент кода, который является частью большей симуляции, и, к моему удивлению, функция равенства STL (std :: equal) намного медленнее, чем простой цикл for, сравнивая два элемента массива элемент за элементом. Я написал небольшой тестовый пример, который, по моему мнению, представляет собой справедливое сравнение между ними, и разница с использованием g ++ 6.1.1 из архивов Debian не является незначительной. Я сравниваю два четырехэлементных массива целых чисел со знаком. Я протестировал std :: equal, operator == и маленький цикл for. Я не использовал std :: chrono для точного определения времени, но разницу можно увидеть со временем ./a.out.

Мой вопрос, учитывая приведенный ниже пример кода, почему оператор == и перегруженная функция std :: equal (которая, как я полагаю, вызывает оператор ==) требуют около 40 секунд, а рукописный цикл занимает всего 8 секунд? Я использую новейший ноутбук на базе Intel. Цикл for работает быстрее на всех уровнях оптимизации: -O1, -O2, -O3 и -Ofast. Я скомпилировал код с
g++ -std=c++14 -Ofast -march=native -mtune=native

Запустите код

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

#include<iostream>
#include<algorithm>
#include<array>

using namespace std;
using T = array<int32_t, 4>;

bool
are_equal_manual(const T& L, const T& R)
noexcept {
bool test{ true };
for(uint32_t i{0}; i < 4; ++i) { test = test && (L[i] == R[i]); }
return test;
}

bool
are_equal_alg(const T& L, const T& R)
noexcept {
bool test{ equal(cbegin(L),cend(L),cbegin(R)) };
return test;
}

int main(int argc, char** argv) {

T left{ {0,1,2,3} };
T right{ {0,1,2,3} };

cout << boolalpha << are_equal_manual(left,right) << endl;
cout << boolalpha << are_equal_alg(left,right) << endl;
cout << boolalpha << (left == right) << endl;

bool t{};
const size_t N{ 5000000000 };
for(size_t i{}; i < N; ++i) {
//t = left == right; // SLOW
//t = are_equal_manual(left,right); // FAST
t = are_equal_alg(left,right);  // SLOW
left[0] = i % 10;
right[2] = i % 8;
}

cout<< boolalpha << t << endl;

return(EXIT_SUCCESS);
}

9

Решение

Вот сгенерированная сборка for зациклиться main() когда are_equal_manual(left,right) функция используется:

.L21:
xor     esi, esi
test    eax, eax
jne     .L20
cmp     edx, 2
sete    sil
.L20:
mov     rax, rcx
movzx   esi, sil
mul     r8
shr     rdx, 3
lea     rax, [rdx+rdx*4]
mov     edx, ecx
add     rax, rax
sub     edx, eax
mov     eax, edx
mov     edx, ecx
add     rcx, 1
and     edx, 7
cmp     rcx, rdi

И вот что генерируется, когда are_equal_alg(left,right) функция используется:

.L20:
lea     rsi, [rsp+16]
mov     edx, 16
mov     rdi, rsp
call    memcmp
mov     ecx, eax
mov     rax, rbx
mov     rdi, rbx
mul     r12
shr     rdx, 3
lea     rax, [rdx+rdx*4]
add     rax, rax
sub     rdi, rax
mov     eax, ebx
add     rbx, 1
and     eax, 7
cmp     rbx, rbp
mov     DWORD PTR [rsp], edi
mov     DWORD PTR [rsp+24], eax
jne     .L20

Я не совсем уверен, что происходит в сгенерированном коде для первого случая, но он явно не вызывает memcmp(), Кажется, он не сравнивает содержимое массивов вообще. Хотя цикл все еще повторяется 5000000000 раз, он оптимизирован, чтобы ничего не делать. Тем не менее, цикл, который использует are_equal_alg(left,right) все еще выполняет сравнение. По сути, компилятор все еще может оптимизировать ручное сравнение намного лучше, чем std::equal шаблон.

1

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]