STDIO
Tìm kiếm gần đây

    Nội dung

    Tư Duy Tối Ưu Hóa Trong Lập Trình Games - Phần 1: Codes Trong C/C++

    18/09/2014
    05/04/2017
    Tư Duy Tối Ưu Hóa Trong Lập Trình Games - Phần 1: Codes Trong C/C++
    Bài viết hướng tối ưu hóa trong lập trình với C++, tối ưu hóa lập trình C++ với games, bài viết hướng games bởi vì games đòi hỏi hiệu năng rất cao, và các games lớn thông thường sử dụng C++ làm nền tảng.

    Giới thiệu

    Không chỉ games đòi hỏi hiệu năng cao mà các loại ứng dụng khác cũng vậy, các nhà phát triển rất cần sự đóng góp thêm ý tưởng tối ưu là rất cần thiết.

    Đối tượng hướng đến

    Các bạn có hiểu biết tốt về kỹ thuật lập trình và khả năng C/C++ tốt.

    Bài liên quan

    1. Tư Duy Tối Ưu Hóa Trong Lập Trình Games - Phần 1: Codes Trong C/C++
    2. Tư Duy Tối Ưu Hóa Trong Lập Trình Games - Phần 2: Quản Lý Bộ Nhớ Phân Mảnh
    3. STDIO Coding Convention - Level 1

    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, ta 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ữ.

    Ta xét trường hợp sau

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

    Từ đoạn ... trở xuống, ta không hề sử dụng sObj nên đó là 1 sự lãng phí ta phải bỏ. Tuy nhìn đơn giản nhưng rất nhiều bạn vẫn không xóa nó đi. Và chính vì thế cho đến lúc code phức tạp hơn 1 chút như bên dưới

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

    Nếu enable == true thì hàm sẽ ngưng thực thi và trả NULL, do đó sObj sẽ không để làm gì cả. Một sự bất cẩn thường xuyên sẽ dẫn đến các bất cẩn đáng tiếc khi mọi thứ phức tạp hơn. Vậy ta cần tạo thói quen tốt, đó là tính tiết kiệm.

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

    Mặc định ta khởi tạo 1 đối tượng, hàm tạo (constructor) sẽ đượ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 StdioMember
    {
    private:
    	byte* m_profilePicture;
    	int m_profilePictureLength;
    	
    	char* m_DNAString;
    	int m_DNAStringLength;
    
    public:
    	StdioMember(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;
    }

    Ngay từ lúc khởi tạo StdioMember member thì constructor sẽ được gọi ngay sau đó và điều thuận tiện này cũng là điều bất lợi, vì đôi lúc đó chưa phải là thời điểm mà ta 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 đó ta 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 đó (khi nào mà ta muốn), vậy ta không nên đặt các khởi tạo tại constructor.

    Nếu chưa cần khởi tạo, ta có 2 cách và thông thường ta sử dụng con trỏ và khi nào cần khởi tạo ta sẽ cấp phát động bằng toán tử new. Nhưng giả sử 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ì ta sẽ 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à ta sẽ gọi phương thức đó khi nào ta thấy cần thiết.

    Vậy ta được code như sau

    #include <string.h>
    
    typedef char byte;
    
    class StdioMember
    {
    private:
    	byte* m_profilePicture;
    	int m_profilePictureLength;
    	
    	char* m_DNAString;
    	int m_DNAStringLength;
    
    public:
    
    	StdioMember()
    	{
    	}
    	
    	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()
    {
    	/*......*/
    
    	StdioMember 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 ta xem như đã gọi constructor mặc định cho nó, và không có cách thức để gọi các constructor mà ta mong muốn.

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

    Phân tích codes trên ta 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. Phân tích riêng 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 ta 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. Vậy ta nghĩ đến vấn đề, tại thời điểm khởi tạo m_name ta sẽ gọi thẳng copy-constructor để tránh thêm bước gọi constructor.

    Để làm điều này, ta sẽ 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)
    	{
    	}
    }

    Tôi có giải thích thêm về vấn đề này trong bài viết Sự Ra Đời Của Phương Thức Khởi Tạo (Constructor), Phương Thức Hủy (Destructor) Trong Một Class - C++.

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

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

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

    Thảo luận

    In order to comment you must be a STDIO Insider. Please sign up or log in to continue.

    Đăng nhập

    Bài viết liên quan

    Tư Duy Tối Ưu Hóa Trong Lập Trình Games - Phần 2: Quản Lý Bộ Nhớ Phân Mảnh

    Tư Duy Tối Ưu Hóa Trong Lập Trình Games - Phần 2: Quản Lý Bộ Nhớ Phân Mảnh

    Vấn đề phân mảnh trong quá trình cấp phát và thu hồi bộ nhớ liên tục dẫn đến thiếu bộ nhớ với các thiết bị có bộ nhớ thấp đã từng làm tôi tốn thời gian. Giải quyết vấn đề ...

    La Kiến Vinh

    19/09/2014

    Struct Trong Lập Trình C Và Tư Duy Trừu Tượng Hoá

    Struct Trong Lập Trình C Và Tư Duy Trừu Tượng Hoá

    Struct là một khái niệm ra đời từ rất sớm trong lịch sử phát triển ngôn ngữ lập trình C. Việc sử dụng struct giúp hệ thống hoá các đối tượng, tránh tình trạng rời rạc và ...

    Rye Nguyen

    11/08/2015

    Tối Ưu Mã C/C++ Cho Người Mới Bắt Đầu

    Tối Ưu Mã C/C++ Cho Người Mới Bắt Đầu

    Xuất phát từ vấn đề đánh giá tiêu chuẩn một sản phẩm do một người lập trình làm ra, điều quan trọng không kém đó chính là tốc độ xử lí và kích thước của một chương trình. ...

    Trần Minh Cường

    28/08/2015

    template Trong C++

    template Trong C++

    Template là từ khóa trong C++, chúng ta có thể hiểu rằng là nó một kiểu dữ liệu trừu tượng, đặc trưng cho các kiểu dữ liệu cơ bản. Khi học về lập trình hướng đối tượng ...

    Trung Nguyễn

    21/09/2014

    Học Lập Trình Nên Bắt Đầu Từ Đâu?

    Học Lập Trình Nên Bắt Đầu Từ Đâu?

    Học lập trình nên bắt đầu từ đâu? Lựa chọn học từ nền tảng có phải luôn là lựa chọn tối ưu?

    STDIO TrainingGóc nhìn

    30/06/2020

    Sơ Lược Về Phong Cách Lập Trình

    Sơ Lược Về Phong Cách Lập Trình

    Bài viết là một vài chia sẻ về cách hình thành phong cách lập trình để giúp hệ thống phần mềm dễ bảo trì và mở rộng.

    STDIO TrainingLập trình

    27/05/2014

    9 Tính Năng Quan Trọng Trong C++11

    9 Tính Năng Quan Trọng Trong C++11

    C++11 là một phiên bản cải tiến và nâng cấp từ C++98 (hay các bạn vẫn gọi là C++), với những tính năng mới tối ưu hơn, dễ sử dụng hơn, dễ quản lý bộ nhớ hơn, và khắc phục ...

    Lê Minh Tài

    13/08/2015

    Bản Chất Của Biến Trong C/C++

    Bản Chất Của Biến Trong C/C++

    Những ngày đầu được học và làm việc với các kiểu biến như int, float, char….Tôi luôn có những thắc mắc về:”Điều gì đang xảy ra bên trong biến int, char… khi ta cấp phát ...

    Trần Hữu Danh

    16/01/2015

    Lập Trình Game Với DirectX - Phần 2: Khởi Tạo Cửa Sổ

    Lập Trình Game Với DirectX - Phần 2: Khởi Tạo Cửa Sổ

    Ở bài viết cũ của mình, tôi có đề cập đến Game thực chất là 1 đối tượng trong chương trình. Đối tượng đó trải qua 3 giai đoạn chính gồm: Khởi Tạo (init), Chạy (run) và ...

    Trần Hữu Danh

    21/01/2015

    Tổng Quan Về Ngôn Ngữ Lập Trình Java

    Tổng Quan Về Ngôn Ngữ Lập Trình Java

    Java là một trong những ngôn ngữ lập trình mạnh và được sử dụng đông đảo trong phát triển phần mềm, các trang web, game hay ứng dụng trên các thiết bị di động, ngôn ngữ ...

    Lê Minh Trung

    20/05/2015

    STDIO
    Trang chính
    Công ty TNHH STDIO

    30, Trịnh Đình Thảo, Hòa Thạnh, Tân Phú, Hồ Chí Minh
    +84 28.36205514 - +84 942.111912
    developer@stdio.vn

    383/1 Quang Trung, Phường 10, Quận Gò Vấp, Hồ Chí Minh
    Số giấy phép ĐKKD: 0311563559 do sở Kế hoạch và Đầu Tư TPHCM cấp ngày 23/02/2012

    ©STDIO, 2013 - 2020