Técnicas de programación para mejorar el rendimiento de C ++

Capítulo 2 Constructor y Destructor

heredar

Un objeto se crea cuando realmente se necesita.
La creación (o destrucción) de un objeto desencadena la creación (destrucción) recursiva de objetos padre y miembro. Tenga cuidado con el uso compuesto de objetos en la jerarquía compleja. Hacen que la creación y la destrucción sean más caras.
Inicialice las variables de miembro utilizando una construcción explícita.

class FTest
{
    
    
public:
   FTest(const std::wstring &str)
    :_str(str)//建议
   {
    
    
	//   _str = str;//不建议
   }
 private:
    std::wstring _str;
};

Capítulo 3 Funciones virtuales

La construcción de funciones virtuales

  • El constructor debe inicializar vptr (puntero de tabla de función virtual)
  • La función virtual se llama indirectamente a través del puntero, por lo que primero debe obtener el puntero a la tabla de funciones virtuales y luego obtener el desplazamiento de función correcto
  • La inserción se determina en tiempo de compilación y es imposible que el compilador establezca funciones virtuales que se resuelven en tiempo de ejecución para que estén integradas.

Plantillas y herencia

Las funciones virtuales que solo se pueden resolver durante el tiempo de ejecución no pueden usar inlining. Porque el enlace dinámico de las llamadas a funciones es el resultado de la herencia. Entonces, una forma de eliminar el enlace dinámico es usar un diseño basado en plantillas en lugar de herencia. La plantilla avanza el paso de análisis desde el tiempo de ejecución hasta el tiempo de compilación.

Caso de clase de cadena segura para subprocesos

Preparación

class Locker
{
    
    
public:
	Locker(){
    
    }
	virtual ~Locker(){
    
    }
	virtual void lock() = 0;
	virtual void unlock() = 0;
};
class CirticalSectionLocker : public Locker
{
    
    
public:
	CirticalSectionLocker(){
    
    }
	~CirticalSectionLocker(){
    
    }
	virtual void lock()override
	{
    
    
		//临界区方式加锁
	}
	virtual void unlock()override
	{
    
    
		//临界区方式解锁
	}
};
class MutexLocker : public Locker
{
    
    
public:
	MutexLocker(){
    
    }
	~MutexLocker(){
    
    }
	virtual void lock()override
	{
    
    
		//互斥锁方式加锁
	}
	virtual void unlock()override
	{
    
    
		//互斥锁方式解锁
	}
};
class SemaphoreLocker : public Locker
{
    
    
public:
	SemaphoreLocker(){
    
    }
	~SemaphoreLocker(){
    
    }
	virtual void lock()override
	{
    
    
		//信号量方式加锁
	}
	virtual void unlock()override
	{
    
    
		//信号量方式解锁
	}
};

Sobre la base de derivar de la clase de cadena, el
código de muestra está diseñado desde las siguientes tres perspectivas , solo para ayudar a la comprensión, y puede que no sea posible compilar y
codificar
para derivar CriticalSectionString, MutexString y SemahoreStrintg de la clase de cadena. Cada clase implementa su propio mecanismo de sincronización.

class MutexString : public std::string
{
    
    
public:
    int getLength()
	{
    
    
		int length = 0;
		_mutexLocker.lock();
		length = std::string::length();
		_mutexLocker.unlock();
		return length;
	}
private:
MutexLocker  _mutexLocker;
}

Este diseño tiene ventajas de rendimiento. Llame a los métodos correctos de bloqueo y desbloqueo a través de funciones virtuales. Pero la desventaja de este diseño es que necesita escribir su propia clase de cadena para cada mecanismo de sincronización. Conduce a una menor reutilización del código
Herencia
Deriva una clase ThreadSafeString separada. Contiene un puntero a un objeto LoCKer y se selecciona un mecanismo de sincronización específico durante la operación a través de un mecanismo polimórfico.

class ThreadSafeString : public string
{
    
    
public:
     ThreadSafeString(const char *str,Locker *locker)
	   :std::string(str),_locker(locker)
	 {
    
    
		 
	 }
	 int getLength()
	 {
    
    
		 _locker->lock();//未进行判空
		 int length = std::string::length();
		 _locker->unlock();
		 return length;
	 }
private:
  Locker* _locker;
}
{
    
    
   MutexLocker  locker = new MutexLocker ;
   ThreadSafeString  safeStr("ABC",locker);
}

Las llamadas de función virtual de bloqueo y desbloqueo solo se resuelven durante la ejecución, por lo que no se pueden insertar. Esto provoca una pérdida de rendimiento.
Plantilla
Clase de cadena basada en la plantilla, que se obtiene después de la parametrización del tipo de casillero

