JIT và Static Compiler
Như đã nói trong các bài viết trước, JIT có thể biên dịch IL / bytecode thành mã gốc trong thời gian chạy. Chi phí của điều đó đã được đề cập, nhưng không phải là kết luận của nó:
JIT có một vấn đề lớn là nó không thể biên dịch mọi thứ: biên dịch JIT mất thời gian, vì vậy JIT sẽ chỉ biên dịch một số phần của mã, trong khi trình biên dịch tĩnh sẽ tạo ra một bản nhị phân gốc đầy đủ: Đối với một số loại chương trình, tĩnh trình biên dịch sẽ dễ dàng vượt trội hơn JIT.
Tất nhiên, C # (hoặc Java, hoặc VB) thường nhanh hơn để tạo ra giải pháp khả thi và mạnh mẽ hơn C ++ (nếu chỉ vì C ++ có ngữ nghĩa phức tạp và thư viện chuẩn C ++, mặc dù thú vị và mạnh mẽ, nhưng khá kém khi so sánh với đầy đủ phạm vi của thư viện chuẩn từ .NET hoặc Java), do đó, thông thường, sự khác biệt giữa C ++ và .NET hoặc Java JIT sẽ không hiển thị cho hầu hết người dùng và đối với những mã nhị phân quan trọng, tốt, bạn vẫn có thể gọi xử lý C ++ từ C # hoặc Java (ngay cả khi loại cuộc gọi riêng này có thể khá tốn kém) ...
Lập trình siêu hình C ++
Lưu ý rằng thông thường, bạn đang so sánh mã thời gian chạy C ++ với mã tương đương trong C # hoặc Java. Nhưng C ++ có một tính năng có thể vượt trội hơn Java / C #, đó là lập trình siêu mẫu: Quá trình xử lý mã sẽ được thực hiện tại thời điểm biên dịch (do đó, làm tăng rất nhiều thời gian biên dịch), dẫn đến thời gian chạy bằng không (hoặc gần như bằng không).
Tôi vẫn chưa thấy ảnh hưởng thực tế về điều này (tôi chỉ chơi với các khái niệm, nhưng sau đó, sự khác biệt là số giây thực thi đối với JIT và 0 đối với C ++), nhưng điều này đáng nói, cùng với lập trình siêu mẫu thực tế thì không. không đáng kể...
Chỉnh sửa 2011/06/10: Trong C ++, việc chơi với các kiểu được thực hiện tại thời điểm biên dịch, nghĩa là tạo ra mã chung gọi mã không chung (ví dụ: trình phân tích cú pháp chung từ chuỗi sang kiểu T, gọi API thư viện chuẩn cho kiểu T mà nó nhận ra, và làm cho trình phân tích cú pháp dễ dàng mở rộng bởi người dùng của nó) rất dễ dàng và rất hiệu quả, trong khi phần tương đương trong Java hoặc C # rất khó viết và sẽ luôn chậm hơn và được giải quyết trong thời gian chạy ngay cả khi các loại được biết trong thời gian biên dịch, có nghĩa là hy vọng duy nhất của bạn là JIT sẽ hoàn thiện toàn bộ vấn đề.
...
Chỉnh sửa 2011/09-20: Nhóm đằng sau Blitz ++ ( Trang chủ , Wikipedia ) đã đi theo hướng đó và rõ ràng, mục tiêu của họ là đạt được hiệu suất của FORTRAN về các phép tính khoa học bằng cách chuyển càng nhiều càng tốt từ thực thi thời gian chạy sang thời gian biên dịch, thông qua lập trình siêu mẫu C ++ . Vì vậy, " Tôi vẫn chưa để thấy ảnh hưởng cuộc sống thực về vấn đề này " phần tôi đã viết ở trên dường như không tồn tại trong cuộc sống thực.
Sử dụng bộ nhớ C ++ gốc
C ++ có cách sử dụng bộ nhớ khác với Java / C #, và do đó, có những ưu điểm / khuyết điểm khác nhau.
Không có vấn đề gì về tối ưu hóa JIT, không có gì nhanh chóng như truy cập trực tiếp con trỏ vào bộ nhớ (chúng ta hãy bỏ qua bộ nhớ cache của bộ xử lý trong giây lát, v.v.). Vì vậy, nếu bạn có dữ liệu liền kề trong bộ nhớ, việc truy cập nó thông qua con trỏ C ++ (tức là con trỏ C ... Hãy cho Caesar là do của nó) sẽ nhanh hơn nhiều lần so với trong Java / C #. Và C ++ có RAII, giúp xử lý dễ dàng hơn rất nhiều so với C # hoặc thậm chí trong Java. C ++ không cần xác using
định phạm vi tồn tại của các đối tượng của nó. Và C ++ không có finally
mệnh đề. Đây không phải là một lỗi.
:-)
Và mặc dù có cấu trúc giống như C # nguyên thủy, các đối tượng C ++ "trên ngăn xếp" sẽ không tốn kém gì khi cấp phát và hủy bỏ, và sẽ không cần GC hoạt động trong một luồng độc lập để thực hiện việc dọn dẹp.
Đối với phân mảnh bộ nhớ, các trình cấp phát bộ nhớ trong năm 2008 không phải là các trình cấp phát bộ nhớ cũ từ năm 1980 thường được so sánh với GC: Cấp phát C ++ không thể di chuyển trong bộ nhớ, đúng, nhưng sau đó, giống như trên hệ thống tệp Linux: Ai cần đĩa cứng chống phân mảnh khi phân mảnh không xảy ra? Việc sử dụng bộ cấp phát phù hợp cho đúng tác vụ phải là một phần của bộ công cụ dành cho nhà phát triển C ++. Giờ đây, việc viết các trình phân bổ không phải là dễ dàng, và sau đó, hầu hết chúng ta đều có những việc tốt hơn để làm và để sử dụng hầu hết, RAII hoặc GC là quá đủ tốt.
Chỉnh sửa 2011-10-04: Ví dụ về trình phân bổ hiệu quả: Trên nền tảng Windows, kể từ Vista, đống phân mảnh thấp được bật theo mặc định. Đối với các phiên bản trước, LFH có thể được kích hoạt bằng cách gọi hàm HeapSetInformation của WinAPI ). Trên các hệ điều hành khác, bộ phân bổ thay thế được cung cấp (xemhttps://secure.wikimedia.org/wikipedia/vi/wiki/Malloc để biết danh sách)
Giờ đây, mô hình bộ nhớ có phần trở nên phức tạp hơn với sự nổi lên của công nghệ đa lõi và đa luồng. Trong lĩnh vực này, tôi đoán .NET có lợi thế hơn, và Java, tôi đã nói, giữ vị trí cao hơn. Thật dễ dàng để một số hacker "trên kim loại trần" ca ngợi mã "gần máy" của anh ta. Nhưng hiện nay, việc sản xuất lắp ráp bằng tay tốt hơn là khá khó khăn hơn là để trình biên dịch làm việc của nó. Đối với C ++, trình biên dịch thường trở nên tốt hơn so với hacker kể từ một thập kỷ. Đối với C # và Java, điều này thậm chí còn dễ dàng hơn.
Tuy nhiên, tiêu chuẩn mới C ++ 0x sẽ áp đặt một mô hình bộ nhớ đơn giản cho trình biên dịch C ++, mô hình này sẽ chuẩn hóa (và do đó đơn giản hóa) mã đa xử lý / song song / luồng hiệu quả trong C ++, đồng thời làm cho việc tối ưu hóa dễ dàng hơn và an toàn hơn cho trình biên dịch. Nhưng sau đó, chúng ta sẽ xem trong vài năm tới liệu những lời hứa của nó có thành sự thật hay không.
C ++ / CLI so với C # / VB.NET
Lưu ý: Trong phần này, tôi đang nói về C ++ / CLI, tức là C ++ được lưu trữ bởi .NET, không phải C ++ gốc.
Tuần trước, tôi đã có một khóa đào tạo về tối ưu hóa .NET và phát hiện ra rằng dù sao thì trình biên dịch tĩnh cũng rất quan trọng. Quan trọng hơn JIT.
Cùng một đoạn mã được biên dịch trong C ++ / CLI (hoặc tổ tiên của nó, Managed C ++) có thể nhanh hơn nhiều lần so với cùng một đoạn mã được tạo trong C # (hoặc VB.NET, mà trình biên dịch của nó tạo ra IL giống như C #).
Bởi vì trình biên dịch tĩnh C ++ tốt hơn rất nhiều để tạo ra mã đã được tối ưu hóa hơn so với C #.
Ví dụ: nội tuyến hàm trong .NET được giới hạn đối với các hàm có mã byte có độ dài nhỏ hơn hoặc bằng 32 byte. Vì vậy, một số mã trong C # sẽ tạo ra một bộ truy cập 40 byte, bộ truy cập này sẽ không bao giờ được JIT chèn vào. Mã tương tự trong C ++ / CLI sẽ tạo ra một bộ truy cập 20 byte, sẽ được nội tuyến bởi JIT.
Một ví dụ khác là các biến tạm thời, được trình biên dịch C ++ biên dịch đơn giản trong khi vẫn được đề cập trong IL do trình biên dịch C # tạo ra. Tối ưu hóa biên dịch tĩnh C ++ sẽ dẫn đến ít mã hơn, do đó, một lần nữa cho phép tối ưu hóa JIT tích cực hơn.
Lý do cho điều này được suy đoán là thực tế trình biên dịch C ++ / CLI được hưởng lợi từ các kỹ thuật tối ưu hóa rộng lớn từ trình biên dịch gốc C ++.
Phần kết luận
Tôi yêu C ++.
Nhưng theo tôi thấy, C # hay Java đều tốt hơn. Không phải vì chúng nhanh hơn C ++, mà vì khi bạn bổ sung các phẩm chất của chúng, chúng sẽ có năng suất cao hơn, cần ít đào tạo hơn và có nhiều thư viện tiêu chuẩn hoàn chỉnh hơn C ++. Và đối với hầu hết các chương trình, sự khác biệt về tốc độ của chúng (theo cách này hay cách khác) sẽ không đáng kể ...
Chỉnh sửa (2011-06-06)
Kinh nghiệm của tôi trên C # /. NET
Bây giờ tôi đã có 5 tháng viết mã C # chuyên nghiệp gần như độc quyền (bổ sung vào CV của tôi đã có đầy đủ C ++ và Java, và một chút về C ++ / CLI).
Tôi đã chơi với WinForms (Ahem ...) và WCF (tuyệt vời!) Và WPF (Tuyệt vời !!!! Cả hai thông qua XAML và raw C #. WPF rất dễ dàng tôi tin rằng Swing không thể so sánh với nó), và C # 4.0.
Kết luận là mặc dù việc tạo mã hoạt động trong C # / Java dễ dàng / nhanh hơn so với C ++, nhưng việc tạo ra một mã mạnh, an toàn và mạnh mẽ trong C # (và thậm chí trong Java) còn khó hơn rất nhiều so với C ++. Có rất nhiều lý do, nhưng nó có thể được tóm tắt bởi:
- Generics không mạnh bằng các template ( cố gắng viết một phương thức Parse chung hiệu quả (từ chuỗi sang T) hoặc một phương thức hiệu quả tương đương với boost :: lexical_cast trong C # để hiểu vấn đề )
- RAII vẫn chưa có đối thủ ( GC vẫn có thể bị rò rỉ (vâng, tôi đã phải xử lý vấn đề đó) và sẽ chỉ xử lý bộ nhớ. Ngay cả C #
using
cũng không dễ dàng và mạnh mẽ bởi vì viết một triển khai hủy bỏ là rất khó )
- C #
readonly
và Java final
không có gì hữu ích bằng C ++const
( Không có cách nào bạn có thể hiển thị dữ liệu phức tạp chỉ đọc (ví dụ như một cây mã) trong C # mà không cần công việc to lớn, trong khi đó là một tính năng tích hợp của C ++. Dữ liệu bất biến là một giải pháp thú vị , nhưng không phải mọi thứ đều có thể được biến thành bất biến, vì vậy nó thậm chí là không đủ, cho đến nay ).
Vì vậy, C # vẫn là một ngôn ngữ dễ chịu miễn là bạn muốn thứ gì đó hoạt động, nhưng là một ngôn ngữ khó chịu vào thời điểm bạn muốn thứ gì đó hoạt động luôn và an toàn .
Java thậm chí còn khó chịu hơn, vì nó có những vấn đề tương tự như C # và hơn thế nữa: Thiếu using
từ khóa tương đương của C # , một đồng nghiệp rất giỏi của tôi đã dành quá nhiều thời gian để đảm bảo tài nguyên của nó được giải phóng chính xác, trong khi tương đương trong C ++ sẽ có dễ dàng (sử dụng hàm hủy và con trỏ thông minh).
Vì vậy, tôi đoán mức tăng năng suất của C # / Java có thể nhìn thấy đối với hầu hết các mã ... cho đến ngày bạn cần mã hoàn hảo nhất có thể. Ngày đó, bạn sẽ biết đau. (bạn sẽ không tin những gì được hỏi từ máy chủ và ứng dụng GUI của chúng tôi ...).
Giới thiệu về Java và C ++ phía máy chủ
Tôi giữ liên lạc với các nhóm máy chủ (tôi đã làm việc 2 năm trong số họ, trước khi trở lại nhóm GUI), ở phía bên kia của tòa nhà và tôi đã học được một điều thú vị.
Những năm trước, xu hướng là các ứng dụng máy chủ Java sẽ thay thế các ứng dụng máy chủ C ++ cũ, vì Java có rất nhiều khuôn khổ / công cụ và dễ bảo trì, triển khai, v.v.
... Cho đến khi vấn đề về độ trễ thấp nổi lên trong những tháng cuối cùng. Sau đó, các ứng dụng máy chủ Java, bất kể việc tối ưu hóa bởi nhóm Java lành nghề của chúng tôi, đơn giản và rõ ràng đã thua trong cuộc đua với máy chủ C ++ cũ, không thực sự được tối ưu hóa.
Hiện tại, quyết định là giữ các máy chủ Java để sử dụng chung ở những nơi mà hiệu suất vẫn quan trọng, không bị mục tiêu độ trễ thấp quan tâm và tích cực tối ưu hóa các ứng dụng máy chủ C ++ vốn đã nhanh hơn cho nhu cầu độ trễ thấp và cực thấp.
Phần kết luận
Không có gì là đơn giản như mong đợi.
Java, và thậm chí cả C #, là những ngôn ngữ tuyệt vời, với các thư viện và khuôn khổ tiêu chuẩn mở rộng, nơi bạn có thể viết mã nhanh và có kết quả rất sớm.
Nhưng khi bạn cần sức mạnh thô, tối ưu hóa mạnh mẽ và có hệ thống, hỗ trợ trình biên dịch mạnh mẽ, các tính năng ngôn ngữ mạnh mẽ và an toàn tuyệt đối, thì Java và C # khiến bạn khó giành được phần trăm chất lượng còn thiếu nhưng quan trọng cuối cùng mà bạn cần để duy trì trên các đối thủ.
Có vẻ như bạn cần ít thời gian hơn và các nhà phát triển ít kinh nghiệm hơn trong C # / Java so với C ++ để tạo ra mã chất lượng trung bình, nhưng mặt khác, thời điểm bạn cần xuất sắc để hoàn thiện mã chất lượng, việc lấy kết quả đột nhiên dễ dàng và nhanh chóng hơn. ngay trong C ++.
Tất nhiên, đây là nhận thức của riêng tôi, có lẽ giới hạn trong nhu cầu cụ thể của chúng tôi.
Tuy nhiên, đó là những gì xảy ra ngày hôm nay, cả trong nhóm GUI và nhóm phía máy chủ.
Tất nhiên, tôi sẽ cập nhật bài đăng này nếu có điều gì mới xảy ra.
Chỉnh sửa (2011-06-22)
"Chúng tôi thấy rằng liên quan đến hiệu suất, C ++ giành được lợi nhuận lớn. Tuy nhiên, nó cũng đòi hỏi những nỗ lực điều chỉnh sâu rộng nhất, nhiều nỗ lực trong số đó được thực hiện ở mức độ phức tạp mà lập trình viên bình thường không có được.
[...] Phiên bản Java có lẽ là đơn giản nhất để triển khai, nhưng khó phân tích hiệu suất nhất. Cụ thể là những tác động xung quanh việc thu gom rác rất phức tạp và rất khó điều chỉnh. "
Nguồn:
Chỉnh sửa (2011-09-20)
"Từ ngữ đang diễn ra ở Facebook là ' mã C ++ được viết hợp lý chỉ chạy nhanh ', điều này nhấn mạnh nỗ lực to lớn đã bỏ ra để tối ưu hóa mã PHP và Java. Nghịch lý là mã C ++ khó viết hơn các ngôn ngữ khác, nhưng mã hiệu quả là một [viết bằng C ++ dễ dàng hơn nhiều so với các ngôn ngữ khác]. "
- Herb Sutter tại // build / , trích dẫn Andrei Alexandrescu
Nguồn: