Tải bản đầy đủ (.doc) (60 trang)

MỘT SỐ VẤN ĐỀ VỀ TỐI ƯU HÓA XỬ LÝ CỦA CHƯƠNG TRÌNH

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (537.99 KB, 60 trang )

ĐẠI HỌC QUỐC GIA TP.HCM
TRƯỜNG ĐẠI HỌC CÔNG NGHỆ THÔNG TIN
BÁO CÁO MÔN HỌC
NGUYÊN LÝ VÀ PHƯƠNG PHÁP LẬP TRÌNH
MỘT SỐ VẤN ĐỀ VỀ
TỐI ƯU HÓA XỬ LÝ CỦA CHƯƠNG TRÌNH
Cán bộ giảng dạy: TS. Nguyễn Tuấn Đăng
Học viên: Nguyễn Bá Kỳ
Mã số: CH0901025
Tp.HCM, 10-2010
LỜI CA
́
M ƠN
Chúng em xin chân thành cám ơn TS. Nguyễn Tuấn Đăng đã tận tình
giảng dạy chúng em trong quá trình học tập để thực hiện bài tiểu luận này.
Chúng em xin chân thành cám ơn Phòng Đào tạo sau Đại học, trường
Đại Học Khoa Học Công nghệ Thông tin – Đại học Quốc Gia Tp.HCM đã
tạo điều kiện thuận lợi cho em trong học tập và công tác.
Chúng em xin chân thành cám ơn quý Thầy Cô trong Trường đã tận
tình giảng dạy, trang bị cho chúng em những kiến thức quý báu trong suốt
thời gian học.
Xin chân thành cám ơn người thân và bạn bè đã ủng hộ, giúp đỡ tôi
trong thời gian học tập và nghiên cứu.
Học viên thực hiện,
Nguyễn Bá Kỳ
Tháng 10/ 2010
i
Mục lục
Chương 1Một số vấn đề về
Tối ưu hóa xử lý chương trình 5
1.0Đặt vấn đề 5


1.1Hiện tượng nút cổ chai 7
1.2Kỹ thuật đo thời gian thực hiện và lưu lại quá trình hoạt động
(Profiling) 15
1 2.1Đo thời gian tự động 15
1 2.2Kỹ thuật profiling – lưu lại quá trình thực hiện. 17
1 2.3Tập trung quan tâm vào các đoạn mã trọng tâm của chương
trình. 19
1 2.4Biểu diễn trực quan số liệu thống kê hiệu năng thực hiện của
chương trình 21
1.3Các chiến lược tối ưu hóa thời gian thực hiện 23
1 3.1Sử dụng thuật toán hoặc cấu trúc dữ liệu tốt hơn 23
1 3.2Sử dụng các chức năng tối ưu hóa của trình biên dịch 24
1 3.3Tinh chỉnh mã nguồn 25
1 3.4Không tối ưu hóa những gì không gây vấn đề 26
1.4Tinh chỉnh mã nguồn 28
1 4.1Tập hợp những biểu thức chung. 28
1 4.2Thay thế các thao tác chi phí cao bằng các thao tác chi phí thấp
hơn. 29
1 4.3Trải rộng hay loại bỏ các vòng lặp. 30
1 4.4Lưu trữ lại các giá trị thường dùng 31
1 4.5Xây dựng chức năng cấp phát bộ nhớ đặc dụng 32
1 4.6Dùng bộ nhớ trung gian cho việc nhập và xuất dữ liệu. 32
1 4.7Xử lý riêng từng tình huống đặc biệt 33
1 4.8Tính trước kết quả 33
1 4.9Dùng các giá trị xấp xỉ 34
1 4.10Viết lại mã nguồn bằng ngôn ngữ cấp thấp 34
1.5Tối ưu hóa không gian lưu trữ 35
1 5.1Tiết kiệm không gian bằng cách dùng kiểu dữ kiệu có kích
thước nhỏ nhất 36
1 5.2Không cần lưu trữ những gì có thể tính lại dễ dàng 37

1.6Ước lượng 39
1.7Tổng kết 43
1.8Các tài liệu đọc thêm 44
Bài tập 1.1 – Chương trình Spam Test 45
1.Mục đích 45
2.Các chức năng của chương trình 46
3.Cấu trúc dữ liệu và giải thuật được sử dụng 46
4.Nhận xét 48
Bài tập 1.249
1.Yêu cầu 49
ii
2.Thực hiện 49
Bài tập 1.350
1.Yêu cầu 50
2.Giải thích 50
Bài tập 1.4 - Module Special Memset 51
1.Mục đích 51
2.Giải thích hoạt động của module Special Memset 51
Bài tập 1.5 - Module Special Allocator 53
1.Mục đích 53
2.Giải thích hoạt động của module Special Allocator 53
Bài tập 1.6 và 1.7 - Chương trình Cost Model 56
1.Mục đích 56
2.Các chức năng của chương trình 57
iii
Danh sách các hình
Hình 1.1 Đồ thị thể hiện sự biến thiên tốc độ xử lý của chương trình với
kích thước của bảng băm trong trường hợp kích thước là lũy thừa của 2
và trường hợp kích thước là số nguyênt tố 22
Hình 1.2 – Chương trình Spam Test 45

Hình 1.3 Chương trình Cost Model 56
Danh sách các bảng
Bảng 1.1 Bảng thống kê quá trình thực hiện
chương trình spamtest nguyên thủy 19
Bảng 1.2 Bảng thống kê quá trình thực hiện
chương trình spamtest cải tiến 20
Bảng 1.3 Kết quả đo tốc độ thực hiện của các lệnh cơ bản trong C/C++
trên hệ thống Intel Pentium IV 2.4 GHz, 512MB DDRAM 333 Mhz 58
iv
Một số vấn đề về tối ưu hóa xử lý chương trình
Chương 1 Một số vấn đề về
Tối ưu hóa xử lý chương trình
Bài viết này dựa trên nội dung của chương 7 “Hiệu năng xử lý của chương
trình” (Chapter 7 – Performance) trích trong sách “The Practice of
Programming” của tác giả Brian W. Kernighan va Rob Pike [1999]. Trong
chương này, các tác giả trình bày một số vấn đề về tối ưu hóa xử lý chương
trình, trong đó tập trung đi sâu phân tích các khía cạnh về tối ưu hóa thời gian
xử lý, đồng thời trình bày một số kỹ thuật về tối ưu hóa không gian lưu trữ.
1.0 Đặt vấn đề
Vấn đề tối ưu hóa xử lý của chương trình được đặt ra từ rất lâu rồi. Trước
đây, các nhà lập trình luôn cố gắng tối ưu hóa chương trình của mình do các thế
hệ máy tính bấy giờ có tốc độ xử lý còn chậm và giá thành cao. Ngày nay, máy
tính đã trở nên phổ biến và tốc độ xử lý ngày càng tăng nhanh, do đó nhu cầu
đặt ra đối với bài toán tối ưu hóa tuyệt đối chương trình đã giảm đi rất nhiều.
Vậy ta có còn cần quan tâm đến vấn đề hiệu năng xử lý của chương trình nữa
hay không?
Trên thực tế, bài toán tối ưu hóa hiệu năng xử lý của chương trình vẫn rất có
ý nghĩa trong cuộc sống. Tuy nhiên, vấn đề này chỉ thật sự cần được đặc biệt
quan tâm khi bài toán cần được giải quyết thật sự có ý nghĩa quan trọng, trong
khi chương trình hiện có để giải quyết bài toán này có tốc độ xử lý còn quá

