• Thứ Sáu, 03/06/2005 14:47 (GMT+7)

    Công cụ phát triển: Trình biên dịch C/C++ tốt nhất?

    Lượt xem 19821
    Đánh giá

    So sánh 9 trình biên dịch C/C++ theo các tiêu chí nổi bật của ngôn ngữ: sự linh động, tính khả chuyển, hiệu quả và tốc độ. Mặc dù xuất hiện nhiều ngôn ngữ lập trình và công nghệ mới, C++ vẫn là công cụ chính của nhiều nhà phát triển, có thể vị trí của nó sẽ vẫn được duy trì nhiều năm nữa. C++ nổi bật về sự linh động, tính khả chuyển, hiệu quả và tốc độ. Mặc dù năng lực xử lý của máy tính đã có sự gia tăng đáng kể, hiệu suất phần mềm vẫn là yếu tố quan trọng, C++ là ngôn ngữ sẽ cho hiệu suất vượt trội hầu như về mọi phương diện nếu được sử dụng một cách đúng đắn.

    So sánh 9 trình biên dịch C/C++ theo các tiêu chí nổi bật của ngôn ngữ: sự linh động, tính khả chuyển, hiệu quả và tốc độ.
    Mặc dù xuất hiện nhiều ngôn ngữ lập trình và công nghệ mới, C++ vẫn là công cụ chính của nhiều nhà phát triển, có thể vị trí của nó sẽ vẫn được duy trì nhiều năm nữa. C++ nổi bật về sự linh động, tính khả chuyển, hiệu quả và tốc độ. Mặc dù năng lực xử lý của máy tính đã có sự gia tăng đáng kể, hiệu suất phần mềm vẫn là yếu tố quan trọng, C++ là ngôn ngữ sẽ cho hiệu suất vượt trội hầu như về mọi phương diện nếu được sử dụng một cách đúng đắn.
    Bài viết này so sánh 9 trình biên dịch C++ phổ biến, đánh giá về hiệu suất, tính năng và công cụ. Các trình biên dịch (TBD) hoặc là chỉ hỗ trợ môi trường Win32, hoặc là hỗ trợ các biến thể khác Win32. Việc kiểm tra đánh giá được thực hiện trên 1 máy Windows XP Pro (1 BXL 2GHz, 512MB RAM) hoàn toàn không chạy các tác vụ khác.

    Thời gian biên dịch
    Trong nhiều trường hợp, thời gian biên dịch không quan trọng. Tuy nhiên, nó là yếu tố then chốt trong các hệ thống lớn hay trong môi trường phát triển thường hay biên dịch ứng dụng (như trong mô hình phát triển eXtreme Programming). Các yếu tố quan trọng trong quá trình biên dịch/liên kết mã nguồn bao gồm: số lượng các thư viện, sử dụng các thành phần được biên dịch trước, độ phức tạp của mã lệnh, yêu cầu về tối ưu (cho cả biên dịch và liên kết) và kích thước các mô đun biên dịch. Các tình huống được xem xét:

    1. C1. Một file C lớn (1000 hàm), duy nhất (không 'include'); chỉ biên dịch, không tối ưu.
    2. C2. Một file C với số lượng file include lớn (500); chỉ biên dịch, không tối ưu.
    3. C3. Một file C với số lượng file include lồng nhau nhiều cấp (100), mỗi file được include bởi file trước nó và file main. Thử nghiệm đánh giá ảnh hưởng của việc include nhiều cấp; chỉ biên dịch không tối ưu.
    4. pch. Một bộ file C++ (main.cpp, pch.cpp và 40 file lớp .h/.cpp) dùng chung header, (biên dịch và liên kết; các header được biên dịch trước; không tối ưu).
    5. whereis. Một file C++ duy nhất nhưng phức tạp với nhiều include thư viện hệ điều hành và template (chỉ biên dịch; tối ưu về kích thước).
    6. MMComBsc. Thư viện DLL lớn (44 file nguồn C và 37 file nguồn C++, 111 file header, kích thước biên dịch 80KB) bao gồm các hàm COM và các lớp (biên dịch và liên kết; các header được biên dịch trước; tối ưu về kích thước).
    7. zlib. Thư viện nén dữ liệu tổng quát và miễn phí, có thể chạy trên nhiều nền tảng hệ thống.

    Dùng các script Python (http://www.ddj.com/ftp/2003/200310/cppcomp.zip) để tạo các file nguồn cho các tình huống 1- 4. Mã nguồn whereis có ở http://stlsoft.org/ (file nhị phân cập nhật có ở http://synesis.com.au/r_systools.html). Các file nguồn cho MMComBs.dll có nhiều thứ liên quan đến vấn đề bản quyền vì vậy bạn tạm chấp nhận thông tin cung cấp ở đây!
    Kết quả kiểm tra các tình huống 1- 3 và 5 được lấy với ptime (http://synesis.com.au/r_systools.html) bằng cách thực hiện nhiều lần (15 lần), bỏ qua 2 kết quả cao nhất và 1 kết quả thấp nhất, còn lại lấy trung bình nhằm giảm sai lệch do bước đệm hoặc khởi động. Kiểm tra các tình huống 4, 6 và 7 dùng các makefile, đo thời gian bằng ptime. Các kết quả kiểm tra được thể hiện trong Bảng 1.
    Khái niệm 'Did Not Compile' (DNC, không biên dịch) của CodeWarrior trong các kết quả ở tình huống C3 là do TBD từ chối xử lý include lồng nhau lên tới 100 cấp; các thử nghiệm cho thấy số cấp giới hạn là 30. Thông tin trợ giúp của CodeWarrior: 'Để khắc phục vấn đề này, hãy nghiên cứu trình tự lô gic ẩn đằng sau các #include lồng nhau. Có thể có cách tách các #include lồng nhau nhiều cấp thành một loạt #include lồng nhau ít cấp hơn' - điều này có thể đúng nhưng không phải lúc nào cũng có thể thực hiện được. Watcom có thể không biên dịch các tình huống whereis và MMComBsc vì nó không hỗ trợ template hiệu quả.
    Có một số khác biệt đáng kể về hiệu suất. Borland tỏ ra tốt nhất, theo sát là VC++ 6, Digital Mars và VC++ 7 đồng hạng 3. CodeWarrior, GCC và Intel là các TBD chậm chạp nhất trong nhóm. VC++ 7 biên dịch pch nhanh hơn 43 lần so với CodeWarrior! VC++ 7.1 chậm hơn VC++ 7.0 trong mọi thử nghiệm.


    Bảng 1:Thời gian biên dịch(ms) của 7 tình huống với các trình biên dịch(các con số in đậm là những kết quả tốt nhất)


    Tốc độ sinh mã
    Kế tiếp chúng ta xem xét tốc độ sinh mã, giới hạn ở 5 tình huống sau:
    1. Dhrystone (http://www.webopedia .com/TERM/D/Dhrystone.html). Phép đo này kiểm tra tốc độ tính toán số nguyên. Vì nó sử dụng toàn bộ CPU (không có bất kỳ tác vụ truy cập tài nguyên hay I/O nào trong quá trình kiểm tra), đây là phép đo tốt về tốc độ mã lệnh đã được biên dịch. Tốc độ được đo với số Dhrystone trong 1 giây (số càng lớn càng tốt).

    2. Int2string. Chuyển đổi số nguyên sang dạng chuỗi là công việc có thể hao tốn tài nguyên hệ thống. Các số nguyên lên hàng triệu chữ số (0->9.999,999) được chuyển sang dạng chuỗi, và các độ dài chuỗi được cộng dồn lại. Ở đây sử dụng 2 phương pháp chuyển đổi:
    o    Dùng hàm thư viện sprintf(). Tốc độ này phản ánh sự khác biệt về hiệu suất của thư viện các TBD. (Intel dùng thư viện của VC++ 7.0).
    o    Dùng template integer_to_string<>. Tốc độ này phản ánh trực tiếp tốc độ của mã lệnh đã biên dịch.

    3. StringTok. Tình huống này tạo một tập chuỗi lớn để phân tách từ mã (token), dùng ';' làm dấu phân cách. Nó phân tách chuỗi theo từ mã, sau đó lặp vòng tuần tự để tính tổng các độ dài từ mã. Ở đây dùng các thư viện phân tách từ mã boost::tokenizer<>(http:'//boost.org) và stlsoft::string_tokenizer<>(http://stlsoft.org/).

    4. RectArr. Để bắt các TBD phát huy hết năng lực sinh mã trong các tình huống phức tạp, ở đây dùng template mảng 3D fixed_array_2d<> của STLSoft, lập tham số là kiểu giá trị stlsoft::basic_simple_string thay vì std::basic_string<> để chú trọng đến ảnh hưởng hiệu suất của TBD và giảm những khác biệt trong các thư viện chuẩn. Tình huống này tạo một mảng 3D có kích thước thay đổi (100x100x100) và lặp vòng qua cả 3 'chiều' của mảng, gán một giá trị ngẫu nhiên cho mỗi phần tử. Có 2 cách thực hiện:
    o    Cách đầu tiên thực hiện việc đếm một lần
    o    Cách thứ hai thực hiện 10 lần. Việc cấp phát và khởi tạo 1 triệu phần tử  được thực hiện dần.

    5. zlib. Đây là thư viện đặc trưng trong nhiều ứng dụng (http://zlib.org). Nó là phép kiểm tra hiệu suất có giá trị. Chương trình kiểm tra thực hiện nén toàn bộ nội dung một file nguồn rồi xuất sang một file kết quả trong một vòng lặp đo thời gian. Ở đây thực hiện biên dịch cả mã nguồn zlib 1.1.4 và chương trình kiểm tra với 9 TBD và thực thi nó với một file lớn (65 MB) và một file nhỏ (149 KB).
    Khác với tình huống Dhrystone (có cơ chế đo thời gian riêng), tất cả các tình huống khác đều thực hiện định thời dựa trên lớp performance_counter của WinSTL (xem http://winstl.org/ và http://www.windevnet.com/documents/win0305a/). Các kết quả phản ánh tốc độ mã lệnh thuần túy, không bị ảnh hưởng của hệ điều hành và các tác động khác. Tất cả tình huống đều được tối ưu về tốc độ (-O2, -opt speed, -o+speed, -O3, -O2, -O2, -ot). Bảng 2 thể hiện kết quả.
    Đặc biệt tình huống Dhrystone được thực hiện chạy kiểm tra 9 lần, bỏ qua các giá trị cao nhất và thấp nhất, còn lại tính trung bình. (Mã nguồn của tất cả các tình huống có ở http://www.ddj.com/ftp/2003/200310/cppcomp.zip)
    Giá trị 'DNC' của Digital Mars là do TBD này không được hỗ trợ trong thư viện Boost 1.30. Vấn đề tương thích Boost/Digital Mars đang được thực hiện, và có thể đã hoàn tất khi bạn đọc bài viết này. Các giá trị 'DNC' của Watcom phản ánh việc thiếu khả năng hỗ trợ template nói chung của nó.

    Intel vượt lên trên các TBD khác, nó nhanh nhất ở 2 tình huống và đứng hạng 2 ở 5 tình huống còn lại. (Thực ra hiệu suất kém của nó là ở tình huống Int2String(sprintf), trong đó hiệu suất của nó tùy thuộc nhiều vào hàm sprintf() của thư viện VC++ 7.0). Digital Mars, VC++ 7.0 và VC++ 7.1 ngang ngửa nhau ở vị trí thứ hai.
    Do không nổi bật ở 5 tình huống, và cho tốc độ kém ở 2 tình huống khác, Watcom đành phải xếp cuối bảng. Tuy nhiên, nó chiến thắng ở tình huống Int2String(sprintf()), vì vậy cũng không đến nỗi tệ. Borland và CodeWarrior thực hiện tốt ở vài tình huống - Borland nhanh nhất với zlib nhưng lại mất điểm ở những tình huống khác. GCC thực hiện kém ở tất cả các tình huống, ngoại trừ 2 biến thể của STLSoft.
    Những khác biệt giữa các biến thể của các tình huống Int2String và StringTok không đáng kể. Dùng template integer_to_string<> cải thiện tốc độ đáng kể, thời gian thực thi chỉ khoảng 15% đến 55% so với printf(). Các bộ phân tích từ mã chuỗi cho thấy có sự khác biệt đáng kể: thời gian thực thi của bộ phân tích từ mã của STLSoft khoảng từ 6 đến 26 % so với Boost.


    Bảng : 9 trình biên dịch C/C++ được đánh giá so sánh

    Kích thước mã sinh ra
    Tốc độ thực thi không phải lúc nào cũng quan trọng hơn kích thước mã sinh ra, cũng như việc tối ưu tốc độ thực thi không phải luôn cho kết quả xử lý nhanh hơn, vì bộ mã lớn hơn thường không có đủ bộ nhớ đệm và phải yêu cầu bộ nhớ ảo của hệ điều hành.
    Hẳn bạn luôn thích mã chương trình nhỏ gọn. Trong bảng 3, chú trọng đến kích thước mô đun, VC++ là TBD dẫn đầu. VC++ 7.0 tạo ra mã nhỏ nhất, theo sau là VC++ 7.1, kế tiếp là VC++ 6.0. Intel, Digital Mars, và Watcom thực hiện  công việc khá tốt. Borland và CodeWarrior cũng không tệ. GCC gây kinh ngạc với các mô đun có kích thước gấp 10 lần so với TBD đầu bảng trong một số tình huống.

    Thư viện
    Standard Library. Ngoại trừ Digital Mars và Watcom, tất cả các TBD đều hỗ trợ tốt C++.98 Standard Library. Digital Mars C++ có kèm theo STL và STLport mới nhất của SGI (www.stlport.org), nhưng chưa cập nhật các tên header mới ( thay vì ) và cho phép khai báo bên trong std namespace. Cả hai TBD trên đang được sửa đổi để tương thích hoàn toàn.

    ATL
    . Ngoại trừ GCC và Watcom, tất cả TBD đều hỗ trợ ATL, tuy nhiên có một số chỉ hỗ trợ phiên bản 3 chứ không phải 7.
    Boost. Bộ thư viện này được tất cả các TBD hỗ trợ, trừ Digital Mars (đang được sửa đổi), VC++ 6 (hỗ trợ hạn chế) và Watcom (hoàn toàn không hỗ trợ).
    Managed C++. Chỉ có VC++ 7 hỗ trợ Managed C++. Tuy nhiên, theo bối cảnh của bài viết này, Managed C++ không phải là thư viện quan trọng.
    MFC. Mặc dù khá 'xưa' nhưng MFC vẫn còn được sử dụng rộng rãi (và vẫn còn có ích). Nó được hỗ trợ đầy đủ với VC++, Intel C++, CodeWarrior và Digital Mars. MFC cũng có thể dùng trong Borland C++ Builder, nhưng Watcom và GCC thì hoàn toàn không hỗ trợ.
    STLSoft. Ngoại trừ Watcom, tất cả các TBD khác đều hỗ trợ hầu hết các thư viện của STLSoft, Watcom cũng hỗ trợ một số khá lớn. STLSoft được tích hợp chung với Digital Mars từ phiên bản 8.34 trở đi.
    Win32/Platform SDK. Tất cả TBD Win32 đều hỗ trợ Win32 API, mặc dù có một số không hỗ trợ phiên bản đi theo Platform SDK. Đặc biệt, GCC và Watcom không hỗ trợ phiên bản Platform SDK tháng 2/2003.
    16­bit. Cả Digital Mars và Watcom đều hỗ trợ 16-bit. Nhu cầu về tính năng này không cao; nhưng cần có công cụ đáp ứng trong trường hợp cần thiết.
    WTL. VC++ và Intel làm việc rất tốt với WTL, hơn hẳn các TBD khác. CodeWarrior thì chỉ làm việc một ít, còn Borland và Digital Mars thì cần tốn một ít công sức.

     


    Bảng 2 : Thời gian thực thi (ms) của 9 trình biên dịch qua các tình huống(các con số in đậm là những kết quả tốt nhất)


    Công cụ phát triển
    Hầu hết các TBD đều đi kèm môi trường phát triển và bẫy lỗi tích hợp (IDDE - Integrated Development and Debugging Environment). Việc chọn lựa công cụ phát triển gần như là vấn đề sở thích cá nhân. Nói chung tất cả các IDDE đều có tính năng tối thiểu cần thiết để tạo dự án phần mềm và mã nguồn cũng như bẫy lỗi. Tuy nhiên một số IDDE khá đơn giản. Các IDDE như Digital Mars hay Watcom khó lòng lôi kéo các lập trình viên khỏi môi trường phát triển ưa thích.

    Kết luận
    Không thể đơn giản nói TBD nào mạnh nhất. Hầu hết các TBD đều có có điểm mạnh và điểm yếu.

    o  TBD của Borland nhanh nhất, hỗ trợ ngôn ngữ tốt và không có mặt nào đáng chê trách. Tuy nhiên, nó hay báo lỗi biên dịch bên trong (ICE - internal compiler error) khi phải thực hiện công việc quá khó, đây cũng là 'bệnh' của VC++.

    o   Code Warrior rất mạnh về mặt ngôn ngữ, có thông báo lỗi dễ hiểu. Nó là một IDDE tốt (nhưng không thật xuất sắc), sinh mã khá hiệu quả, và hỗ trợ tất cả các thư viện phổ biến. Đây là ứng cử viên sáng giá nếu bạn chỉ được quyền chọn một.

    o   Digital Mars tỏ ra xuất sắc, xếp trong 3 hạng đầu về hỗ trợ ngôn ngữ, thời gian biên dịch và tốc độ thực thi. Nó chiếm hạng 4 về kích thước file thực thi. Tuy nhiên nó lại hỗ trợ thư viện Standard Library không theo chuẩn, thỉnh thoảng báo lỗi ICE, và môi trường phát triển lạc hậu. Tuy nhiên nó miễn phí và có được sự hỗ trợ tốt.

    o   GCC hỗ trợ ngôn ngữ tốt nhất, nó có khả năng làm việc tốt với bất kỳ TBD nguồn mở nào (có thể với TBD thương mại). Tuy nhiên, đây cũng là vấn đề gây nên các đặc tính kém hiệu quả của nó. Các tình huống thử nghiệm cho thấy GCC là TBD chậm nhất, sinh mã chậm nhất và cồng kềnh nhất (ngược với Digital Mars).

    o Tốc độ sinh mã - đây thường là yếu tố quan trọng nhất - Intel chiếm ưu thế tuyệt đối. TBD này cho kích thước mã sinh ra tốt nhưng lại bị mất điểm về thời gian biên dịch. Intel cũng hỗ trợ ngôn ngữ tốt nhưng nó không có môi trường phát triển riêng mà được tích hợp chung với Visual Studio (98 và .NET). Thật ngạc nhiên là nhiều lập trình viên trên môi trường Microsoft (các hệ thống Win32, Visual Studio) không hề biết khai thác điều này.

    o Visual C++ thực hiện tốt hơn mong đợi. Nó sinh mã có kích thước nhỏ nhất, biên dịch nhanh, có tốc độ sinh mã tốt (nhưng thua Intel). Sau hết, nó cũng hỗ trợ ngôn ngữ tốt; thậm chí VC++ 7.1 có thể phát hiện một số lỗi typename trong các thư viện STLSoft mà CodeWarrior và GCC bỏ sót. VC++ đã từng bị đánh giá thấp do tính năng hỗ trợ ngôn ngữ kém (giờ đây vấn đề này được giải quyết đầy ấn tượng trong phiên bản 7.1, tuy rằng hơi muộn), thời gian cập nhật lâu (từ Version 6 lên 7 mất 5 năm), kích thước cồng kềnh tới hàng gigabyte, và cả TBD và IDDE đều 'nghĩ' rằng bạn sẽ dùng MFC của Microsoft. Cuối cùng, WTL được hỗ trợ đầy đủ trong Visual Studio .NET và các trình trợ giúp.

    o Watcom hỗ trợ template kém, điều này làm cho nó khó so sánh với các TBD khác. Nó tốt trong những lĩnh vực đặc trưng, mặc dù tốc độ Dhrystone cực kỳ kém. Hy vọng nó sẽ tiếp tục phát triển theo 'chiêu bài' Open Watcom của mình, và quay lại với ánh hào quang mà Watcom C/C++ từng có vào những năm giữa thập niên 1990.

    Tất cả các giới dùng C++ chuyên nghiệp đều không dùng duy nhất một TBD. Không có TBD nào đáp ứng được mọi yêu cầu, dùng nhiều TBD có thể cho giải pháp toàn diện hơn. Hơn nữa, không có gì tốt hơn là làm cho mã nguồn của bạn có khả năng làm việc với nhiều hơn một TBD. Về phương diện này thì Borland và Visual C++ đặc biệt kém. Hãy chỉnh sửa môi trường phát triển C++ kết hợp 3 hay 4 TBD, bạn sẽ thấy chất lượng chương trình của mình tăng lên rõ rệt. 


    Bảng 3: Kích thước các mô đun (byte) của 8 tình huống (các con số in đậm là những kết quả tốt nhất)

    DDJ 10/2003

    Từ khóa: tool_
    ID: A0401_77
    Ý kiến của bạn? Ý kiến của bạn?
    Tin ngày :

Thuật ngữ