深度探索C++对象模型(21)——函数语义学(5)——函数调用、继承关系性能

1.函数调用中编译器的循环代码优化

代码:

#include <iostream>
#include <ctime>

using namespace std;

namespace _nmsp1 //命名空间
{

	__int64 mytest(int mv)
	{
		__int64 icout = 0;
		for (int i = 1; i < 1000000; i++)
		{
			icout += 1;
			//if (i == 10000 && mv == 999)
			//{
			//	printf("------\n");
			//}
		}
		//icout += 循环多少次之后的和值
		return icout;
	}

	void func()
	{
		clock_t start, end;
		__int64 mycout = 1;
		start = clock();
		for (int i = 0; i <= 1000; i++)
		{
			//mycout += mytest(i); //给固定参数时,编译器将这种参数固定的函数调用视为一种不变的表达式
			mycout += mytest(6);
		}
		//mycout += 循环1000次的和值
		end = clock();
		cout << " 用时(毫秒):" << end - start << endl;
		cout << " mycout:" << mycout << endl;
	}
}
int main()
{
	_nmsp1::func();
	return 1;
}

在VS下编译有两种版本,debug版(会插入许多信息用于调试)和release版(会进行代码优化,用于发布),下面使用release运行

debug版运行结果:

深度探索C++对象模型(21)——函数语义学(5)——函数调用、继承关系性能

release版运行结果:

深度探索C++对象模型(21)——函数语义学(5)——函数调用、继承关系性能

编译器对下列这种循环可能优化成这样(注释代码):

深度探索C++对象模型(21)——函数语义学(5)——函数调用、继承关系性能

这样就导致运行时间大大降低

在循环中将mytest(6)中的参数变为可变参数i,如下:

深度探索C++对象模型(21)——函数语义学(5)——函数调用、继承关系性能

release版运行结果:

深度探索C++对象模型(21)——函数语义学(5)——函数调用、继承关系性能

运行时间大幅提升,因为这种情况下编译器无法做到像上面的固定参数那样非常强的优化

在for循环中加入判断语句,如下:

深度探索C++对象模型(21)——函数语义学(5)——函数调用、继承关系性能

release版运行结果:

深度探索C++对象模型(21)——函数语义学(5)——函数调用、继承关系性能

由于加入了判断语句,使编译器的无法进行一些优化,使性能下降

结论:

(1)编译器能把循环优化成1条语句;

(2)在编译期间,编译器也具有运算能力,有些运算编译器在编译期间就能搞定;

2.继承关系深度增加,或多重继承,导致开销增加

代码1:

#include <iostream>

using namespace std;

namespace _nmsp2
{
	class A
	{
	public:
		A()
		{
			cout << "A::A()" << endl;
		}
	};

	class B :public A
	{
	public:
	};
	class C :public B
	{
	public:
		C()
		{
			cout << "C::C()" << endl;
		}

	};
	void func()
	{
		C cobj;
	}
}


int main()
{
	_nmsp2::func();
}

反汇编:

深度探索C++对象模型(21)——函数语义学(5)——函数调用、继承关系性能

深度探索C++对象模型(21)——函数语义学(5)——函数调用、继承关系性能

深度探索C++对象模型(21)——函数语义学(5)——函数调用、继承关系性能

可以发现编译器在C的函数体内代码执行前插入了调用B的构造函数的代码(编译器帮我们生成,为了又在B中调用A的构造函数)

当A中没有定义构造函数时,反汇编:

深度探索C++对象模型(21)——函数语义学(5)——函数调用、继承关系性能

可以发现C中就不会插入调用父类构造函数的代码

但一般类都会定义构造函数,所以当调用子类构造函数时,一般会层层调用父类构造函数,导致开销增加

代码2:

#include <iostream>

using namespace std;

namespace _nmsp2
{
	class A
	{
	public:
		A()
		{
			cout << "A::A()" << endl;
		}
	};
	class A1
	{
	public:
		A1()
		{
			cout << "A1::A1()" << endl;
		}
	};

	class B :public A, public A1
	{
	public:
	};
	class C :public B
	{
	public:
		C()
		{
			cout << "C::C()" << endl;
		}

	};
	void func()
	{
		C cobj;
	}
}


int main()
{
	_nmsp2::func();	
}

反汇编:

深度探索C++对象模型(21)——函数语义学(5)——函数调用、继承关系性能

多继承下,合成的B的构造函数会添加调用多个父类的构造函数的代码,导致开销增加

3.继承关系深度增加,虚函数导致的开销增加

代码:

#include <iostream>

using namespace std;
namespace _nmsp3
{
	class A
	{
	public:
		//A()
		//{
		//	cout << "A::A()" << endl;
		//}
		virtual void myvirfunc() {}
	};	

	class B :public A
	{
	public:
	};

	class C :public B
	{
	public:
		C()
		{
			cout << "C::C()" << endl;
		}
	};

	void func()
	{
		C *pc = new C();
	}
}

int main()
{
	_nmsp3::func();
}

反汇编:

当有虚函数时,C的构造函数中被插入给虚函数表指针赋值的语句,同时它的每一层父类的构造函数中也有给虚函数表指针赋值的语句,会增加开销

C的构造函数:

深度探索C++对象模型(21)——函数语义学(5)——函数调用、继承关系性能

B的构造函数:

深度探索C++对象模型(21)——函数语义学(5)——函数调用、继承关系性能

A的构造函数:

深度探索C++对象模型(21)——函数语义学(5)——函数调用、继承关系性能

同时可以从上述反汇编发现:当父类中有虚函数时,即便父类没有构造函数,编译器会帮我们自动合成构造函数,在其中插入给虚函数表指针赋值的语句。