chậm so với yêu cầu của thực tế, đồng thời ta có cơ sở để tối ưu hóa việc xử lý
nhằm tăng tốc độ của chương trình mà vẫn đảm bảo tính đúng đắn, bền vững và
trong sáng của chương trình. Một chương trình có tốc độ nhanh nhưng đưa ra
đáp số chưa chính xác không giúp tiết kiệm được thời gian!
Do đó, nguyên lý đầu tiên của tối ưu hóa là phải cân nhắc có cần tối ưu hóa
chương trình hay không. Phải chăng chương trình hiện có đã đủ tốt rồi, không
Nguyễn Bá Kỳ -CH0901025 5
Một số vấn đề về tối ưu hóa xử lý chương trình
cần hoặc không thể tối ưu hóa thêm được nữa? Giả sử ta đã biết được cách mà
một chương trình sẽ được sử dụng và môi trường thực hiện chương trình này,
liệu việc tối ưu hóa nhằm tăng tốc độ xử lý của chương trình có đem lại lợi ích
gì hay không. Hầu hết những chương trình mà sinh viên viết trong quá trình học
ở trường chỉ được sử dụng một vài lần, sau đó sẽ không sử dụng lại nữa, do đó,
vấn đề tốc độ thường không được quan tâm. Vấn đề này thường cũng không
ảnh hưởng và không cần quan tâm đối với hầu hềt các chương trình cá nhân,
chẳng hạn như các công cụ, khung kiểm tra, thử nghiệm Ngược lại, đối với
các chức năng xử lý chính của một sản phẩm thương mại, chẳng hạn như thư
viện đồ họa của phần mềm, vấn đề tối ưu hóa tốc độ xử lý có ý nghĩa hết sức
quan trọng và cần được giải quyết.
Như vậy, khi nào thì ta nên thử tìm cách tăng tốc xử lý của một chương trình
và làm cách nào để thực hiện được điều này? Kết quả cụ thể mà ta mong muốn
khi tiến hành việc tối ưu hóa là gì?
Nội dung của chương này sẽ trình bày các vấn đề về làm thế nào để chương
trình chạy nhanh hơn (tối ưu hóa thời gian) hay sử dụng bộ nhớ tiết kiệm hơn
(tối ưu hóa không gian lưu trữ). Tốc độ là vấn đề thường được quan tâm nhiều
hơn nên ta sẽ đi sâu vào vấn đề này. Không gian lưu trữ (bộ nhớ chính, đĩa)
thường ít được quan tâm hơn, nhưng cũng có ý nghĩa hết sức quan trọng, nên ta
cũng sẽ tìm hiểu một số vấn đề liên quan đến tối ưu hóa không gian lưu trữ.
Trong chương 2 - Thuật toán và Cấu trúc Dữ liệu, chiến lược tốt nhất là sử
dụng các thuật toán đơn giản nhất, rõ ràng nhất, và các cấu trúc dữ liệu phù hợp

với bài toán cần giải quyết. Do đo, cần đo các thông số hiệu năng hoạt động để
quyết định có cần tiến hành sửa đổi chương trình/thuật toán hay không; cần sử
dụng các option của trình biên dịch để phát sinh mã thực hiện có tốc độ xử lý
nhanh nhất; cần xác định nhũng phần nào trong chương trình khi được tối ưu
hóa sẽ đem lại hiệu quả cao nhất cho toàn bộ chương trình; thực hiện lần lượt
từng thay đổi cải tiến rồi sau đó đánh giá, phân tích trước khi tiến hành các thay
đổi tiếp theo; đồng thời cần phải giữ lại phiên bản đơn giản chính xác (chưa tối
Nguyễn Bá Kỳ -CH0901025 6
Một số vấn đề về tối ưu hóa xử lý chương trình
ưu hóa nhưng chắc chắn chính xác) để kiểm tra kết quả so với các phiên bản cải
tiến.
Việc đo lường và thống kê là thành phần quan trọng của quy trình cải tiến
hiệu năng của chương trình vì suy luận và trực giác của con người có thể dẫn
đến những định hướng sai lầm và cần phải được bổ sung bằng những công cụ
đo thời gian thực hiện và lập sơ đồ thời gian của chương trình. Dựa vào các số
liệu đo đạt và thống kê được thông qua các kỹ thuật như đo thời gian (timing
command) và lưu lại quá trình hoạt động (profiler), ta có thể rút ra được những
nhận xét và giải pháp nhằm tối ưu hóa chương trình. Việc cải tiến hiệu năng
phải đi kèm với quá trình kiểm chứng chương trình (sử dụng kỹ thuật kiểm tra
tự động, lưu vết các sửa đổi và phiên bản, thanh tra mã nguồn – code inspection,
sử dụng các bộ dữ liệu thử nghiệm ) để đảm bảo chương trình sau khi sửa đổi
vẫn đảm bảo tính đúng đắn và không làm mất đi các ưu điểm vốn có trong các
phiên bản trước đó.
Nếu từ ban đầu chương trình được viết tốt với thuật toán hiệu quả thì có thể
chỉ cần sửa đổi rất ít hoặc thậm chí không cần sửa đổi chương trình để cải tiến
hiệu năng. Ngược lại, đối với những chương trình viết chưa tốt, tổ chức chưa
hợp lý, không trong sáng, việc tối ưu hóa đòi hỏi phải sửa đổi rất nhiều, thậm
chí phải tổ chức lại cấu trúc chương trình và viết lại từ đầu.
1.1 Hiện tượng nút cổ chai
Đầu tiên, ta mô tả cách giải quyết hiện tượng nút cổ chai trong một chương

