최근에 다음과 같은 기본 생성자를 선언했기 때문에 성능이 저하되었음을 알았습니다.
Foo() = default;
대신에
Foo() {}
(참고로, 기본 생성자를 재정의하는 가변 생성자가 있었기 때문에 명시 적으로 선언해야했습니다)
이 두 줄의 코드가 동일하다고 생각했기 때문에 이상하게 보였습니다. 기본 생성자가 가능한 한 기본 생성자가 가능하지 않으면 두 번째 코드 줄에서 오류가 발생하고 첫 번째 줄은 오류가 발생합니다. 기본 생성자를 암시 적으로 삭제합니다. '내 상황이 아닙니다!).
좋아, 그래서 나는 작은 테스터를 만들었고 결과는 컴파일러에 따라 상당히 많이 다르지만 특정 설정을 사용하면 일관된 결과를 얻습니다.
#include <chrono>
template <typename T>
double TimeDefaultConstructor (int n_iterations)
{
auto start_time = std::chrono::system_clock::now();
for (int i = 0; i < n_iterations; ++i)
T t;
auto end_time = std::chrono::system_clock::now();
std::chrono::duration<double> elapsed_seconds = end_time - start_time;
return elapsed_seconds.count();
}
template <typename T, typename S>
double CompareDefaultConstructors (int n_comparisons, int n_iterations)
{
int n_comparisons_with_T_faster = 0;
for (int i = 0; i < n_comparisons; ++i)
{
double time_for_T = TimeDefaultConstructor<T>(n_iterations);
double time_for_S = TimeDefaultConstructor<S>(n_iterations);
if (time_for_T < time_for_S)
++n_comparisons_with_T_faster;
}
return (double) n_comparisons_with_T_faster / n_comparisons;
}
#include <vector>
template <typename T>
struct Foo
{
std::vector<T> data_;
Foo() = default;
};
template <typename T>
struct Bar
{
std::vector<T> data_;
Bar() {};
};
#include <iostream>
int main ()
{
int n_comparisons = 10000;
int n_iterations = 10000;
typedef int T;
double result = CompareDefaultConstructors<Foo<T>,Bar<T>> (n_comparisons, n_iterations);
std::cout << "With " << n_comparisons << " comparisons of " << n_iterations
<< " iterations of the default constructor, Foo<" << typeid(T).name() << "> was faster than Bar<" << typeid(T).name() << "> "
<< result*100 << "% of the time" << std::endl;
std::cout << "swapping orientation:" << std::endl;
result = CompareDefaultConstructors<Bar<T>,Foo<T>> (n_comparisons, n_iterations);
std::cout << "With " << n_comparisons << " comparisons of " << n_iterations
<< " iterations of the default constructor, Bar<" << typeid(T).name() << "> was faster than Foo<" << typeid(T).name() << "> "
<< result*100 << "% of the time" << std::endl;
return 0;
}
위의 프로그램을 사용하면 다음 g++ -std=c++11
과 유사한 출력이 일관되게 나타납니다.
기본 생성자의 10000 반복에 대한 10000 비교에서 Foo는 시간 스와핑 방향의 4.69 %보다 빠릅니다. 기본 생성자의 10000 반복에 대한 10000 비교에서 Bar는 Foo보다 96.23 % 더 빠릅니다.
컴파일러 설정을 변경하면 결과가 변경되는 것처럼 보이며 때로는 완전히 뒤집습니다. 그러나 내가 이해할 수없는 것은 그것이 왜 중요한가?
이 벤치 마크는 측정 대상을 측정하지 않습니다. 교체 Bar() {};
로 Bar() = default;
제작 Foo
하고 Bar
동일하고, 동일한 결과를 얻을 수 있습니다 :
기본 생성자의 10000 반복에 대한 10000 비교에서 Foo는 시간 스와핑 방향의 69.89 %보다 빠릅니다. 기본 생성자의 10000 반복에 대한 10000 비교에서는 Bar가 Foo보다 29.9 % 더 빠릅니다.
이것은 생성자가 아닌 다른 것을 측정하고 있다는 생생한 데모입니다.
-O1
최적화 를 활성화 하면 for
루프가 1T t;
로 저하 됩니다 .
test ebx, ebx
jle .L3
mov eax, 0
.L4:
add eax, 1
cmp ebx, eax
jne .L4
.L3:
모두 Foo
와 Bar
. 즉, 사소한 for (int i = 0; i < n_iterations; ++i);
루프로.
활성화 -O2
하거나 -O3
완전히 최적화되면.
최적화 ( -O0
) 없이 다음 어셈블리를 얻습니다.
mov DWORD PTR [rbp-4], 0
.L35:
mov eax, DWORD PTR [rbp-4]
cmp eax, DWORD PTR [rbp-68]
jge .L34
lea rax, [rbp-64]
mov rdi, rax
call Foo<int>::Foo()
lea rax, [rbp-64]
mov rdi, rax
call Foo<int>::~Foo()
add DWORD PTR [rbp-4], 1
jmp .L35
.L34:
Bar
으로 Foo
대체 된 경우에도 동일합니다 Bar
.
이제 생성자를 살펴 보겠습니다.
Foo<int>::Foo()
push rbp
mov rbp, rsp
sub rsp, 16
mov QWORD PTR [rbp-8], rdi
mov rax, QWORD PTR [rbp-8]
mov rdi, rax
call std::vector<int, std::allocator<int> >::vector()
nop
leave
ret
과
Bar<int>::Bar()
push rbp
mov rbp, rsp
sub rsp, 16
mov QWORD PTR [rbp-8], rdi
mov rax, QWORD PTR [rbp-8]
mov rdi, rax
call std::vector<int, std::allocator<int> >::vector()
nop
leave
ret
보시다시피 이것들도 동일합니다.
GCC 8.3 1 개
이 기사는 인터넷에서 수집됩니다. 재 인쇄 할 때 출처를 알려주십시오.
침해가 발생한 경우 연락 주시기 바랍니다[email protected] 삭제
몇 마디 만하겠습니다