template<class LOCKER>
class ThreadSafeString : public string
{
    
    
public:
	ThreadSafeString(const char *str)
		:std::string(str)
	{
    
    

	}
	int getLength();
private:
	LOCKER  _locker;
};
template<class LOCKER>
inline int ThreadSafeString<LOCKER>::getLength()
{
    
    
	_locker.lock() :
		int length = std::string::length();
	_locker.unlock();
	return length;
}
{
    
    
 ThreadSafeString<MutexLocker> safeString = "Hello":
 int len = safeString.getLength();
}

Este diseño también evita llamadas de funciones virtuales para bloquear y desbloquear. ThreadSafeString declara seleccionar un tipo de sincronización específico al crear una instancia de la plantilla. Al igual que la codificación rígida, permite al compilador analizar estas dos llamadas a funciones virtuales y ponerlas en línea.
El cálculo de la plantilla se realiza por adelantado desde el período de ejecución hasta el período de compilación, y se utiliza la inserción en tiempo de compilación, lo que mejora el rendimiento.

Capítulo 4 Optimización del valor de retorno

Si el objeto debe devolverse por valor, los pasos para crear y destruir objetos locales se pueden omitir a través de RVO, mejorando así el rendimiento.

Capítulo 5 Objetos temporales

Pasar por valor

class Test 
{
    
    
public:
  Test(){
    
    }
  ~Test(){
    
    }
};
void functionPassValue(Test test)
{
    
    
	//....
}//此种方式 编译器将创建一个Test类型的临时对象。并且使用test作为输入参数来复制构造它(临时对象)。然后临时对象作为实参传递给functionPassValue 该新创建的临时对象将按引用方式传递给functionPassValue
void functionPassReferences(Test &test)
{
    
    
	//...
}
void functionPassPointer(Test *test)
{
    
    
}//按照指针以及引用方式不会产生临时对象。

El costo de crear y destruir objetos temporales es relativamente alto. Si es posible, los objetos deben pasarse por puntero o referencia para evitar generar objetos temporales.

Retorno por valor

Si escribe una función que devuelve un objeto por valor (a diferencia de una referencia o puntero), es probable que produzca un objeto temporal.
std :: wstring getWStringByReturnValue ()
{ std :: wstring str; // ... return str; } // El compilador genera un objeto temporal para almacenar el valor de retorno . El operador + en std :: string




std::string s1 = "Hello";
std::string s2 = "World";
std::string s3;
s3 = s1 + s2;//产生一个临时对象
std::string s3 = s1+s2;//不会产生临时对象

¿Por qué se genera el objeto temporal?
Porque no tenemos derecho a modificar el contenido antiguo de s3 y usar el nuevo contenido de s1 + s2 para sobrescribirlo. El operador de asignación
(=) es responsable de cambiar s3 del contenido antiguo al contenido nuevo. Sin embargo, el compilador no permite que se omita std :: string :: operator = (), por lo que se deben generar objetos temporales. Pero, ¿qué pasa si s3 es un objeto nuevo sin contenido antiguo? En este caso, no hay necesidad de preocuparse por el contenido antiguo. En este momento, el compilador puede usar s3 en lugar de objetos temporales para el almacenamiento. El resultado de s1 + s2 se copia directamente y se construye en el objeto s3. s3 reemplaza los objetos temporales que ya no son necesarios

Resumen de los puntos principales

Los objetos temporales reducirán el rendimiento a la mitad en forma de constructores y destructores.
Declarar el constructor como explícito puede organizar el compilador para utilizar la conversión de tipos entre bastidores. El
compilador a menudo crea objetos temporales para resolver el problema de falta de coincidencia de tipos. Esto puede evitarse mediante la sobrecarga de funciones.
Si es posible, la copia de objetos debe evitarse tanto como sea posible (los retornos de función se devuelven por valor y los argumentos de función se pasan por valor). Pasar y devolver objetos por referencia
Donde el operador puede ser "+, -, *" o "/". Utilice operator = operator para eliminar objetos temporales

Grupo de memoria de un solo subproceso

Clasificación desde la perspectiva del tamaño de la asignación


Administrador de memoria de tamaño fijo que asigna memoria de tamaño fijo Tamaño
variable Administrador de memoria que
asigna bloques de memoria de cualquier tamaño. Se desconoce el tamaño de la asignación solicitada.

Considere desde una perspectiva de concurrencia

Un solo hilo El
administrador de memoria está limitado a un solo hilo. La memoria es utilizada por un hilo y no excede el límite del hilo. Este tipo de administrador de memoria no tiene múltiples subprocesos que involucren acceso mutuo.
Múltiples
subprocesos El administrador de memoria es utilizado simultáneamente por varios subprocesos. Esta implementación debe incluir segmentos de código que se ejecuten mutuamente de forma exclusiva. En cualquier momento, solo un hilo puede ejecutar un segmento de código.

Supongo que te gusta

Origin blog.csdn.net/weixin_39308337/article/details/107800072
Recomendado
Clasificación