trình quan trọng được sử dụng thường xuyên trong môi trường cục bộ.
Các thư điện tử mà ta nhận được thông qua thiết bị gateway nối kết giữa
mạng cục bộ với Internet. Mỗi ngày, có hàng vạn thư điện tử từ bên ngoài được
gởi đến cho vài nghìn thành viên trong mạng cục bộ thông qua gateway và sau
đó được chuyển đến từng người trong mạng nội bộ. Sự ngăn cách này cô lập
mạng nội bộ của ta với Internet và cho phép ta chỉ cần công bố một tên máy (là
tên của gateway) cho tất cả mọi thành viên trong mạng nội bộ.
Nguyễn Bá Kỳ -CH0901025 7
Một số vấn đề về tối ưu hóa xử lý chương trình
Một trong số các dịch vụ của gateway là lọc các “spam”, là các thư rác kèm
theo các quảng cáo về những dịch vụ mà lợi ích chưa được kiểm chứng. Sau
thời gian thử nghiệm ban đầu thành công, bộ lọc các “spam” được chính thức
cài đặt và sử dụng như một đặc tính không thể thiếu cho tất cả người dùng trong
mạng nội bộ, và rồi rắc rối lập tức xuất hiện. Gateway, vốn cũ kỹ và thường rất
bận, bị quá tải vì chương trình lọc chiếm quá nhiều thời gian – nhiều hơn hẳn so
với thời gian cần thiết cho tất cả các thao tác xử lý khác đối với bức thư – đến
mức độ hàng đợi thư bị đầy và việc truyền phát thư tín bị trì hoãn nhiều giờ
trong khi hệ thống cố xoay sở để kịp phân phối thư.
Trên đây là một ví dụ về vấn đề tốc độ thực hiện chương trình: chương trình
không đủ nhanh để hoàn thành nhiệm vụ, và sự chậm trễ này tạo ra trở ngại cho
người dùng. Do đó, chương trình cần phải được cải tiến, tối ưu hóa để có thể
chạy nhanh hơn nhiều so với phiên bản hiện tại.
Trước hết, ta hãy khảo sát cách hoạt động của bộ lọc “spam”. Mỗi thông
điệp gửi tới được xem như một chuỗi ký tự đơn, và một bộ so mẫu ký tự kiểm
tra chuỗi đó xem có chứa bất kỳ một cụm từ nào trong số các cụm từ thường
gặp trong các “spam” hay không, chẳng hạn như: “Make millions in your spare
time” (kiếm hàng triệu đồng trong thời gian rảnh của bạn) hay “XXX-rated”
(các trang khiêu dâm). Các thông điệp có khuynh hướng lặp đi lặp lại, do đó, kỹ
thuật này có hiệu quả đáng kể, và nếu như một thông điệp “spam” lọt lưới thì
một cụm từ đặc trưng cho “spam” đó sẽ được thêm vào danh sách các “spam”

để chặn nó vào lần sau.
Do không có sẵn một công cụ so sánh chuỗi vừa chạy nhanh vừa đáng tin
cậy, ví dụ như grep, nên người ta viết ra một bộ lọc riêng dùng cho mục đích
lọc các “spam”. Mã nguồn rất đơn giản; chương trình lọc “spam” sẽ tìm xem
mỗi bức thông điệp có chứa một trong số các cụm từ (các mẫu) nào đó hay
không:
/* Hàm isspam:
kiểm tra chuỗi thông điệp mesg có chứa spam nào không */
Nguyễn Bá Kỳ -CH0901025 8
Một số vấn đề về tối ưu hóa xử lý chương trình
int isspam(char *mesg)
{
int i;
for (i=0; i<npat; i++)
if (strstr(mesg, pat[i]) != NULL)
{
printf("spam: match for '%s'\n", pat[i]);
}
}
Làm thế nào ta có thể thực hiện đoạn chương trình trên nhanh hơn? Ta cần
phải tìm kiếm chuỗi ký tự mẫu trong bức thông điệp, do đó, việc sử dụng hàm
strstr của thư viện C là cách tốt nhất để tìm kiếm vì đây là hàm thư viện
chuẩn được xây dựng hiệu quả.
Sử dụng kỹ thuật lưu lại quá trình hoạt động (kỹ thuật profiling - sẽ được
trình bày chi tiết trong phần 1 2.2-Kỹ thuật profiling – lưu lại quá trình thực
hiện.), ta nhận thấy rằng hàm strstr có một số vấn đề chưa phù hợp khi sử
dụng cho một bộ lọc spam. Trong trường hợp cụ thể này, việc thay đổi cách
thức làm việc của hàm strstr có thể tăng hiệu quả hoạt động của hệ thống.
Hàm strstr được cài đặt lại như sau:
/* Hàm strstr :

dùng hàm strchr để tìm kiếm kí tự đầu tiên */
char* strstr(const char *s1, const char *s2)
{
int n;
n = strlen(s2);
for (;;)
{
s1 = strchr(s1, s2[0]);
if (s1 == NULL)
return NULL;
if (strncmp(s1, s2, n) == 0)
return (char*) s1;
s1++;
}
}
Đoạn chương trình trên được viết lại hướng tới mục tiêu hiệu quả tốc độ.
Trên thực tế, đoạn lệnh này chạy nhanh nếu dùng cho một mục đích tiêu biểu
Nguyễn Bá Kỳ -CH0901025 9
Một số vấn đề về tối ưu hóa xử lý chương trình
nào đó, vì chỉ dùng các hàm được tối ưu hóa cao để thực hiện công việc. Đoạn
chương trình này gọi hàm strchr để tìm vị trí tiếp theo của ký tự đầu tiên
trong chuỗi, sau đó gọi hàm strncmp để kiểm tra phần còn lại của chuỗi có so
khớp với phần còn lại của chuỗi mẫu. Như vậy, đoạn chương trình này sẽ bỏ
qua một phần lớn của bức thông điệp để tìm ký tự đầu tiên của chuỗi mẫu, rồi
quét nhanh để kiểm tra phần còn lại. Thế nhưng, tại sao tốc độ thực hiện chương
trình khi sử dụng hàm strstr được cải tiến vẫn còn quá chậm?
Hiện tượng trên có thể được lý giải bằng các lý do chính sau đây. Trước
tiên, hàm strncmp lấy chiều dài chuỗi mẫu bằng strlen làm tham số đầu
vào. Thế nhưng vì chuỗi mẫu có chiều dài cố định nên việc tính lại độ dài chuỗi
mẫu khi xử lý mỗi thông điệp là không cần thiết .

Thứ hai, hàm strncmp có chứa một vòng lặp phức tạp. Thực tế, không
những hệ thống phải so sánh từng byte của hai chuỗi mà phải tìm byte cuối
‘\0’ trên cả hai chuỗi trong khi vẫn đếm lùi tham số chiều dài. Vì chiều dài của
tất cả các chuỗi đều đã biết trước (dù không cần dùng tới hàm strncmp) nên sự
phức tạp này là không cần thiết. Ta thấy rằng phép đếm là cần thiết, nhưng việc
kiểm tra byte ‘\0’ sẽ lãng phí nhiều thời gian.
Thứ ba, hàm strnchr cũng phức tạp, bởi vì đồng thời vừa tìm ký tự và
kiểm tra byte ‘\0’ kết thúc bức thông điệp. Trong một lần gọi hàm isspam,
bức thông điệp là cố định, vì vậy thời gian dùng để kiểm tra byte ‘\0’ là lãng
phí vì ta đã biết chính xác bức thông điệp kết thúc ở đâu.
Cuối cùng, dù cho hàm strncmp, strchr, và strlen đều rất hiệu quả khi
làm việc độc lập, tổng thời gian cần để gọi các hàm này cũng xấp xỉ với thời
gian tính toán của chúng. Sẽ hiệu quả hơn nếu làm mọi việc bằng chỉ một hàm
strstr được viết lại thật cẩn thận và tránh gọi tất cả những hàm khác.
Những vấn đề thuộc dạng này chính là một nguồn gốc chung làm chậm tốc
độ thực hiện chương trình – một thủ tục hoặc một phương thức giao tiếp làm
việc tốt trong một tình huống điển hình nhưng lại thực hiện kém hiệu quả trong
Nguyễn Bá Kỳ -CH0901025 10
Một số vấn đề về tối ưu hóa xử lý chương trình
tình huống bất thường xảy ra trở thành vấn đề trung tâm của chương trình. Hàm
strstr có sẵn thực hiện tốt khi chuỗi mẫu và chuỗi cần tìm ngắn và thay đổi ở
mỗi lần hàm được gọi. Nhưng khi chuỗi cần tìm dài và cố định thì thời gian thời
gian xử lý lại bị lãng phí quá nhiều.
Từ những nhận xét trên đây, ta quyết định viết lại hàm strstr để xử lý trên
chuỗi mẫu cùng với chuỗi thông điệp nhằm tìm ra chỗ so khớp mà không cần
gọi các thủ tục con. Khả năng thực hiện khi thực hiện giải pháp này hoàn toàn
có thể dự đoán được: chương trình có thể hơi chậm trong một số trường hợp cá
biệt, nhưng lại rất nhanh trong việc lọc các “spam” và quan trọng nhất là không
bao giờ chạy quá lâu. Để kiểm tra lại tính đúng đắn của bản cài đặt mới cũng
như tốc độ thực hiện của chương trình mới, ta xây dựng một bộ kiểm chứng tốc

độ thực hiện. Bộ thử này không chỉ gồm các ví dụ đơn giản như tìm một từ
trong câu, mà còn gồm cả một số trường hợp đặc biệt, ví dụ như tìm chuỗi mẫu
chỉ có một ký tự ‘x’ trong chuỗi có một ngàn ký tự ‘e’, và chuỗi mẫu có một
ngàn ký tự ‘x’ trong một chuỗi chỉ có một ký tự ‘e’. Những trường hợp đặc biệt
như vậy là phần chính trong việc đánh giá khả năng thực hiện.
Thư viện được cập nhật bằng hàm strstr mới và bộ lọc “spam” chạy
nhanh hơn 30%, đây là một kết quả cải tiến đáng kể cho việc viết lại.
Tuy nhiên, tốc độ thực hiện vẫn còn quá chậm. Khi giải quyết các khó khăn,
điều quan trọng là xác định đúng vấn đề cần giải quyết. Cho đến nay, ta đang
đặt vấn đề tìm cách nhanh nhất để truy soát một chuỗi ký tự mẫu trong một
chuỗi ký tự. Tuy nhiên, thật ra thì vấn đề là tìm kiếm một tập hợp lớn, cố định
các chuỗi ký tự mẫu trong một chuỗi ký tự dài thay đổi. Trong trường hợp đó,
hàm strstr chưa thật sự là giải pháp đúng.
Cách hiệu quả nhất để làm cho một chương trình chạy nhanh hơn là dùng
một giải thuật tốt hơn. Bây giờ, khi đã nhìn rõ hơn vấn đề cần giải quyết, ta cần
suy nghĩ xem thuật toán nào sẽ làm việc tốt nhất.
Xét vòng lặp cơ bản:
Nguyễn Bá Kỳ -CH0901025 11
Một số vấn đề về tối ưu hóa xử lý chương trình
for (i=0; i < npat; i++)
if (strstr(meg, pat[i]) != NULL)
return 1;
Vòng lặp này duyệt qua bức thông điệp npat lần độc lập với nhau; giả sử đoạn
lệnh không tìm ra bất kỳ sự so khớp nào, đoạn lệnh này đã phân tích từng byte
của bức thông điệp npat lần trong strlen(mesg)*npat phép so sánh.
Một cách tiếp cận tốt hơn là đảo ngược vòng lặp, quét qua bức thông điệp
một lần ở vòng lặp ngoài trong khi tìm tất cả các chuỗi mẫu song song ở vòng
lặp trong:
for (j=0; mesg[j] != '\0'; j++)
if (có chuỗi mẫu nào có phần đầu trùng với


ký tự đầu của mesg[j])
return 1;
Sự cải thiện tốc độ bắt nguồn từ cách nhìn hết sức đơn giản. Để nhận ra liệu
có chuỗi mẫu nào so khớp với bức thông điệp ở vị trí j không, ta không cần
phải xét tất cả các chuỗi mẫu mà chỉ cần xét các chuỗi có cùng ký tự đầu tiên
với mesg[j]. Nói cách khác, với 52 ký tự chữ hoa và chữ thường, ta chỉ cần
thực hiện strlen(mesg)*npat/52 phép so sánh. Vì các chữ cái không phân
bố đều, có nhiều từ bắt đầu bằng ký tự ‘s’ hơn là bằng ký tự ‘x’, ta không thể
kết luận rằng giải pháp này cải thiện tốc độ nhanh hơn gấp 52 lần phiên bản
trước, tuy nhiên, rõ ràng việc tối ưu hóa này đem lại kết quả rất đáng kể. Để
thực hiện, ta xây dựng một bảng băm dùng ký tự đầu tiên của chuỗi mẫu làm
khóa.
Dựa vào một vài tính toán trước để xây dựng một bảng những chuỗi mẫu bắt
đầu với từng ký tự, hàm isspam vẫn được viết ngắn gọn như sau:
int patlen[NPAT];
/* mảng chiều dài của các chuỗi mẫu */
int starting[UCHAR_MAX+1][NSTART];
/* danh sách các chuỗi mẫu bắt bằng ký tự tương ứng */
int nstarting[UCHAR_MAX+1];
/* số chuỗi mẫu bắt đầu bằng ký tự tương ứng */

