Search…

Tối Ưu Hóa - Tối Ưu Hóa Code C++

26/09/20204 min read
Các mẹo tối ưu hóa code làm tăng hiệu suất trong lập trình C++.

Các trường hợp tối ưu hóa codes

  • Tạo một đối tượng khi thật sự cần thiết.
  • Gọi hàm khởi tạo khi thật sự cần thiết.
  • Chỉ định hàm khởi tạo cho các đối tượng là thuộc tính của class.
  • Sử dụng tham chiếu, con trỏ (shallow copy) nếu có thể.

Tạo một đối tượng khi thật sự cần thiết

Khi thật sự không cần thiết nên hạn chế khởi tạo 1 đối tượng vì chi phí bao gồm hiệu suất cho khởi tạo và thu hồi rất lớn, bên cạnh đó còn tốn bộ nhớ để lưu trữ.

Xét trường hợp sau

void BeAStdio()
{
	Stdio sObj;
	
	...
}

Từ đoạn ... trở xuống, không hề sử dụng sObj nên đó là 1 sự lãng phí phải bỏ, tuy nhìn đơn giản nhưng nếu không xóa dòng code đó, khi đến trường hợp code phức tạp hơn như bên dưới:

char* BeAStdio(bool enable)
{
	Stdio sObj;
	
	if (enable == true)
		return nullptr;
	else
		...
}

Nếu enable == true thì hàm sẽ ngưng thực thi và trả nullptr, sObj sẽ không có chức năng gì trong hàm, trường hợp này rất lãng phí.

Gọi hàm khởi tạo khi thật sự cần thiết

Mặc định khởi tạo 1 đối tượng, hàm tạo (constructor) được gọi sau khi hàm đó được khởi tạo, giả sử trong hàm khởi tạo có dạng sau:

#include <string.h>

typedef char byte;

class Proifle
{
private:
	byte* m_profilePicture;
	int m_profilePictureLength;
	
	char* m_DNAString;
	int m_DNAStringLength;

public:
	Profile(int pictureLength, byte* profilePicture,
				int DNALength, char* DNA)
	{
		m_profilePicture = new byte[pictureLength];
		m_profilePictureLength = new char[DNALength];
		
		memcpy(m_profilePicture, profilePicture, pictureLength);
		memcpy(m_DNAString, DNA, DNALength);
	}
};

int main()
{
	/*......*/

	StdioMember member(picLength, pic, dnaLength, dna);

	/*......*/
	
	return 0;
}

Từ lúc khởi tạo StdioMember member thì constructor sẽ gọi ngay sau đó và điều thuận tiện này cũng là điều bất lợi, đôi lúc đó chưa phải là thời điểm muốn khởi tạo các giá trị cho thuộc tính trong đối tượng, có khi thời điểm đó cũng đang cần hiệu suất của máy tính và muốn việc khởi tạo được diễn ra sau đó, không nên đặt các khởi tạo tại constructor.

Nếu chưa cần khởi tạo, có thể sử dụng con trỏ và khi nào cần khởi tạo sẽ cấp phát động bằng toán tử new.

Nếu bài toán đặt ra là phải cấp phát đối tượng nhưng chưa cần khởi tạo các giá trị trong đối tượng thì cần thiết kế lại lớp như sau.

  • Thêm 1 constructor với thân của constructor là rỗng.
  • Viết thêm 1 phương thức khởi tạo và gọi phương thức đó khi nào thấy cần thiết.

Code như sau

#include <string.h>

typedef char byte;

class Profile
{
private:
	byte* m_profilePicture;
	int m_profilePictureLength;
	
	char* m_DNAString;
	int m_DNAStringLength;

public:
	Profile()
	{
	}
	
	void Init(int pictureLength, byte* profilePicture,
				int DNALength, char* DNA)
	{
		m_profilePicture = new byte[pictureLength];
		m_profilePictureLength = new char[DNALength];
		
		memcpy(m_profilePicture, profilePicture, pictureLength);
		memcpy(m_DNAString, DNA, DNALength);
	}
};

int main()
{
	/*......*/

	Profile member;

	/*......*/
	
	member.Init();
	
	return 0;
}

Chỉ định hàm khởi tạo cho các đối tượng là thuộc tính của class

Với 1 thuộc tính trong 1 class, có thể đó là 1 đối tượng, nếu như khai báo thông thường xem như đã gọi constructor mặc định cho nó và không có cách thức để gọi các constructor mong muốn.

class Sins
{
private:
	string m_name;
	
public:
	Sins(string & name)
	{		
		m_name = name;
	}
}

Phân tích code trên có, 2 quá trình:

  • Sins được tạo ra.
  • Sins gọi constructor.

Trong quá trình đối tượng Sins được tạo thành thì m_name được tạo, nó sẽ gọi constructor của nó.

Vào thân hàm constructor của Sins và xảy ra copy-constructor gán name vào m_name.

Xét riêng m_name thấy nó phải gọi constructor trước, sau đó lại tiến hành gán (gọi copy-construtor) thêm 1 lần nữa. Do đó, nên gọi thẳng copy-constructor để tránh thêm bước gọi constructor.

Để làm điều này, chỉ định copy-constructor cho Sins khi nó đang khởi tạo bằng cách sau

class Sins
{
private:
	string m_name;
	
public:
	Sins(string & name):m_name(name)
	{
	}
}

Sử dụng tham chiếu, con trỏ nếu có thể

Với C, chỉ có con trỏ, bản chất khi truyền con trỏ vào 1 hàm là gán địa chỉ của con trỏ, nếu con trỏ đang quản lý 1 đối tượng, không tốn kém việc truyền các đối tượng vào hàm. Đối với cách truyền tham chiếu (trong C++) cũng có ý tưởng tương tự như vậy.

Như hàm vẽ 1 đối tượng tên là Sins bên dưới, không cần thiết phải sao chép cả đối tượng vì trong trường hợp này chỉ muốn lấy dữ liệu từ nó để vẽ (có thể sử dụng phương pháp truyền địa chỉ qua pointer nếu muốn, nhưng ở đây dùng phương pháp tên chung - truyền tham chiếu).

void DrawSins(const Sins & s, int posX, int posY)
{
	Draw(s.texture, s.rect.x, s.rect.y, s.rect.width, s.height, posX, posY);
}

Bài chung series

IO Stream

IO Stream Co., Ltd

30 Trinh Dinh Thao, Hoa Thanh ward, Tan Phu district, Ho Chi Minh city, Vietnam
+84 28 22 00 11 12
developer@iostream.co

383/1 Quang Trung, ward 10, Go Vap district, Ho Chi Minh city
Business license number: 0311563559 issued by the Department of Planning and Investment of Ho Chi Minh City on February 23, 2012

©IO Stream, 2013 - 2024