Nguyễn Bá Kỳ -CH0901025 12
Một số vấn đề về tối ưu hóa xử lý chương trình
/* Hàm isspam: kiểm tra chuỗi thông điệp mesg có chứa
spam nào không */
int isspam(char *mesg)
{
int i, j, k;
unsigned char c;

for (j=0; (c = mesg[j]) != '\0'; j++)
{
for (i=0; i<nstarting[c]; i++)
{
k = starting[c][i];
if (memcmp(mesg+j, pat[k], patlen[k]) == 0)
{
printf("spam: match for ‘%s’\n", pat[k]);
return 1;
}
}
}
return 0;
}
Với mỗi ký tự ‘c’, mảng hai chiều starting[c][] chứa danh sách các
chuỗi mẫu bắt đầu bằng ký tự ‘c’. Mảng nstarting[c] ghi nhận bao nhiêu
chuỗi mẫu bắt đầu bằng ký tự ‘c’. Nếu không có các bảng đó, vòng lặp trong sẽ
chạy từ 0 đến npat, khoảng 1000, thay vì chỉ chạy từ 0 đến khoảng 20. Còn
phần tử mảng patlen[k] chứa kết quả tính toán trước của
strlen(pat[k]).
Hình dưới đây phác họa các cấu trúc dữ liệu trên dùng một tập hợp ba chuỗi
mẫu bắt đầu bằng ký tự ‘b’:
Nguyễn Bá Kỳ -CH0901025 13
Một số vấn đề về tối ưu hóa xử lý chương trình
3
.
.
.
.
.

.
1 7 3 5 9 7
4
9
1 4
b u y !
b i g b u c k s
b e s t p i c t u r e s !
[ ' b ' ]
[ 1 7 ]
[ 3 5 ]
[ 9 7 ]
n s t a r t i n g :
s t a r t i n g :
p a t l e n :
p a t :
Hình 1. SEQ Hình \* ARABIC \s 1 1 Minh họa các cấu trúc dữ liệu được
sử dụng
để lưu trữ thông tin của các chuỗi ký tự mẫu
Dưới đây là đoạn chương trình để xây dựng các bảng chứa thông tin các
chuỗi mẫu:
int i;
unsigned char c;
for (i=0; i<npat; i++)
{
c = pat[i][0];
if (nstarting[c] >= NSTART)
eprintf(" too many patterns (>=%d) begin '%c'",
NSTART, c);
starting[c][nstarting[c]++] = i;

patlen[i] = strlen(pat[i]);
}
Phụ thuộc vào dữ liệu nhập, bộ lọc “spam” bây giờ cải tiến chạy nhanh hơn
từ 5 đến 10 lần khi dùng hàm strstr, và nhanh hơn từ 7 đến 15 lần khi dùng
phương pháp đầu tiên. Tốc độ thực hiện không thể đạt được mức nhanh hơn 52
lần so với phiên bản đầu tiên, một phần do sự phân bố không đều của bảng chữ
Nguyễn Bá Kỳ -CH0901025 14
Một số vấn đề về tối ưu hóa xử lý chương trình
cái, một phần do vòng lặp trong chương trình mới phức tạp hơn, một phần khác
do vẫn còn phải thực hiện quá nhiều phép so chuỗi không đạt, nhưng bộ lọc
“spam” không gây ra hiện tượng nút cổ chai gây tắt nghẽn việc phân phối thư
nữa. Vấn đề tốc độ thực hiện chương trình đã được giải quyết.
Phần còn lại của chương này sẽ đi sâu vào các kỹ thuật được dùng để phát
hiện các vấn đề về tốc độ thực hiện chương trình, xác định các đoạn mã chậm
và tăng tốc cho chúng. Trước khi tiếp tục, ta cần xem lại ví dụ về bộ lọc
“spam” nhằm rút ra những bài học cần thiết. Nếu bộ lọc “spam” không phải là
một nút cổ chai thì tất cả các cố gắng mà ta vừa thực hiện đều trở nên không
còn ý nghĩa. Một khi đã xác định được vấn đề, ta sử dụng kỹ thuật profiling và
những kỹ thuật khác để khảo sát và phân tích việc thực hiện của chương trình
nhằm tìm ra mấu chốt vấn đề thật sự ở đâu. Sau đó, ta phải chắc chắn rằng ta đã
giải quyết đúng vấn đề, thử nghiệm toàn bộ chương trình chứ không chỉ tập
trung vào hàm strstr, là đầu mối nghi ngờ ban đầu, thoạt đầu tưởng là đã rõ
nhưng thực ra lại là không đúng. Cuối cùng, ta giải quyết đúng vấn đề bằng một
giải thuật tốt hơn, và kiểm nghiệm lại xem chương trình đã chạy nhanh hơn
chưa. Một khi chương trình đã chạy đủ nhanh, ta sẽ dừng lại. Việc suy nghĩ tìm
cách cải tiến tiếp tục là không cần thiết?
Bài tập 1.1. Hãy cài đặt một phiên bản isspam dùng hai ký tự làm chỉ mục.
Hãy cho biết mức độ cải tiến của hệ thống trong trường hợp này? Đây là những
trường hợp đơn giản của kiểu cấu trúc dữ liệu được gọi là trie. Đặc tính chung
của đa số những cấu trúc dữ liệu này là chấp nhận tốn không gian lưu trữ để

thời gian thực hiện chương trình nhanh hơn.
1.2 Kỹ thuật đo thời gian thực hiện và lưu lại quá trình hoạt
động (Profiling)
1 2.1 Đo thời gian tự động
Đa số hệ thống đều có lệnh để đo thời gian chạy một chương trình. Trên
Unix, lệnh này là time:
Nguyễn Bá Kỳ -CH0901025 15
Một số vấn đề về tối ưu hóa xử lý chương trình
% time slowwprogram
real 7.0
user 6.2
sys 0.1
%
Đoạn chương trình trên thực hiện lệnh (slowprogram) và trả lại 3 giá trị
(tính bằng giây):
- Thời gian “thực” (real time) là tổng thời gian từ khi bắt đầu đến khi kết
thúc chương trình;
- Thời gian sử dụng CPU ở chế độ người dùng (user mode) là thời gian
dùng để thực hiện chương trình;
- Thời gian CPU ở chế độ hệ thống (system mode) là thời gian hệ điều
hành xử lý theo yêu cầu của chương trình.
Nếu hệ thống bạn đang dùng có lệnh tương tự, hãy dùng lệnh đó. Giá trị đo
được chứa đựng nhiều thông tin hơn, tin cậy hơn, và dễ theo dõi hơn là thời
gian đo bằng một đồng hồ bấm giờ. Cần lưu ý là bạn hãy ghi chú thật cẩn thận.
Trong khi bạn làm việc trên chương trình, sửa đổi, đo đạc, bạn sẽ thu được rất
nhiều thông tin đến mức chỉ cần sau một vài ngày bạn sẽ bị nhầm lẫn các số liệu
có được (chẳng hạn như phiên bản nào của chương trình chạy nhanh hơn
20%?). Nhiều kỹ thuật đã nghiên cứu trong Chương 6 có thể được đưa vào để
đo đạc và cải tiến tốc độ thực hiện của chương trình. Bạn nên cho chương trình
thực thi thật sự trên máy tính và đo đạt các số liệu với các bộ dữ liệu kiểm

chứng, và điều quan trọng nhất là dùng kỹ thuật kiểm tra hồi quy để bảo đảm
các sửa đổi không làm mất đi tính đúng đắn của chương trình.
Nếu hệ thống của bạn không có lệnh time, hay nếu bạn đang đo thời gian
của một hàm cụ thể nào đó, bạn có thể dễ dàng xây dựng một cấu trúc đo thời
gian tương tự. Trong C và C++ đều có cung cấp một hàm chuẩn, hàm clock,
dùng để tính thời gian CPU mà chương trình đã dùng cho đến thời điểm hiện
tại. Hàm này có thể được gọi trước và sau một hàm để đo thời gian sử dụng
CPU:
Nguyễn Bá Kỳ -CH0901025 16
Một số vấn đề về tối ưu hóa xử lý chương trình
#include <time.h>
#include <stdio.h>

clock_t before;
double elapsed;
before = clock();
long_running_function();
elapsed = clock() - before;
printf("function used %.3f seconds\n",
elapsed/CLOCKS_PER_SEC);
Hằng số CLOCKS_PER_SEC xác định số lần dao động của bộ đếm (timer)
hệ thống trong 1 giây. Trong trường hợp hàm thực hiện trong một phần rất nhỏ
của một giây, ta có thể cho thực hiện nhiều lần hàm này bằng một vòng lặp, tuy
nhiên, khi đó, ta cần lưu ý đến chi phí của bản thân vòng lặp nếu thời gian này
đáng kể so với tổng thời gian thực hiện của hàm cần đo:
before = clock();
for (i=0; i<1000; i++)
short_running_function();
elapsed = (clock() - before)/(double)i;
Trong Java, ta có thể sử dụng các hàm trong lớp Date để xấp xỉ thời gian

CPU:
Date before = new Date();
long_running_function();
Date after = new Date();
long elapsed = after.getTime() - before.getTime();
Hàm getTime trả về giá trị tính bằng miligiây.
1 2.2 Kỹ thuật profiling – lưu lại quá trình thực hiện.
Bên cạnh phương pháp đo thời gian đã trình bày ở trên, ta còn có một công
cụ quan trọng hỗ trợ phân tích việc thực thi chương trình: đó chính là kỹ thuật
profiling - một hệ thống giúp lưu lại thông tin quá trình thực hiện. Kỹ thuật này
giúp ghi nhận lại việc gọi thực hiện và thời gian thực hiện từng thao tác trong
chương trình. Một số hệ thống ghi nhận chi tiết từng hàm, số lần mà hàm này
Nguyễn Bá Kỳ -CH0901025 17
Một số vấn đề về tối ưu hóa xử lý chương trình
được gọi thực hiện và tỷ lệ thời gian đã sử dụng để thực hiện từng hàm so với
toàn bộ thời gian thực hiện đoạn chương trình. Một số hệ thống ghi nhận lại số
lần từng phát biểu trong chương trình được thực hiện. Những phát biểu được
thực hiện nhiều lần sẽ chiếm phần lớn thời gian thi hành, trong khi đó, những
phát biểu không bao giờ được thực hiện chính là những đoạn mã thừa hoặc chưa
được kiểm tra đầy đủ.
Kỹ thuật ghi nhận quá trình thực hiện là công cụ hiệu quả để tìm các đoạn
mã chính trọng tâm của chương trình, tức là những hàm hoặc những đoạn mã
chiếm phần lớn thời gian tính toán. Tuy nhiên, việc sử dụng và phân tích các số
liệu kết quả của kỹ thuật này cần được tiến hành một cách thận trọng. Do tính
chất phức tạp của trình biên dịch cũng như trong các hiệu ứng của bộ nhớ đệm
và bộ nhớ chính, cùng với việc ghi nhận quá trình thực hiện của chương trình sẽ
làm ảnh hưởng đến tốc độ thực hiện của chương trình này nên các thống kê của
kỹ thuật này chỉ là kết quả xấp xỉ.
Kỹ thuật ghi nhận quá trình thực hiện thường được kích hoạt bằng một cờ
của trình biên dịch hoặc chức năng đặc biệt. Chương trình được cho thực hiện,

và sau đó một công cụ phân tích sẽ biểu diễn các kết quả. Trên Unix, ta thường
sử dụng cờ -p và công cụ prof như sau:
% cc –p spamtest.c –o spamtest
% spamtest
% prof spamtest
Bảng sau đây thể hiện kết quả ghi nhận quá trình thực hiện chương trình bộ
lọc spam. Chương trình dùng một bức thông điệp cố định và một tập hợp cố
định gồm 217 cụm từ mẫu, tập hợp này so khớp với bức thông điệp 10000 lần.
Chương trình chạy trên máy 250 MHz MIPS R1000 dùng bản gốc của hàm
strstr trong đó gọi các hàm chuẩn khác. Cần chú ý sự xuất hiện các giá trị
kích cỡ của dữ liệu đầu vào (217 cụm từ) và số lần chạy (10000) trong cột số
lần gọi thực hiện.
12234768552: Tổng số lệnh đượcthực hiện
Nguyễn Bá Kỳ -CH0901025 18
Một số vấn đề về tối ưu hóa xử lý chương trình
13961810001: Tổng số chu kỳ tính toán
55.847: Tổng thời gian tính toán (giây)
1.141: Số chu kỳ trung bình/lệnh
Giây % cum% Số chu kỳ Số lệnh Số lần gọi Hàm
45.260 81.0 81.0 11314990000 9440110000 48350000
strchr
6.081 10.9 91.9 1520280000 1566460000 46180000
strncmp
2.592 4.6 96.6 648080000 854500000 2170000
strstr
1.825 3.3 99.8 456225559 344882213 2170435
strlen
0.088 0.2 100.0 21950000 28510000 10000
isspam
0.000 0.0 100.0 100025 100028 1

main
0.000 0.0 100.0 53677 80268 219
_memcopy
0.000 0.0 100.0 48888 46403 217
strcpy
0.000 0.0 100.0 17989 19894 219
fgets
0.000 0.0 100.0 16798 17547 230
__malloc
0.000 0.0 100.0 10305 10900 204
realfree
0.000 0.0 100.0 6293 7161 217
estrdup
0.000 0.0 100.0 6032 8575 231
cleanfre
e
0.000 0.0 100.0 5932 5729 1
readpat
0.000 0.0 100.0 5899 6339 219
getline
0.000 0.0 100.0 5500 5720 220
_malloc
Bảng 1.1 Bảng thống kê quá trình thực hiện
chương trình spamtest nguyên thủy
Rõ ràng hàm strchr và hàm strncmp, cả hai do hàm strstr gọi, chiếm
hầu hết thời gian thực hiện chương trình. Nhận xét của tác giả Knuth hoàn toàn
chính xác: chỉ một phần nhỏ của chương trình đã chiếm gần hết thời gian chạy
chương trình. Khi một chương trình lần đầu tiên được xây dựng profile, hàm
được gọi nhiều nhất thường chiếm đến 50% hoặc hơn thời gian thực hiện
chương trình, như ở trường hợp này. Điều này.giúp ta dễ dàng xác định cần tập

trung chú ý vào phần nào trong chương trình để cải tiến việc thực hiện.
1 2.3 Tập trung quan tâm vào các đoạn mã trọng tâm của
chương trình.
Sau khi viết lại hàm strstr, ta ghi nhận lại quá trình thực hiện của chương
trình spamtest. Lúc này, ta nhận thấy rằng 99.8% thời gian bây giờ bị chiếm
bởi hàm strstr, mặc dù cả chương trình đã nhanh hơn rõ rệt. Khi gặp tình
Nguyễn Bá Kỳ -CH0901025 19
Một số vấn đề về tối ưu hóa xử lý chương trình
trạng có duy nhất một hàm gây ra hiện tượng nút cổ chai ta thường có hai con
đường để chọn lựa: cải tiến hàm đó bằng một thuật toán tốt hơn, hoặc bỏ toàn
bộ hàm đó bằng cách viết lại cả chương trình.
Trong trường hợp này, ta chọn giải pháp viết lại toàn bộ chương trình. Dưới
đây là một vài dòng kết quả đầu tiên của việc ghi nhận quá trình thực hiện của
chương trình spamtest dùng bản cài đặt có tốc độ cao của hàm isspam. Cần
chú ý rằng tổng thời gian toàn bộ đã giảm đi rất nhiều, trong đó hàm memcmp là
hàm được gọi xử lý nhiều nhất (hot spot) và hàm isspam bây giờ chiếm một
phần đáng kể trong thời gian tính toán. Phiên bản này phức tạp hơn so với phiên
bản sử dụng hàm strstr nhưng ít tốn chi phí hơn nhờ không sử dụng hàm
strlen và strchr trong hàm isspam và việc thay thế hàm strncmp bằng
hàm memcmp có chi phí xử lý đối với từng byte thấp hơn.
Giây % cum% Số chu kỳ Số lệnh số lần gọi Hàm
3.524 56.9 56.9 880890000 1027590000 46180000
memcmp
2.662 43.0 100.0 665550000 902920000 10000
isspam
0.001 0.0 100.0 140304 106043 652
strlen
0.000 0.0 100.0 100025 100028 1
main
Bảng 1.2 Bảng thống kê quá trình thực hiện

chương trình spamtest cải tiến
Hãy so sánh số chu kỳ đếm được và số lần gọi trong hai kết quả thống kê
quá trình thực hiện chương trình. Cần lưu ý rằng số lần gọi hàm strlen đã
giảm từ vài triệu lần gọi xuống còn 652 lần, số lần gọi hàm strncmp bằng với
số lần gọi hàm memcmp (có chi phí xử lý đối với từng byte thấp hơn). Cũng cần
lưu ý là hàm isspam sau khi tích hợp hàm strchr vẫn sử dụng số chu kỳ ít
hơn nhiều so với hàm strchr trước đó vì hàm này chỉ kiểm tra các chuỗi mẫu
thích hợp ở mỗi bước.
Một đoạn lệnh khi thực hiện chiếm phần lớn thời gian xử lý của toàn bộ
chương trình, tạm gọi là điểm nóng (hot spot), có thể được loại bỏ hay “làm
nguội” đi bằng một số kỹ thuật đơn giản hơn so với cách ta đã dùng cho chương
Nguyễn Bá Kỳ -CH0901025 20
Một số vấn đề về tối ưu hóa xử lý chương trình
trình bộ lọc spam. Xét ứng dụng Awk, kết quả ghi nhận cho thấy một hàm được
gọi thực hiện đến hàng triệu lần trong vòng lặp sau đây:
for (j=i; j < MAXFLD; j++)
clear(j);
Vòng lặp này, thực hiện việc xóa các trường trước khi đọc vào một dòng
mới, đã chiếm hơn 50% thời gian thi hành. Hằng số MAXFLD (số trường tối đa
của một dòng) bằng 200. Tuy nhiên, trong hầu hết các ứng dụng Awk, số
trường thực sự được sử dụng và cần được xóa chỉ gồm hai đến ba trường. Do
vậy, rất nhiều thời gian bị lãng phí vào việc xóa các trường không bao giờ được
xác lập. Khi thay hằng số trên bằng giá trị số lượng tối đa các trường trong lần
thực hiện trước đó cho phép tăng tốc toàn bộ chương trình lên khoảng 25%.
Chúng ta chỉ cần thay đổi giá trị cận trên của vòng lặp:
for (j=i; j < maxfld; j++)
clear(j);
maxfld = i
1 2.4 Biểu diễn trực quan số liệu thống kê hiệu năng thực hiện
của chương trình

Hình vẽ thường rất hữu ích trong việc thể hiện trực quan các số liệu thống
kê hiệu năng thực hiện của chương trình. Các hình vẽ có khả năng biểu diễn
trực quan thông tin về hiệu quả của việc thay đổi các tham số, so sánh các thuật
toán và các cấu trúc dữ liệu, và đôi khi còn chỉ ra được những trường hợp
chương trình thực hiện theo cách không mong muốn.
Đồ thị dưới đây thể hiệu ảnh hưởng của kích thước mảng các bảng băm lên
thời gian thi hành chương trình của giải thuật Markov (viết bằng C) với dữ liệu
đầu vào gồm 42685 từ và 22482 tiếp đầu ngữ. Ta hãy làm hai thử nghiệm. Một
thử nghiệm gồm một số các lần chạy chương trình dùng các mảng có kích thước
là lũy thừa của 2 từ 2 đến 16384; thử nghiệm kia dùng các mảng có kích thước
là số nguyên tố lớn nhất không vượt quá lũy thừa của 2 gần nhất. Ta xem liệu
Nguyễn Bá Kỳ -CH0901025 21
Một số vấn đề về tối ưu hóa xử lý chương trình
kích thước của mảng là số nguyên tố có tạo ra sự khác biệt nào trong tốc độ
thực hiện chương trình hay không.
Hình 1.1 Đồ thị thể hiện sự biến thiên tốc độ xử lý của chương trình với
kích thước của bảng băm trong trường hợp kích thước là lũy thừa của 2
và trường hợp kích thước là số nguyênt tố
Đồ thị trên cho thấy thời gian thực hiện của chương trình gần như không
thay đổi đối với kích thước bảng băm lớn hơn hay bằng 1000, đồng thời kích
thước bảng băm là lũy thừa của 2 hay là số nguyên tố không phải là yếu tố ảnh
hưởng đến tốc độ thực hiện chương trình.
Bài tập 1.2. Cho dù hệ thống bạn đang dùng có lệnh time hay không, hãy
sử dụng hàm clock hay hàm getTime để viết một tiện ích đo thời gian cho
mục đích dùng riêng của bạn. Hãy so sánh độ chính xác của kết quả đo được
bằng công cụ do bạn xây dựng với kết quả khi sử dụng đồng hồ bên ngoài
(chẳng hạn như đồng hồ treo tường). Những hoạt động khác trong máy tính có
ảnh hưởng như thế nào đến việc đo thời gian?
Nguyễn Bá Kỳ -CH0901025 22
50

20
10
5
1
2
Kích thước bảng băm
1
10
100 1000 10000
0.5
0.2
Thời gian
(giây)
Số nguyên tố
Lũy thừa của 2
Một số vấn đề về tối ưu hóa xử lý chương trình
Bài tập 1.3. Ở bảng kết quả thống kê quá trình hoạt động đầu tiên, hàm
strchr được gọi thực hiện 48350000 lần trong khi hàm strncmp chỉ gọi thực
hiện 46180000 lần. Hãy giải thích sự khác biệt này.
1.3 Các chiến lược tối ưu hóa thời gian thực hiện
Trước khi bắt đầu sửa đổi một chương trình để tăng tốc độ xử lý nhanh hơn,
hãy chắc rằng chương trình hiện tại chạy thật sự quá chậm, và hãy dùng các
công cụ đo thời gian và công cụ ghi nhận quá trình thực hiện để xác định được
phần lớn thời gian xử lý của chương trình làm những công việc gì. Một khi đã
biết xác định được vấn đề, ta có nhiều áp dụng nhiều chiến lược để thực hiện
mục đích tối ưu hóa thời gian thực hiện. Dưới đây là một số chiến lược nhằm tối
ưu hóa thời gian thực hiện theo thứ tự hiệu quả giảm dần.
1 3.1 Sử dụng thuật toán hoặc cấu trúc dữ liệu tốt hơn
Yếu tố quan trọng nhất làm cho chương trình chạy nhanh hơn là lựa chọn
thuật toán và cấu trúc dữ liệu hiệu quả. Trên thực tế, có sự khác biệt rất lớn giữa

một thuật toán hiệu quả và một thuật toán không hiệu quả. Như đã trình bày ở
phần 1.1-Hiện tượng nút cổ chai, đối với chương trình bộ lọc spam, việc thay
đổi cấu trúc dữ liệu giúp cải thiện tốc độ nhanh hơn 10 lần, và hệ số tốc độ này
còn có thể được cải thiện lớn hơn nếu như thuật toán có độ phức tạp giảm từ
O(n
2
) còn O(n logn). Điều này đã được phân tích chi tiết ở Chương 2 - Thuật
toán và Cấu trúc Dữ liệu.
Vấn đề xác định độ phức tạp của thuật toán cũng như chương trình là vấn đề
khá phức tạp. Đoạn lệnh duyệt 1 chuỗi ký tự s dưới đây dường như có độ phức
tạp tuyến tính nhưng thật ra độ phức tạp của thuật toán này lại là bậc 2 (O(n
2
)).
Nếu chuỗi s có n ký tự, mỗi lần gọi hàm strlen sẽ thực hiện việc duyệt qua n
ký tự của chuỗi, trong khi vòng lặp được thực hiện n lần.
? for (i = 0; i < strlen(s); i++)
? if (s[i] == c)
?
Nguyễn Bá Kỳ -CH0901025 23
Một số vấn đề về tối ưu hóa xử lý chương trình
1 3.2 Sử dụng các chức năng tối ưu hóa của trình biên dịch
Chúng ta có thể cải thiện đáng kể tốc độ xử lý của chương trình một cách
đơn giản mà không cần sửa đổi bất kỳ một dòng mã nguồn nào bằng cách sử
dụng hiệu quả các chức năng tối ưu hóa có sẵn trong trình biên dịch. Các trình
biên dịch hiện đại ngày nay có khả năng tối ưu hóa hiệu quả các đoạn mã nguồn
chương trình.
Việc sử dụng các chiến lược tối ưu hóa của trình biên dịch có khuynh hướng
gây xáo trộn việc bắt lỗi (debug) mã nguồn. Do đó, ở chế độ mặc định, các trình
biên dịch C và C++ thường không sử dụng nhiều các chức năng tối ưu hóa cho
chương trình. Các tùy chọn trong trình biên dịch cho phép lập trình viên quyết

định sử dụng hay không các chức năng tối ưu hóa của trình biên dịch. Lập trình
viên thường cần phải kích thước tường minh tùy chọn tối ưu hóa mã nguồn của
trình biên dịch sau khi đã kiểm lỗi xong chương trình .
Việc tối ưu hóa thông qua chức năng của trình biên dịch thường cải thiện
thời gian chạy chương trình từ vài phần trăm cho đến hai trăm phần trăm. Tuy
nhiên, việc tối ưu hóa này đôi khi lại có thể làm giảm tốc độ chương trình, do
vậy cần đo lường sự cải thiện trước khi đóng gói sản phẩm. Đối với chương
trình bộ lọc spam, với cùng dữ liệu thử nghiệm, chương trình sử dụng phiên bản
thuật toán so trùng khớp cuối cùng có thời gian thi hành ban đầu là 8.1 giây
giảm xuống còn 5.9 giây khi sử dụng chức năng tối ưu hóa của trình biên dịch
(mức độ cải thiện là 25%). Ngược lại, ở phiên bản dùng hàm strstr đã sửa
đổi, việc tối ưu hóa không đem lại một cải thiện nào bởi lẽ hàm strstr đã
được tối ưu hóa khi đưa vào thư viện: bộ tối ưu hóa chỉ áp dụng vào mã nguồn
đang được biên dịch trong chương trình chứ không phải vào các thư viện của hệ
thống. Tuy nhiên, một số trình biên dịch có các bộ tối ưu hóa toàn cục có khả
năng phân tích toàn bộ chương trình tìm những chỗ còn có thể cải thiện được.
Nếu một trình biên dịch như thế có trong hệ thống của bạn, hãy thử sử dụng;
điều này có thể giúp giảm được một vài chu kỳ xử lý.
Nguyễn Bá Kỳ -CH0901025 24

×