Hiệu suất C ++ so với Java / C #


119

Sự hiểu biết của tôi là C / C ++ tạo ra mã gốc để chạy trên một kiến ​​trúc máy cụ thể. Ngược lại, các ngôn ngữ như Java và C # chạy trên một máy ảo, máy ảo này sẽ loại bỏ kiến ​​trúc gốc. Về mặt logic, có vẻ như Java hoặc C # không thể phù hợp với tốc độ của C ++ vì bước trung gian này, tuy nhiên tôi đã được thông báo rằng các trình biên dịch mới nhất ("điểm nóng") có thể đạt được tốc độ này hoặc thậm chí vượt quá nó.

Có lẽ đây là một câu hỏi về trình biên dịch hơn là một câu hỏi về ngôn ngữ, nhưng có ai có thể giải thích bằng tiếng Anh đơn giản làm thế nào để một trong những ngôn ngữ máy ảo này có thể hoạt động tốt hơn ngôn ngữ mẹ đẻ không?


Java và C # có thể thực hiện tối ưu hóa dựa trên cách ứng dụng thực sự được chạy bằng cách sử dụng mã vì nó có sẵn trong thời gian chạy. ví dụ: nó có thể mã nội tuyến trong một thư viện được chia sẻ mà thực sự có thể thay đổi trong khi chương trình đang chạy và vẫn chính xác.
Peter Lawrey

Một số phép đo thực tế để kiểm tra trước khi đọc rất nhiều lý thuyết rất khó hiểu trong các câu trả lời sau: shootout.alioth.debian.org/u32/…
Justicle

Câu trả lời:


178

Nói chung, C # và Java có thể nhanh hơn hoặc nhanh hơn vì trình biên dịch JIT - trình biên dịch biên dịch IL của bạn lần đầu tiên được thực thi - có thể thực hiện các tối ưu hóa mà chương trình biên dịch C ++ không thể thực hiện được vì nó có thể truy vấn máy. Nó có thể xác định xem máy là Intel hay AMD; Pentium 4, Core Solo hoặc Core Duo; hoặc nếu hỗ trợ SSE4, v.v.

Một chương trình C ++ thường phải được biên dịch trước với các tối ưu hóa hỗn hợp để nó chạy tốt trên tất cả các máy, nhưng không được tối ưu hóa nhiều như có thể cho một cấu hình (tức là bộ xử lý, tập lệnh, phần cứng khác).

Ngoài ra, một số tính năng ngôn ngữ nhất định cho phép trình biên dịch trong C # và Java đưa ra các giả định về mã của bạn để cho phép nó tối ưu hóa một số phần nhất định mà trình biên dịch C / C ++ không an toàn. Khi bạn có quyền truy cập vào con trỏ, có rất nhiều cách tối ưu hóa không an toàn.

Ngoài ra Java và C # có thể thực hiện phân bổ heap hiệu quả hơn C ++ vì lớp trừu tượng giữa trình thu gom rác và mã của bạn cho phép nó thực hiện tất cả việc nén heap cùng một lúc (một hoạt động khá tốn kém).

Bây giờ tôi không thể nói cho Java về điểm tiếp theo này, nhưng tôi biết rằng C # chẳng hạn sẽ thực sự loại bỏ các phương thức và lời gọi phương thức khi nó biết phần thân của phương thức là trống. Và nó sẽ sử dụng loại logic này trong suốt mã của bạn.

Vì vậy, như bạn có thể thấy, có rất nhiều lý do khiến việc triển khai C # hoặc Java nhất định sẽ nhanh hơn.

Điều này đã nói lên tất cả, các tối ưu hóa cụ thể có thể được thực hiện trong C ++ sẽ thổi bay mọi thứ bạn có thể làm với C #, đặc biệt là trong lĩnh vực đồ họa và bất cứ lúc nào bạn gần với phần cứng. Con trỏ làm điều kỳ diệu ở đây.

Vì vậy, tùy thuộc vào những gì bạn đang viết, tôi sẽ đi với cái này hay cái kia. Nhưng nếu bạn đang viết thứ gì đó không phụ thuộc vào phần cứng (trình điều khiển, trò chơi video, v.v.), tôi sẽ không lo lắng về hiệu suất của C # (một lần nữa không thể nói về Java). Nó sẽ làm tốt.

Về phía Java, @Swati chỉ ra một bài báo hay:

https://www.ibm.com/developerworks/library/j-jtp09275


Lý do của bạn là không có thật - các chương trình C ++ được xây dựng cho kiến ​​trúc mục tiêu của chúng, chúng không cần phải chuyển đổi trong thời gian chạy.
Justicle

3
@Justicle Trình biên dịch c ++ tốt nhất của bạn sẽ cung cấp cho các kiến ​​trúc khác nhau thường là x86, x64, ARM và những thứ khác. Bây giờ bạn có thể yêu cầu nó sử dụng các tính năng cụ thể (giả sử SSE2) và nếu bạn may mắn, nó thậm chí sẽ tạo một số mã dự phòng nếu tính năng đó không khả dụng, nhưng đó là mức chi tiết mà người ta có thể nhận được. Chắc chắn không có chuyên môn hóa tùy thuộc vào kích thước bộ nhớ cache và những gì không.
Voo

4
Xem shootout.alioth.debian.org/u32/… để biết các ví dụ về lý thuyết này không xảy ra.
Justicle

1
Thành thật mà nói, đây là một trong những câu trả lời tệ nhất. Nó rất vô căn cứ, tôi chỉ có thể đảo ngược nó. Quá nhiều khái quát hóa, quá nhiều ẩn số (tối ưu hóa các chức năng trống rỗng thực sự chỉ là phần nổi của tảng băng chìm). Một trình biên dịch C ++ sang trọng có: Thời gian. Xa xỉ khác: Không có kiểm tra được thực thi. Nhưng hãy tìm thêm trong stackoverflow.com/questions/145110/c-performance-vs-java-c/… .
Sebastian Mach

1
@OrionAdrian được rồi, bây giờ chúng ta đã đủ vòng tròn ... Hãy xem shootout.alioth.debian.org/u32/… để biết các ví dụ về lý thuyết này không xảy ra. Nói cách khác, hãy cho chúng tôi thấy rằng lý thuyết của bạn có thể được chứng minh là đúng trước khi đưa ra những tuyên bố đầu cơ mơ hồ.
Justicle

197

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ó finallymệ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:

  1. 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 đề )
  2. 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 # usingcũ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ó )
  3. C # readonlyvà Java finalkhô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 usingtừ 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:


8
Bạn chỉnh sửa sau 5 tháng C # mô tả chính xác trải nghiệm của riêng tôi (các mẫu tốt hơn, const tốt hơn, RAII). +1. Ba tính năng đó vẫn là những tính năng giết người của cá nhân tôi cho C ++ (hoặc D, mà tôi chưa có thời gian).
Sebastian Mach

"Việc xử lý mã sẽ được thực hiện tại thời điểm biên dịch". Do đó, lập trình siêu mẫu chỉ hoạt động trong chương trình có sẵn tại thời điểm biên dịch, điều này thường không xảy ra, ví dụ: không thể viết thư viện biểu thức chính quy hoạt động cạnh tranh trong vanilla C ++ vì nó không có khả năng tạo mã thời gian chạy (một khía cạnh quan trọng của siêu lập trình).
JD

"chơi với các kiểu được thực hiện tại thời điểm biên dịch ... tương đương trong Java hoặc C # là tốt nhất để 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 kiểu được biết tại thời điểm biên dịch". Trong C #, điều đó chỉ đúng với kiểu tham chiếu và không đúng với kiểu giá trị.
JD

1
"Cho dù có tối ưu hóa JIT đi chăng nữa, thì sẽ không có gì nhanh bằng việc truy cập trực tiếp con trỏ vào bộ nhớ ... 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 của nó) sẽ mất thời gian nhanh hơn trong Java / C # ". Mọi người đã quan sát thấy Java đánh bại C ++ trong bài kiểm tra SOR từ điểm chuẩn SciMark2 chính xác vì con trỏ cản trở việc tối ưu hóa liên quan đến răng cưa. blogs.oracle.com/dagastine/entry/sun_java_is_faster_than
JD

Cũng cần lưu ý rằng .NET thực hiện loại đặc biệt hóa chung cho các thư viện được liên kết động sau khi liên kết trong khi C ++ không thể vì các mẫu phải được giải quyết trước khi liên kết. Và rõ ràng lợi thế lớn nhất của generics so với các mẫu là các thông báo lỗi có thể hiểu được.
JD

48

Bất cứ khi nào tôi nói về hiệu suất được quản lý và không được quản lý, tôi muốn chỉ vào loạt bài Rico (và Raymond) đã so sánh các phiên bản C ++ và C # của từ điển tiếng Trung / tiếng Anh. Đây tìm kiếm google sẽ cho phép bạn đọc cho chính mình, nhưng tôi giống như bản tóm tắt của Rico.

Vậy tôi có xấu hổ vì thất bại tan nát của mình không? Khó khăn. Mã được quản lý có kết quả rất tốt mà hầu như không có bất kỳ nỗ lực nào. Để đánh bại Raymond quản lý phải:

  • Viết nội dung I / O tệp của riêng anh ấy
  • Viết lớp chuỗi của riêng anh ấy
  • Viết trình phân bổ của riêng anh ấy
  • Viết bản đồ quốc tế của riêng anh ấy

Tất nhiên, anh ấy đã sử dụng các thư viện cấp thấp hơn có sẵn để làm việc này, nhưng đó vẫn còn rất nhiều việc. Bạn có thể gọi những gì còn lại là một chương trình STL? Tôi không nghĩ vậy, tôi nghĩ anh ấy giữ lớp std :: vector mà cuối cùng không bao giờ là một vấn đề và anh ấy đã giữ hàm find. Khá nhiều thứ khác đã biến mất.

Vì vậy, bạn chắc chắn có thể đánh bại CLR. Tôi nghĩ Raymond có thể làm cho chương trình của anh ấy đi nhanh hơn.

Điều thú vị là thời gian để phân tích cú pháp tệp như được báo cáo bởi cả hai chương trình hẹn giờ nội bộ là khoảng như nhau - 30ms cho mỗi chương trình. Sự khác biệt là ở chi phí.

Đối với tôi điểm mấu chốt là phải mất 6 lần sửa đổi cho phiên bản không được quản lý để đánh bại phiên bản được quản lý vốn là một cổng đơn giản của mã không được quản lý ban đầu. Nếu bạn cần từng chút hiệu suất cuối cùng (và có thời gian và chuyên môn để đạt được nó), bạn sẽ phải không được quản lý, nhưng đối với tôi, tôi sẽ lấy thứ tự lợi thế về cường độ mà tôi có trên các phiên bản đầu tiên trên 33 % Tôi đạt được nếu tôi cố gắng 6 lần.


3
liên kết là chết, tìm thấy bài viết đề cập ở đây: blogs.msdn.com/b/ricom/archive/2005/05/10/416151.aspx
gjvdkamp

Trước hết, nếu chúng ta nhìn vào đoạn mã của Raymond Chen, rõ ràng anh ấy không hiểu lắm về C ++ hoặc cấu trúc dữ liệu. Mã của anh ấy gần như tiếp cận thẳng với mã C cấp thấp ngay cả trong trường hợp mã C không có lợi ích về hiệu suất (nó có vẻ như là một loại không tin tưởng và có thể là thiếu kiến ​​thức về cách sử dụng trình định hình). Anh ấy cũng không hiểu được cách triển khai từ điển theo thuật toán hợp lý nhất (anh ấy đã sử dụng std :: find vì Chúa). Nếu có điều gì đó tốt về Java, Python, C #, vv - tất cả họ đều cung cấp từ điển rất hiệu quả ...
stinky472

Tries hoặc thậm chí std :: map sẽ có lợi hơn nhiều đối với C ++ hoặc thậm chí là một bảng băm. Cuối cùng, từ điển chính xác là loại chương trình được hưởng lợi nhiều nhất từ ​​các thư viện và khuôn khổ cấp cao. Nó không thể hiện sự khác biệt trong ngôn ngữ quá nhiều so với các thư viện liên quan (trong đó, tôi vui vẻ nói rằng C # hoàn thiện hơn nhiều và cung cấp nhiều công cụ hơn phù hợp với nhiệm vụ). Hiển thị một chương trình sử dụng các khối bộ nhớ lớn để so sánh, giống như một ma trận / mã vectơ quy mô lớn. Điều đó sẽ giải quyết này khá nhanh chóng ngay cả khi, như trong trường hợp này, các lập trình viên không biết gì ...
stinky472

26

Việc biên dịch để tối ưu hóa CPU cụ thể thường được đánh giá quá cao. Chỉ cần lấy một chương trình trong C ++ và biên dịch với tối ưu hóa cho pentium PRO và chạy trên pentium 4. Sau đó biên dịch lại với tối ưu hóa cho pentium 4. Tôi đã trải qua những buổi chiều dài làm việc đó với một số chương trình. Kết quả chung ?? Thông thường hiệu suất tăng ít hơn 2-3%. Vì vậy, các lợi thế JIT trên lý thuyết hầu như không có. Hầu hết các khác biệt về hiệu suất chỉ có thể được quan sát thấy khi sử dụng các tính năng xử lý dữ liệu vô hướng, một điều gì đó cuối cùng sẽ cần đến sự tinh chỉnh thủ công để đạt được hiệu suất tối đa. Những tối ưu hóa kiểu đó rất chậm và tốn kém để thực hiện, khiến chúng đôi khi không phù hợp với JIT.

Trên thế giới thực và ứng dụng thực, C ++ vẫn thường nhanh hơn java, chủ yếu là do dung lượng bộ nhớ nhẹ hơn dẫn đến hiệu suất bộ nhớ cache tốt hơn.

Nhưng để sử dụng hết khả năng của C ++, nhà phát triển phải làm việc chăm chỉ. Bạn có thể đạt được kết quả vượt trội, nhưng bạn phải sử dụng bộ não của mình cho điều đó. C ++ là ngôn ngữ quyết định mang đến cho bạn nhiều công cụ hơn, đồng thời bạn phải trả giá là bạn phải học chúng để có thể sử dụng ngôn ngữ tốt.


4
Việc bạn biên dịch để tối ưu hóa CPU không quá nhiều, nhưng bạn đang biên dịch để tối ưu hóa đường dẫn thời gian chạy. Nếu bạn thấy rằng một phương thức rất thường được gọi với một tham số cụ thể, bạn có thể biên dịch trước quy trình đó với tham số đó dưới dạng một hằng số có thể (trong trường hợp boolean điều khiển luồng) tạo ra những khối công việc khổng lồ. C ++ không thể tiến gần đến việc tối ưu hóa như vậy.
Bill K

1
Vậy các JIT thực hiện như thế nào trong việc biên dịch lại các quy trình để tận dụng các đường chạy đã quan sát được và điều đó tạo ra sự khác biệt như thế nào?
David Thornley

2
@Bill Tôi có thể đang trộn hai thứ ... nhưng dự đoán nhánh không được thực hiện tại thời điểm chạy trong đường dẫn hướng dẫn đạt được các mục tiêu tương tự độc lập với ngôn ngữ?
Hardy

@Hardy vâng, CPU có thể thực hiện dự đoán rẽ nhánh bất kể ngôn ngữ, nhưng nó không thể tính toàn bộ vòng lặp bằng cách quan sát rằng vòng lặp không ảnh hưởng đến bất cứ điều gì. Nó cũng sẽ không quan sát thấy rằng mult (0) khó trả về 0 và chỉ cần thay thế toàn bộ lời gọi phương thức bằng if (param == 0) result = 0; và tránh toàn bộ cuộc gọi hàm / phương thức. C có thể làm những điều này nếu trình biên dịch có một cái nhìn tổng quan toàn diện về những gì đang xảy ra, nhưng nói chung nó không có đủ thông tin tại thời điểm biên dịch.
Bill K

21

JIT (Just In Time Compiling) có thể cực kỳ nhanh vì nó tối ưu hóa cho nền tảng mục tiêu.

Điều này có nghĩa là nó có thể tận dụng mọi thủ thuật trình biên dịch mà CPU của bạn có thể hỗ trợ, bất kể CPU mà nhà phát triển đã viết mã trên.

Khái niệm cơ bản của .NET JIT hoạt động như thế này (được đơn giản hóa rất nhiều):

Gọi một phương thức lần đầu tiên:

  • Mã chương trình của bạn gọi một phương thức Foo ()
  • CLR xem xét kiểu triển khai Foo () và lấy siêu dữ liệu được liên kết với nó
  • Từ siêu dữ liệu, CLR biết IL (Mã byte trung gian) được lưu trong địa chỉ bộ nhớ nào.
  • CLR cấp phát một khối bộ nhớ và gọi JIT.
  • JIT biên dịch IL thành mã gốc, đặt nó vào bộ nhớ được cấp phát, sau đó thay đổi con trỏ hàm trong siêu dữ liệu kiểu của Foo () để trỏ đến mã gốc này.
  • Mã gốc được chạy.

Gọi một phương thức lần thứ hai:

  • Mã chương trình của bạn gọi một phương thức Foo ()
  • CLR xem xét kiểu triển khai Foo () và tìm con trỏ hàm trong siêu dữ liệu.
  • Mã gốc tại vị trí bộ nhớ này được chạy.

Như bạn có thể thấy, lần thứ hai, quy trình của nó gần như giống với C ++, ngoại trừ ưu điểm là tối ưu hóa thời gian thực.

Điều đó nói rằng, vẫn còn những vấn đề khác làm chậm ngôn ngữ được quản lý, nhưng JIT sẽ giúp ích rất nhiều.


Nhân tiện Jonathan, tôi nghĩ rằng ai đó vẫn đang tán thành những thứ của bạn. Khi tôi bình chọn bạn, bạn đã có -1 cho bài đăng này.
Brian R. Bondy 28/09/08

12

Tôi thích câu trả lời của Orion Adrian , nhưng có một khía cạnh khác của nó.

Câu hỏi tương tự đã được đặt ra cách đây nhiều thập kỷ về hợp ngữ so với các ngôn ngữ "con người" như FORTRAN. Và một phần của câu trả lời là tương tự.

Có, một chương trình C ++ có khả năng nhanh hơn C # trên bất kỳ thuật toán nào (không tầm thường?) Nhất định, nhưng chương trình trong C # thường sẽ nhanh hơn hoặc nhanh hơn một cách triển khai "ngây thơ" trong C ++ và một phiên bản được tối ưu hóa trong C ++ sẽ mất nhiều thời gian hơn để phát triển và vẫn có thể đánh bại phiên bản C # với một biên độ rất nhỏ. Vì vậy, nó có thực sự xứng đáng?

Bạn sẽ phải trả lời câu hỏi đó trên cơ sở từng người một.

Điều đó nói lên rằng, tôi là một fan hâm mộ lâu năm của C ++ và tôi nghĩ rằng đó là một ngôn ngữ vô cùng biểu cảm và mạnh mẽ - đôi khi không được đánh giá cao. Nhưng trong nhiều vấn đề "thực tế cuộc sống" (đối với cá nhân tôi, điều đó có nghĩa là "loại tôi được trả tiền để giải quyết"), C # sẽ hoàn thành công việc sớm hơn và an toàn hơn.

Hình phạt lớn nhất bạn phải trả? Nhiều chương trình .NET và Java là những ổ chứa bộ nhớ. Tôi đã thấy các ứng dụng .NET và Java chiếm "hàng trăm MB" bộ nhớ, khi các chương trình C ++ có độ phức tạp tương tự hầu như không làm trầy xước "hàng chục" MB.


7

Tôi không chắc bạn sẽ thấy rằng mã Java sẽ chạy nhanh hơn C ++, ngay cả với Hotspot bao lâu một lần, nhưng tôi sẽ giải thích cách nó có thể xảy ra.

Hãy coi mã Java đã biên dịch là ngôn ngữ máy được thông dịch cho JVM. Khi bộ xử lý Hotspot nhận thấy rằng một số đoạn mã đã biên dịch sẽ được sử dụng nhiều lần, nó sẽ thực hiện tối ưu hóa mã máy. Vì Assembly điều chỉnh thủ công hầu như luôn nhanh hơn mã được biên dịch C ++, nên có thể thấy rằng mã máy được điều chỉnh theo chương trình sẽ không quá tệ.

Vì vậy, đối với mã lặp đi lặp lại nhiều, tôi có thể thấy nơi Hotspot JVM có thể chạy Java nhanh hơn C ++ ... cho đến khi bộ sưu tập rác hoạt động. :)


Bạn có thể mở rộng về khẳng định Since hand-tuning Assembly is almost always faster than C++ compiled code? Ý bạn là gì khi nói "hội đồng chỉnh tay" và "mã biên dịch C ++"?
paercebal

Vâng, nó dựa trên ý tưởng rằng trình tối ưu hóa của trình biên dịch tuân theo các quy tắc, còn người viết mã thì không. Vì vậy, sẽ luôn có những đoạn mã mà trình tối ưu hóa thấy rằng nó không thể tối ưu hóa một cách hoàn hảo, trong khi con người có thể bằng cách xem một bức tranh lớn hơn hoặc biết thêm về những gì mã thực sự đang làm. Tôi sẽ nói thêm rằng đây là một nhận xét đã 3 năm tuổi và tôi biết nhiều hơn về HotSpot so với trước đây và tôi có thể dễ dàng thấy tối ưu hóa động là một cách RẤT hay để mã chạy nhanh hơn.
billjamesdev

1. Tối ưu hóa từ Hotspot hoặc bất kỳ JIT nào khác vẫn là tối ưu hóa trình biên dịch. JIT có lợi thế hơn trình biên dịch tĩnh là có thể nội dòng một số kết quả (mã thường được gọi), hoặc thậm chí thực hiện tối ưu hóa dựa trên bộ xử lý đang thực thi, nhưng nó vẫn là tối ưu hóa trình biên dịch. . . 2. Tôi đoán bạn đang nói về tối ưu hóa thuật toán, không phải "điều chỉnh tốt lắp ráp". "việc tinh chỉnh lắp ráp thủ công bởi người viết mã" đã không thể tạo ra kết quả tốt hơn so với tối ưu hóa trình biên dịch kể từ hơn một thập kỷ. Trong thực tế, một sân chơi của con người với lắp ráp thường vít đi bất kỳ tối ưu hóa ...
paercebal

Được rồi, tôi hiểu rằng tôi đang sử dụng sai thuật ngữ, "tối ưu hóa trình biên dịch" chứ không phải "tối ưu hóa tĩnh". Tôi sẽ chỉ ra rằng, ít nhất là trong ngành công nghiệp trò chơi, như gần đây đối với PS2, chúng tôi vẫn đang sử dụng lắp ráp mã hóa thủ công ở những nơi để "tối ưu hóa" cho các chip cụ thể mà chúng tôi biết là trên bảng điều khiển; trình biên dịch chéo cho các chip mới này chưa được tinh vi như trình biên dịch cho các kiến ​​trúc x86. Quay lại câu hỏi ban đầu trên: JIT có những lợi ích của việc có thể để đo lường trước khi tối ưu hóa, đó là một điều tốt (TM)
billjamesdev

Lưu ý rằng hầu hết các GC sản xuất cũng sử dụng trình hợp dịch viết tay vì C / C ++ không cắt nó.
JD

6

Nói chung, thuật toán của chương trình của bạn sẽ quan trọng hơn nhiều đối với tốc độ ứng dụng của bạn hơn là ngôn ngữ . Bạn có thể triển khai một thuật toán kém bằng bất kỳ ngôn ngữ nào, kể cả C ++. Với ý nghĩ đó, bạn thường có thể viết mã chạy nhanh hơn bằng một ngôn ngữ giúp bạn triển khai một thuật toán hiệu quả hơn.

Các ngôn ngữ cấp cao hơn làm rất tốt điều này bằng cách cung cấp khả năng truy cập dễ dàng hơn vào nhiều cấu trúc dữ liệu được xây dựng trước hiệu quả và khuyến khích các phương pháp thực hành sẽ giúp bạn tránh mã không hiệu quả. Tất nhiên, đôi khi chúng cũng có thể khiến bạn dễ dàng viết một loạt mã thực sự chậm, vì vậy bạn vẫn phải biết nền tảng của mình.

Ngoài ra, C ++ đang bắt kịp với các tính năng "mới" (lưu ý dấu ngoặc kép) như vùng chứa STL, con trỏ tự động, v.v. - hãy xem thư viện tăng cường chẳng hạn. Và đôi khi bạn có thể thấy rằng cách nhanh nhất để hoàn thành một số nhiệm vụ yêu cầu một kỹ thuật như số học con trỏ bị cấm trong ngôn ngữ cấp cao hơn - mặc dù chúng theo kiểu chữ cho phép bạn gọi đến một thư viện được viết bằng ngôn ngữ có thể triển khai nó như mong muốn .

Điều chính là phải biết ngôn ngữ bạn đang sử dụng, API được liên kết của nó, những gì nó có thể làm và những hạn chế của nó.


5

Tôi cũng không biết nữa ... các chương trình Java của tôi luôn chậm. :-) Tuy nhiên, tôi chưa bao giờ thực sự nhận thấy các chương trình C # đặc biệt chậm.


4

Đây là một điểm chuẩn giao nhau khác, bạn có thể tự thử trên máy tính của mình.

Nó so sánh ASM, VC ++, C #, Silverlight, Java applet, Javascript, Flash (AS3)

Bản giới thiệu tốc độ plugin Roozz

Xin lưu ý rằng tốc độ của javascript varries phụ thuộc rất nhiều vào trình duyệt đang thực thi nó. Điều này cũng đúng với Flash và Silverlight vì các plugin này chạy trong cùng một quy trình với trình duyệt lưu trữ. Nhưng plugin Roozz chạy các tệp .exe tiêu chuẩn, chạy trong quy trình riêng của chúng, do đó tốc độ không bị ảnh hưởng bởi trình duyệt lưu trữ.


4

Bạn nên xác định "thực hiện tốt hơn ..". Tôi biết, bạn đã hỏi về tốc độ, nhưng nó không phải là tất cả mọi thứ.

  • Máy ảo có thực hiện nhiều chi phí thời gian chạy hơn không? Đúng!
  • Họ có ăn nhiều trí nhớ làm việc hơn không? Đúng!
  • Chúng có chi phí khởi động cao hơn (khởi tạo thời gian chạy và trình biên dịch JIT) không? Đúng!
  • Chúng có yêu cầu cài đặt một thư viện khổng lồ không? Đúng!

Và như vậy, thành kiến ​​của nó, vâng;)

Với C # và Java, bạn phải trả giá cho những gì bạn nhận được (mã hóa nhanh hơn, quản lý bộ nhớ tự động, thư viện lớn, v.v.). Nhưng bạn không có nhiều chỗ để mặc cả về các chi tiết: lấy gói hoàn chỉnh hoặc không có gì.

Ngay cả khi những ngôn ngữ đó có thể tối ưu hóa một số mã để thực thi nhanh hơn mã đã biên dịch, thì toàn bộ cách tiếp cận (IMHO) đều không hiệu quả. Hãy tưởng tượng lái xe mỗi ngày 5 dặm đến nơi làm việc của bạn, với một chiếc xe tải! Nó thoải mái, cảm giác tốt, bạn đang an toàn (vùng cực kỳ xung quanh) và sau khi bạn đạp ga một thời gian, nó thậm chí sẽ nhanh như một chiếc xe tiêu chuẩn! Tại sao tất cả chúng ta không có một chiếc xe tải để lái đi làm? ;)

Trong C ++, bạn nhận được những gì bạn phải trả, không nhiều hơn không ít hơn.

Trích dẫn Bjarne Stroustrup: "C ++ là rác ưa thích thu thập ngôn ngữ của tôi bởi vì nó tạo ra rất ít rác" liên kết văn bản


À, tôi nghĩ anh ấy có ý kiến ​​hay về nhược điểm của nó, anh ấy cũng nói: "C làm cho bạn dễ dàng tự bắn vào chân; C ++ làm cho nó khó hơn, nhưng khi bạn làm điều đó, bạn sẽ mất cả chân";)
Frunsi

"Chúng có yêu cầu cài đặt một thư viện khổng lồ không" Java đang giải quyết vấn đề này với ghép hình dự án mà tôi tin tưởng.
toc777 10/03

"Trong C ++, bạn nhận được những gì bạn phải trả, không nhiều hơn không ít hơn". Ví dụ về bộ đếm: Tôi đã chuẩn hóa việc triển khai cây RB trong OCaml và C ++ (GNU GCC) sử dụng một ngoại lệ để nhảy xa khỏi đệ quy nếu một phần tử được thêm vào đã có mặt để sử dụng lại tập hợp hiện có. OCaml nhanh hơn tới 6 lần so với C ++ vì nó không trả tiền cho việc kiểm tra các trình hủy khi ngăn xếp chưa được gắn kết.
JD

3
@Jon: nhưng tại một thời điểm nào đó (sau này?) Dù sao thì nó cũng phải hủy các đối tượng (ít nhất nó phải giải phóng bộ nhớ của nó). Và cũng lưu ý rằng các ngoại lệ dành cho các trường hợp ngoại lệ, ít nhất là trong C ++ quy tắc đó cần được tôn trọng. Các ngoại lệ trong C ++ có thể rất nặng khi các ngoại lệ xảy ra, đó là một sự đánh đổi.
Frunsi

@Jon: có thể thử lặp lại điểm chuẩn của bạn với timestrên shell. Vì vậy, nó kiểm tra toàn bộ chương trình, không chỉ một khía cạnh. Khi đó kết quả có tương tự không?
Frunsi

3

Mã thực thi được tạo ra từ trình biên dịch Java hoặc C # không được diễn giải - nó được biên dịch sang mã gốc "đúng lúc" (JIT). Vì vậy, lần đầu tiên mã trong chương trình Java / C # gặp phải trong quá trình thực thi, có một số chi phí do "trình biên dịch thời gian chạy" (hay còn gọi là trình biên dịch JIT) biến mã byte (Java) hoặc mã IL (C #) thành lệnh máy gốc. Tuy nhiên, lần sau khi gặp mã đó trong khi ứng dụng vẫn đang chạy, mã gốc sẽ được thực thi ngay lập tức. Điều này giải thích cách một số chương trình Java / C # ban đầu có vẻ chậm, nhưng sau đó hoạt động tốt hơn khi chúng chạy lâu hơn. Một ví dụ điển hình là trang web ASP.Net. Lần đầu tiên truy cập trang web, có thể chậm hơn một chút vì mã C # được trình biên dịch JIT biên dịch thành mã gốc.


3

Một số câu trả lời tốt ở đây về câu hỏi cụ thể bạn đã hỏi. Tôi muốn lùi lại và nhìn vào bức tranh toàn cảnh hơn.

Hãy nhớ rằng nhận thức của người dùng về tốc độ của phần mềm bạn viết bị ảnh hưởng bởi nhiều yếu tố khác chứ không chỉ mức độ tối ưu hóa của codegen. Dưới đây là một số ví dụ:

  • Việc quản lý bộ nhớ thủ công rất khó thực hiện một cách chính xác (không bị rò rỉ) và thậm chí còn khó thực hiện một cách hiệu quả (giải phóng bộ nhớ ngay sau khi bạn làm xong). Nói chung, sử dụng GC có nhiều khả năng tạo ra một chương trình quản lý bộ nhớ tốt hơn. Bạn có sẵn sàng làm việc chăm chỉ và trì hoãn việc cung cấp phần mềm của mình để cố gắng vượt qua GC không?

  • C # của tôi dễ đọc và dễ hiểu hơn C ++ của tôi. Tôi cũng có nhiều cách hơn để thuyết phục bản thân rằng mã C # của tôi đang hoạt động chính xác. Điều đó có nghĩa là tôi có thể tối ưu hóa các thuật toán của mình với ít nguy cơ phát sinh lỗi hơn (và người dùng không thích phần mềm bị treo, ngay cả khi nó xử lý nhanh chóng!)

  • Tôi có thể tạo phần mềm của mình trong C # nhanh hơn trong C ++. Điều đó giúp giải phóng thời gian để làm việc về hiệu suất và vẫn cung cấp phần mềm của tôi đúng hạn.

  • Viết giao diện người dùng tốt trong C # dễ dàng hơn C ++, vì vậy tôi có nhiều khả năng có thể đẩy công việc xuống nền trong khi giao diện người dùng vẫn đáp ứng hoặc cung cấp tiến trình hoặc giao diện người dùng tốt khi chương trình phải chặn một lúc. Điều này không làm cho bất cứ điều gì nhanh hơn, nhưng nó khiến người dùng hài lòng hơn khi chờ đợi.

Tất cả những gì tôi nói về C # có lẽ đúng với Java, chỉ là tôi không có kinh nghiệm để nói chắc chắn.


3

Nếu bạn là một lập trình viên Java / C # học C ++, bạn sẽ bị cám dỗ để tiếp tục suy nghĩ về Java / C # và dịch nguyên văn sang cú pháp C ++. Trong trường hợp đó, bạn chỉ nhận được những lợi ích đã đề cập trước đó của mã gốc so với được thông dịch / JIT. Để đạt được hiệu suất cao nhất trong C ++ so với Java / C #, bạn phải học cách suy nghĩ trong C ++ và thiết kế mã cụ thể để khai thác điểm mạnh của C ++.

Để diễn giải Edsger Dijkstra : [ngôn ngữ đầu tiên của bạn] khiến tâm trí không thể phục hồi được.
Để diễn giải Jeff Atwood : bạn có thể viết [ngôn ngữ đầu tiên của bạn] bằng bất kỳ ngôn ngữ mới nào.


1
Tôi nghi ngờ rằng câu nói "Bạn có thể viết FORTRAN bằng bất kỳ ngôn ngữ nào" đã có trước sự nghiệp của Jeff.
David Thornley

3

Một trong những cách tối ưu hóa JIT quan trọng nhất là nội tuyến phương thức. Java thậm chí có thể nội tuyến các phương thức ảo nếu nó có thể đảm bảo tính chính xác của thời gian chạy. Loại tối ưu hóa này thường không thể được thực hiện bởi các trình biên dịch tĩnh tiêu chuẩn vì nó cần phân tích toàn bộ chương trình, điều này rất khó vì biên dịch riêng biệt (ngược lại, JIT có tất cả chương trình có sẵn cho nó). Nội tuyến phương pháp cải thiện các tối ưu hóa khác, đưa ra các khối mã lớn hơn để tối ưu hóa.

Phân bổ bộ nhớ tiêu chuẩn trong Java / C # cũng nhanh hơn và phân bổ thỏa thuận (GC) không chậm hơn nhiều, nhưng chỉ ít xác định hơn.


Lưu ý rằng freedeletecũng không phải là xác định và GC có thể được xác định bằng cách không phân bổ.
JD

3

Các ngôn ngữ máy ảo không có khả năng hoạt động tốt hơn các ngôn ngữ đã biên dịch nhưng chúng có thể tiến gần đến mức không quan trọng, vì (ít nhất) các lý do sau (tôi đang nói về Java ở đây vì tôi chưa bao giờ học C #).

1 / Môi trường thời gian chạy Java thường có thể phát hiện các đoạn mã được chạy thường xuyên và thực hiện biên dịch kịp thời (JIT) của các đoạn đó để sau này, chúng chạy với tốc độ biên dịch đầy đủ.

2 / Các phần lớn của các thư viện Java được biên dịch để khi bạn gọi một hàm thư viện, bạn đang thực thi mã đã biên dịch chứ không phải thông dịch. Bạn có thể xem mã (bằng C) bằng cách tải xuống OpenJDK.

3 / Trừ khi bạn đang thực hiện các phép tính lớn, phần lớn thời gian chương trình của bạn đang chạy, nó đang chờ đợi đầu vào từ một con người (tương đối chậm).

4 / Vì rất nhiều việc xác thực mã bytecode của Java được thực hiện tại thời điểm tải lớp, chi phí kiểm tra thời gian chạy thông thường được giảm đáng kể.

5 / Trong trường hợp xấu nhất, mã chuyên sâu về hiệu suất có thể được trích xuất vào một mô-đun đã biên dịch và được gọi từ Java (xem JNI) để nó chạy ở tốc độ tối đa.

Tóm lại, mã bytecode của Java sẽ không bao giờ hoạt động tốt hơn ngôn ngữ máy gốc, nhưng có nhiều cách để giảm thiểu điều này. Ưu điểm lớn của Java (theo tôi thấy) là thư viện tiêu chuẩn KHỔNG LỒ và tính chất đa nền tảng.


1
Re item 2, "2 / Các phần lớn của thư viện Java được biên dịch để khi bạn gọi một hàm thư viện, bạn đang thực thi mã đã biên dịch, chứ không phải thông dịch": Bạn có trích dẫn cho điều đó không? Nếu nó thực sự như bạn mô tả, tôi hy vọng sẽ gặp phải mã gốc từ trình gỡ lỗi của mình rất nhiều, nhưng tôi không làm như vậy.
cero 28/09/08

Re: cero Trình gỡ lỗi thường sử dụng các đường dẫn kém hiệu quả hơn nhưng biểu cảm hơn, và do đó không phải là điểm đánh dấu tốt cho bất kỳ thứ gì liên quan đến hiệu suất.
Guvante 07/10/08

2
Có một hiệu suất lớn khác đối với thư viện HUGH này - mã thư viện có thể được viết tốt hơn những gì nhiều lập trình viên sẽ tự viết (có thời gian hạn chế và thiếu kiến ​​thức chuyên môn) và trên Java, vì nhiều lý do, các lập trình viên thường sử dụng thư viện.
Liran Orevi

3

Orion Adrian , hãy để tôi đảo ngược bài đăng của bạn để xem những nhận xét của bạn vô căn cứ như thế nào, bởi vì có thể nói rất nhiều điều về C ++. Và nói rằng trình biên dịch Java / C # tối ưu hóa các hàm trống thực sự khiến bạn có vẻ như bạn không phải là chuyên gia của tôi trong việc tối ưu hóa, bởi vì a) tại sao một chương trình thực lại phải chứa các hàm trống, ngoại trừ mã kế thừa thực sự xấu, b) điều đó thực sự không tối ưu hóa cạnh đen và chảy máu.

Ngoài cụm từ đó, bạn đã nói thẳng về con trỏ, nhưng không phải các đối tượng trong Java và C # về cơ bản hoạt động giống như con trỏ C ++? Có thể chúng không chồng lên nhau? Có thể chúng không vô hiệu? C (và hầu hết các triển khai C ++) có từ khóa hạn chế, cả hai đều có kiểu giá trị, C ++ có tham chiếu đến giá trị với đảm bảo không rỗng. Java và C # cung cấp những gì?

>>>>>>>>>>

Nói chung, C và C ++ có thể nhanh hoặc nhanh hơn vì trình biên dịch AOT - trình biên dịch biên dịch mã của bạn trước khi triển khai, một lần và mãi mãi, trên bộ nhớ cao của nhiều máy chủ xây dựng lõi - có thể thực hiện tối ưu hóa chương trình đã biên dịch C # không thể bởi vì nó có rất nhiều thời gian để làm như vậy. Trình biên dịch có thể xác định xem máy là Intel hay AMD; Pentium 4, Core Solo hoặc Core Duo; hoặc nếu hỗ trợ SSE4, v.v. và nếu trình biên dịch của bạn không hỗ trợ điều phối thời gian chạy, bạn có thể tự giải quyết vấn đề đó bằng cách triển khai một số mã nhị phân chuyên biệt.

Chương trình AC # thường được biên dịch khi chạy nó để nó chạy tốt trên tất cả các máy, nhưng không được tối ưu hóa nhiều như nó có thể cho một cấu hình đơn lẻ (tức là bộ xử lý, tập lệnh, phần cứng khác) và nó phải mất một khoảng thời gian Đầu tiên. Các tính năng như phân hạch vòng lặp, đảo ngược vòng lặp, vector hóa tự động, tối ưu hóa toàn bộ chương trình, mở rộng mẫu, IPO và nhiều tính năng khác, rất khó được giải quyết tất cả và hoàn toàn theo cách không làm phiền người dùng cuối.

Ngoài ra, một số tính năng ngôn ngữ nhất định cho phép trình biên dịch trong C ++ hoặc C đưa ra các giả định về mã của bạn để cho phép nó tối ưu hóa một số phần nhất định mà trình biên dịch Java / C # không an toàn. Khi bạn không có quyền truy cập vào id loại đầy đủ của generic hoặc quy trình chương trình được đảm bảo, sẽ có rất nhiều cách tối ưu hóa không an toàn.

Ngoài ra C ++ và C thực hiện nhiều phân bổ ngăn xếp cùng một lúc chỉ với một lần tăng thanh ghi, điều này chắc chắn hiệu quả hơn so với cấp phát Javas và C # đối với lớp trừu tượng giữa bộ thu gom rác và mã của bạn.

Bây giờ tôi không thể nói cho Java về điểm tiếp theo này, nhưng tôi biết rằng các trình biên dịch C ++ chẳng hạn sẽ thực sự loại bỏ các phương thức và lời gọi phương thức khi nó biết phần thân của phương thức là trống, nó sẽ loại bỏ các biểu thức con phổ biến, nó có thể thử và thử lại để tìm cách sử dụng thanh ghi tối ưu, nó không thực thi kiểm tra giới hạn, nó sẽ tự động hóa các vòng lặp và các vòng lặp bên trong và sẽ đảo ngược từ trong ra ngoài, nó di chuyển các điều kiện ra khỏi các vòng, nó tách và tách các vòng. Nó sẽ mở rộng std :: vector thành các mảng trên không gốc 0 như cách bạn làm theo cách C. Nó sẽ tối ưu hóa liên thủ tục. Nó sẽ xây dựng các giá trị trả về trực tiếp tại trang web của người gọi. Nó sẽ gấp và truyền các biểu thức. Nó sẽ sắp xếp lại dữ liệu theo cách thân thiện với bộ nhớ cache. Nó sẽ thực hiện nhảy luồng. Nó cho phép bạn viết biên dịch theo dõi tia thời gian với chi phí thời gian chạy bằng không. Nó sẽ thực hiện tối ưu hóa dựa trên đồ thị rất tốn kém. Nó sẽ làm giảm sức mạnh, nếu nó thay thế một số mã nhất định bằng mã hoàn toàn không bình đẳng về mặt cú pháp nhưng tương đương về mặt ngữ nghĩa ("xor foo, foo" cũ chỉ là cách tối ưu hóa đơn giản nhất, mặc dù lỗi thời của loại như vậy). Nếu bạn vui lòng hỏi nó, bạn có thể bỏ qua các tiêu chuẩn dấu phẩy động IEEE và kích hoạt nhiều tối ưu hóa hơn như sắp xếp lại toán hạng dấu chấm động. Sau khi nó đã xoa bóp và tàn sát mã của bạn, nó có thể lặp lại toàn bộ quá trình, bởi vì thông thường, một số tối ưu hóa nhất định đặt nền tảng cho các tối ưu hóa chắc chắn. Nó cũng có thể chỉ thử lại với các thông số xáo trộn và xem điểm số của biến thể khác trong xếp hạng nội bộ của nó như thế nào. Và nó sẽ sử dụng loại logic này trong suốt mã của bạn. nó thay thế một số mã nhất định bằng mã hoàn toàn không bình đẳng về mặt cú pháp nhưng tương đương về mặt ngữ nghĩa ("xor foo, foo" cũ chỉ là cách tối ưu hóa đơn giản nhất, mặc dù lỗi thời của loại như vậy). Nếu bạn vui lòng hỏi nó, bạn có thể bỏ qua các tiêu chuẩn dấu phẩy động IEEE và kích hoạt nhiều tối ưu hóa hơn như sắp xếp lại toán hạng dấu chấm động. Sau khi nó đã xoa bóp và tàn sát mã của bạn, nó có thể lặp lại toàn bộ quá trình, bởi vì thông thường, một số tối ưu hóa nhất định đặt nền tảng cho các tối ưu hóa chắc chắn. Nó cũng có thể chỉ thử lại với các thông số xáo trộn và xem điểm số của biến thể khác trong xếp hạng nội bộ của nó như thế nào. Và nó sẽ sử dụng loại logic này trong suốt mã của bạn. nó thay thế một số mã nhất định bằng mã hoàn toàn không bình đẳng về mặt cú pháp nhưng tương đương về mặt ngữ nghĩa ("xor foo, foo" cũ chỉ là cách tối ưu hóa đơn giản nhất, mặc dù lỗi thời của loại như vậy). Nếu bạn vui lòng hỏi nó, bạn có thể bỏ qua các tiêu chuẩn dấu phẩy động IEEE và kích hoạt nhiều tối ưu hóa hơn như sắp xếp lại toán hạng dấu chấm động. Sau khi nó đã xoa bóp và tàn sát mã của bạn, nó có thể lặp lại toàn bộ quá trình, bởi vì thông thường, một số tối ưu hóa nhất định đặt nền tảng cho các tối ưu hóa chắc chắn. Nó cũng có thể chỉ thử lại với các thông số xáo trộn và xem điểm số của biến thể khác trong xếp hạng nội bộ của nó như thế nào. Và nó sẽ sử dụng loại logic này trong suốt mã của bạn. Nếu bạn vui lòng hỏi nó, bạn có thể bỏ qua các tiêu chuẩn dấu phẩy động IEEE và kích hoạt nhiều tối ưu hóa hơn như sắp xếp lại toán hạng dấu chấm động. Sau khi nó đã xoa bóp và tàn sát mã của bạn, nó có thể lặp lại toàn bộ quá trình, bởi vì thông thường, một số tối ưu hóa nhất định đặt nền tảng cho các tối ưu hóa chắc chắn. Nó cũng có thể chỉ thử lại với các thông số xáo trộn và xem điểm số của biến thể khác trong xếp hạng nội bộ của nó như thế nào. Và nó sẽ sử dụng loại logic này trong suốt mã của bạn. Nếu bạn vui lòng hỏi nó, bạn có thể bỏ qua các tiêu chuẩn dấu phẩy động IEEE và kích hoạt nhiều tối ưu hóa hơn như sắp xếp lại toán hạng dấu chấm động. Sau khi nó đã xoa bóp và tàn sát mã của bạn, nó có thể lặp lại toàn bộ quá trình, bởi vì thông thường, một số tối ưu hóa nhất định đặt nền tảng cho các tối ưu hóa chắc chắn. Nó cũng có thể chỉ thử lại với các thông số xáo trộn và xem điểm số của biến thể khác trong xếp hạng nội bộ của nó như thế nào. Và nó sẽ sử dụng loại logic này trong suốt mã của bạn. Nó cũng có thể chỉ thử lại với các thông số xáo trộn và xem điểm số của biến thể khác trong xếp hạng nội bộ của nó như thế nào. Và nó sẽ sử dụng loại logic này trong suốt mã của bạn. Nó cũng có thể chỉ thử lại với các thông số xáo trộn và xem điểm số của biến thể khác trong xếp hạng nội bộ của nó như thế nào. Và nó sẽ sử dụng loại logic này trong suốt mã của bạn.

Vì vậy, như bạn có thể thấy, có rất nhiều lý do tại sao việc triển khai C ++ hoặc C nhất định sẽ nhanh hơn.

Bây giờ tất cả điều này đã nói, nhiều tối ưu hóa có thể được thực hiện trong C ++ sẽ thổi bay bất cứ điều gì bạn có thể làm với C #, đặc biệt là trong lĩnh vực xử lý số, thời gian thực và gần kim loại, nhưng không chỉ ở đó. Bạn thậm chí không cần phải chạm vào một con trỏ duy nhất để đi một chặng đường dài.

Vì vậy, tùy thuộc vào những gì bạn đang viết, tôi sẽ đi với cái này hay cái kia. Nhưng nếu bạn đang viết thứ gì đó không phụ thuộc vào phần cứng (trình điều khiển, trò chơi video, v.v.), tôi sẽ không lo lắng về hiệu suất của C # (một lần nữa không thể nói về Java). Nó sẽ làm tốt.

<<<<<<<<<<

Nói chung, một số lập luận tổng quát nhất định nghe có vẻ thú vị trong các bài viết cụ thể, nhưng nhìn chung thì có vẻ không đáng tin cậy.

Dù sao đi nữa, để tạo hòa bình: AOT thật tuyệt, JIT cũng vậy . Câu trả lời đúng duy nhất có thể là: Nó phụ thuộc. Và những người thông minh thực sự biết rằng bạn có thể sử dụng tốt nhất của cả hai thế giới.


2

Nó chỉ xảy ra nếu trình thông dịch Java đang tạo ra mã máy thực sự được tối ưu hóa tốt hơn mã máy mà trình biên dịch của bạn đang tạo cho mã C ++ bạn đang viết, đến mức mã C ++ chậm hơn Java và chi phí thông dịch.

Tuy nhiên, tỷ lệ điều đó thực sự xảy ra là khá thấp - trừ khi có lẽ Java có một thư viện được viết rất tốt và bạn có thư viện C ++ được viết kém của riêng mình.


Tôi cũng tin rằng có một trọng lượng ngôn ngữ nhất định, khi làm việc ở cấp độ thấp hơn, ít trừu tượng hơn, bạn sẽ phát triển một chương trình nhanh hơn. Điều này không liên quan đến các điểm về bản thân việc thực thi bytecode.
Brian R. Bondy 28/09/08

2

Trên thực tế, C # không thực sự chạy trong một máy ảo như Java. IL được biên dịch thành hợp ngữ, hoàn toàn là mã bản địa và chạy với tốc độ tương tự như mã bản địa. Bạn có thể JIT trước một ứng dụng .NET loại bỏ hoàn toàn chi phí JIT và sau đó bạn đang chạy hoàn toàn mã gốc.

Sự chậm lại với .NET sẽ đến không phải vì mã .NET chậm hơn, mà vì nó thực hiện nhiều thứ hơn ở hậu trường để thực hiện những việc như thu gom rác, kiểm tra tài liệu tham khảo, lưu trữ khung ngăn xếp hoàn chỉnh, v.v. Điều này có thể khá mạnh mẽ và hữu ích khi xây dựng ứng dụng, nhưng cũng phải trả giá. Lưu ý rằng bạn cũng có thể làm tất cả những điều này trong chương trình C ++ (phần lớn chức năng .NET cốt lõi thực sự là mã .NET mà bạn có thể xem trong ROTOR). Tuy nhiên, nếu bạn viết tay cùng một chức năng, bạn có thể sẽ gặp phải một chương trình chậm hơn nhiều vì thời gian chạy .NET đã được tối ưu hóa và tinh chỉnh.

Điều đó nói lên rằng, một trong những điểm mạnh của mã được quản lý là nó có thể hoàn toàn có thể xác minh được. bạn có thể xác minh rằng mã sẽ không bao giờ truy cập vào bộ nhớ của quy trình khác hoặc thực hiện những việc không được lưu trữ trước khi bạn thực thi nó. Microsoft có một nguyên mẫu nghiên cứu về một hệ điều hành được quản lý hoàn toàn đã cho thấy một cách đáng ngạc nhiên rằng môi trường được quản lý 100% thực sự có thể hoạt động nhanh hơn đáng kể so với bất kỳ hệ điều hành hiện đại nào bằng cách tận dụng xác minh này để tắt các tính năng bảo mật không còn cần thiết bởi các chương trình được quản lý (chúng ta đang nói như 10x trong một số trường hợp). Đài SE có một tập rất hay nói về dự án này.


1

Trong một số trường hợp, mã được quản lý thực sự có thể nhanh hơn mã gốc. Ví dụ: các thuật toán thu gom rác "đánh dấu và quét" cho phép các môi trường như JRE hoặc CLR giải phóng một số lượng lớn các đối tượng tồn tại trong thời gian ngắn (thông thường) trong một lần chuyển, trong đó hầu hết các đối tượng đống C / C ++ được giải phóng một lúc- một thời gian.

Từ wikipedia :

Đối với nhiều mục đích thực tế, các thuật toán chuyên sâu về phân bổ / phân bổ giao dịch được triển khai bằng các ngôn ngữ được thu gom rác thực sự có thể nhanh hơn các thuật toán tương đương bằng cách sử dụng phân bổ đống thủ công. Một lý do chính cho điều này là bộ thu gom rác cho phép hệ thống thời gian chạy phân bổ các hoạt động phân bổ và phân bổ theo cách có lợi.

Điều đó nói lên rằng, tôi đã viết rất nhiều C # và rất nhiều C ++, và tôi đã chạy rất nhiều điểm chuẩn. Theo kinh nghiệm của tôi, C ++ nhanh hơn rất nhiều so với C #, theo hai cách: (1) nếu bạn lấy một số mã mà bạn đã viết bằng C #, hãy chuyển nó sang C ++, mã gốc có xu hướng nhanh hơn. Nhanh hơn bao nhiêu? Chà, nó thay đổi rất nhiều, nhưng không hiếm khi thấy tốc độ cải thiện 100%. (2) Trong một số trường hợp, thu gom rác thải có thể ồ ạt làm chậm một ứng dụng quản lý. .NET CLR thực hiện một công việc tồi tệ với đống lớn (ví dụ:> 2GB) và cuối cùng có thể dành nhiều thời gian trong GC - ngay cả trong các ứng dụng có ít - hoặc thậm chí không - đối tượng có tuổi thọ trung gian.

Tất nhiên, trong hầu hết các trường hợp mà tôi đã gặp, các ngôn ngữ được quản lý đủ nhanh, trong một khoảng thời gian dài, và sự cân bằng giữa việc bảo trì và mã hóa để có thêm hiệu suất của C ++ đơn giản là không tốt.


1
Vấn đề là đối với các quy trình chạy lâu, chẳng hạn như máy chủ web, bộ nhớ của bạn theo thời gian sẽ trở nên phân mảnh (trong một chương trình viết bằng C ++) đến mức bạn sẽ phải triển khai một cái gì đó giống như bộ sưu tập rác (hoặc khởi động lại thường xuyên, hãy xem IIS ).
Tony BenBrahim 28/09/08

3
Tôi đã không quan sát thấy điều đó trên các chương trình Unix lớn được dùng để chạy mãi mãi. Chúng có xu hướng được viết bằng C, điều này thậm chí còn tệ hơn trong việc quản lý bộ nhớ so với C ++.
David Thornley

Tất nhiên, câu hỏi đặt ra là liệu chúng ta đang so sánh việc triển khai một chương trình trong mã được quản lý với mã không được quản lý hay hiệu suất cao nhất trên lý thuyết của ngôn ngữ. Rõ ràng, mã không được quản lý luôn có thể nhanh nhất bằng cách được quản lý, vì trong trường hợp xấu nhất, bạn có thể viết một chương trình không được quản lý thực hiện chính xác những điều tương tự như mã được quản lý! Nhưng hầu hết các vấn đề về hiệu suất là thuật toán, không phải vi mô. Ngoài ra, bạn không tối ưu hóa mã được quản lý và không được quản lý theo cùng một cách, vì vậy "C ++ trong C #" thường sẽ không hoạt động tốt.
kyoryu

2
Trong C / C ++, bạn có thể phân bổ các đối tượng tồn tại ngắn hạn trên ngăn xếp và bạn thực hiện khi nó thích hợp. Trong mã được quản lý, bạn không thể , bạn không có lựa chọn. Ngoài ra, trong C / C ++, bạn có thể phân bổ danh sách các đối tượng trong các khu vực liền kề (Foo mới [100]), trong mã được quản lý thì bạn không thể. Vì vậy, so sánh của bạn không hợp lệ. Chà, sức mạnh của sự lựa chọn này đặt ra gánh nặng cho các nhà phát triển, nhưng bằng cách này, họ học cách biết thế giới họ đang sống (ký ức ......).
Frunsi

@frunsi: "trong C / C ++, bạn có thể phân bổ danh sách các đối tượng trong các khu vực liền kề (Foo mới [100]), trong mã được quản lý, bạn không thể". Điều đó là không chính xác. Các kiểu giá trị cục bộ được cấp phát ngăn xếp và bạn thậm chí có thể cấp phát ngăn xếp các mảng của chúng trong C #. Thậm chí có những hệ thống sản xuất được viết bằng C # hoàn toàn không có phân bổ ở trạng thái ổn định.
JD


1

Trên thực tế Sun's HotSpot JVM sử dụng thực thi "chế độ hỗn hợp". Nó diễn giải mã bytecode của phương thức cho đến khi nó xác định (thường thông qua bộ đếm của một số loại) rằng một khối mã cụ thể (phương thức, vòng lặp, khối try-catch, v.v.) sẽ được thực thi nhiều, sau đó JIT sẽ biên dịch nó. Thời gian cần thiết để JIT biên dịch một phương thức thường mất nhiều thời gian hơn so với việc phương thức được thông dịch nếu nó là một phương thức hiếm khi chạy. Hiệu suất thường cao hơn đối với "chế độ hỗn hợp" vì JVM không lãng phí thời gian Mã JITing hiếm khi chạy. C # và .NET không làm được điều này. .NET JITs mọi thứ thường lãng phí thời gian.


1

Đọc về HP Labs ' Dynamo , một trình thông dịch cho PA-8000 chạy trên PA-8000 và thường chạy các chương trình nhanh hơn so với những chương trình thường chạy. Sau đó, nó sẽ không có vẻ gì đáng ngạc nhiên cả!

Đừng nghĩ đó là một "bước trung gian" - chạy một chương trình bao gồm rất nhiều bước khác, bằng bất kỳ ngôn ngữ nào.

Nó thường đi xuống:

  • chương trình có các điểm nóng, vì vậy ngay cả khi bạn chạy chậm hơn 95% phần nội dung mã bạn phải chạy, bạn vẫn có thể cạnh tranh về hiệu suất nếu bạn nhanh hơn ở mức 5%

  • một HLL biết nhiều hơn về ý định của bạn so với một LLL như C / C ++ và do đó có thể tạo ra nhiều mã được tối ưu hóa hơn (OCaml thậm chí còn nhiều hơn thế và trong thực tế thường còn nhanh hơn)

  • trình biên dịch JIT có nhiều thông tin mà trình biên dịch tĩnh không có (ví dụ như dữ liệu thực tế mà bạn tình cờ có trong thời gian này)

  • trình biên dịch JIT có thể thực hiện tối ưu hóa tại thời điểm chạy mà các trình liên kết truyền thống không thực sự được phép thực hiện (như sắp xếp lại các nhánh để trường hợp phổ biến là phẳng hoặc các lệnh gọi thư viện nội tuyến)

Nhìn chung, C / C ++ là những ngôn ngữ khá tệ về hiệu suất: có tương đối ít thông tin về các loại dữ liệu của bạn, không có thông tin về dữ liệu của bạn và không có thời gian chạy động để cho phép tối ưu hóa thời gian chạy.


1

Bạn có thể gặp sự cố ngắn khi Java hoặc CLR nhanh hơn C ++, nhưng nhìn chung hiệu suất kém hơn đối với tuổi thọ của ứng dụng: hãy xem www.codeproject.com/KB/dotnet/RuntimePerformance.aspx để biết một số kết quả cho điều đó.



1

Sự hiểu biết của tôi là C / C ++ tạo ra mã gốc để chạy trên một kiến ​​trúc máy cụ thể. Ngược lại, các ngôn ngữ như Java và C # chạy trên một máy ảo, máy ảo này sẽ loại bỏ kiến ​​trúc gốc. Về mặt logic, có vẻ như Java hoặc C # không thể phù hợp với tốc độ của C ++ vì bước trung gian này, tuy nhiên tôi đã được thông báo rằng các trình biên dịch mới nhất ("điểm nóng") có thể đạt được tốc độ này hoặc thậm chí vượt quá nó.

Điều đó là phi logic. Việc sử dụng một đại diện trung gian không làm giảm hiệu suất. Ví dụ: llvm-gcc biên dịch C và C ++ thông qua LLVM IR (là một máy đăng ký vô hạn ảo) thành mã gốc và nó đạt được hiệu suất tuyệt vời (thường đánh bại GCC).

Có lẽ đây là một câu hỏi về trình biên dịch hơn là một câu hỏi về ngôn ngữ, nhưng có ai có thể giải thích bằng tiếng Anh đơn giản làm thế nào để một trong những ngôn ngữ máy ảo này có thể hoạt động tốt hơn ngôn ngữ mẹ đẻ không?

Dưới đây là một số ví dụ:

  • Máy ảo có trình biên dịch JIT tạo điều kiện thuận lợi cho việc tạo mã trong thời gian chạy (ví dụ như System.Reflection.Emittrên .NET) để bạn có thể biên dịch mã được tạo một cách nhanh chóng bằng các ngôn ngữ như C # và F # nhưng phải dùng đến cách viết trình thông dịch tương đối chậm bằng C hoặc C ++. Ví dụ: để triển khai các biểu thức chính quy.

  • Các phần của máy ảo (ví dụ như rào cản ghi và trình cấp phát) thường được viết bằng trình hợp dịch mã hóa thủ công vì C và C ++ không tạo mã đủ nhanh. Nếu một chương trình nhấn mạnh những phần này của hệ thống thì nó có thể hoạt động tốt hơn bất cứ thứ gì có thể được viết bằng C hoặc C ++.

  • Liên kết động của mã gốc yêu cầu tuân thủ ABI có thể cản trở hiệu suất và cản trở việc tối ưu hóa toàn bộ chương trình trong khi liên kết thường bị trì hoãn trên các máy ảo và có thể hưởng lợi từ việc tối ưu hóa toàn bộ chương trình (như các loại chung được sửa đổi của .NET).

Tôi cũng muốn giải quyết một số vấn đề với câu trả lời được tán thành cao của paercebal ở trên (bởi vì ai đó liên tục xóa nhận xét của tôi về câu trả lời của anh ấy) đưa ra một quan điểm phân cực phản hiệu quả:

Quá trình xử lý mã sẽ được thực hiện tại thời điểm biên dịch ...

Do đó, lập trình siêu mẫu theo mẫu chỉ hoạt động nếu chương trình có sẵn tại thời điểm biên dịch, điều này thường không xảy ra, ví dụ: không thể viết thư viện biểu thức chính quy hoạt động cạnh tranh trong vanilla C ++ vì nó không có khả năng tạo mã trong thời gian chạy (một khía cạnh quan trọng của siêu lập trình).

... chơi với các kiểu được thực hiện tại thời điểm biên dịch ... tương đương trong Java hoặc C # là tốt nhất để 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 đã biết các kiểu tại thời điểm biên dịch.

Trong C #, điều đó chỉ đúng với kiểu tham chiếu và không đúng với kiểu giá trị.

Không có vấn đề gì về tối ưu hóa JIT, sẽ không có gì nhanh chóng như truy cập trực tiếp con trỏ vào bộ nhớ ... nếu bạn có dữ liệu liền kề trong bộ nhớ, truy cập nó thông qua con trỏ C ++ (tức là con trỏ C ... Hãy cho Caesar của nó) sẽ nhanh hơn nhiều lần hơn trong Java / C #.

Mọi người đã quan sát thấy Java đánh bại C ++ trong bài kiểm tra SOR từ điểm chuẩn SciMark2 chính xác bởi vì con trỏ cản trở việc tối ưu hóa liên quan đến răng cưa.

Cũng cần lưu ý rằng .NET thực hiện loại đặc biệt hóa chung cho các thư viện được liên kết động sau khi liên kết trong khi C ++ không thể vì các mẫu phải được giải quyết trước khi liên kết. Và rõ ràng lợi thế lớn nhất của generics so với các mẫu là các thông báo lỗi có thể hiểu được.


0

Trên hết những gì một số người khác đã nói, theo hiểu biết của tôi, .NET và Java tốt hơn trong việc cấp phát bộ nhớ. Ví dụ: họ có thể thu gọn bộ nhớ vì nó bị phân mảnh trong khi C ++ thì không thể (nguyên bản, nhưng có thể nếu bạn đang sử dụng bộ thu gom rác thông minh).


Hoặc nếu bạn đang sử dụng trình phân bổ C ++ tốt hơn và / hoặc nhóm các đối tượng. Điều này khác xa với ma thuật, theo quan điểm C ++, và nó có thể kết thúc để "phân bổ đống" trở thành phân bổ ngăn xếp nhanh như vậy.
paercebal 28-08

Nếu bạn luôn phân bổ mọi thứ trên heap, thì .NET và Java thậm chí có thể hoạt động tốt hơn C / C ++. Nhưng bạn sẽ không làm điều này trong C / C ++.
Frunsi

0

Đối với bất kỳ thứ gì cần nhiều tốc độ, JVM chỉ gọi một triển khai C ++, vì vậy, câu hỏi đặt ra nhiều hơn về mức độ tốt của lib hơn là JVM tốt như thế nào đối với hầu hết những thứ liên quan đến hệ điều hành. Việc thu gom rác sẽ cắt giảm một nửa bộ nhớ của bạn, nhưng việc sử dụng một số tính năng STL và Boost lạ mắt hơn sẽ có tác dụng tương tự nhưng với khả năng lỗi cao gấp nhiều lần.

Nếu bạn chỉ đang sử dụng thư viện C ++ và rất nhiều tính năng cấp cao của nó trong một dự án lớn với nhiều lớp, bạn có thể sẽ chạy chậm hơn so với sử dụng JVM. Ngoại trừ nhiều lỗi dễ xảy ra hơn.

Tuy nhiên, lợi ích của C ++ là nó cho phép bạn tối ưu hóa bản thân, nếu không, bạn sẽ bị mắc kẹt với những gì trình biên dịch / jvm làm. Nếu bạn tạo vùng chứa của riêng mình, viết quản lý bộ nhớ của riêng bạn được căn chỉnh, sử dụng SIMD và thả vào lắp ráp ở đây và ở đó, bạn có thể tăng tốc ít nhất gấp 2 - 4 lần so với những gì hầu hết các trình biên dịch C ++ sẽ tự làm. Đối với một số hoạt động, 16x-32x. Đó là sử dụng các thuật toán tương tự, nếu bạn sử dụng các thuật toán tốt hơn và song song hóa, mức tăng có thể rất ấn tượng, đôi khi nhanh hơn hàng nghìn lần so với các phương pháp thường được sử dụng.


0

Tôi nhìn nó từ một vài điểm khác nhau.

  1. Với thời gian và tài nguyên vô hạn, mã được quản lý hay không được quản lý sẽ nhanh hơn? Rõ ràng, câu trả lời là mã không được quản lý ít nhất luôn có thể ràng buộc mã được quản lý ở khía cạnh này - như trong trường hợp xấu nhất, bạn chỉ cần mã hóa giải pháp mã được quản lý.
  2. Nếu bạn lấy một chương trình bằng một ngôn ngữ và trực tiếp dịch nó sang một ngôn ngữ khác, nó sẽ hoạt động tệ hơn bao nhiêu? Có lẽ là rất nhiều, cho bất kỳ hai ngôn ngữ nào. Hầu hết các ngôn ngữ yêu cầu các tối ưu hóa khác nhau và có các cách hiểu khác nhau. Hiệu suất vi mô thường liên quan nhiều đến việc biết những chi tiết này.
  3. Với thời gian và tài nguyên hữu hạn, ngôn ngữ nào trong hai ngôn ngữ sẽ tạo ra kết quả tốt hơn? Đây là câu hỏi thú vị nhất, vì trong khi một ngôn ngữ được quản lý có thể tạo ra mã chậm hơn một chút (với một chương trình được viết hợp lý cho ngôn ngữ đó), thì phiên bản đó có thể sẽ được thực hiện sớm hơn, cho phép dành nhiều thời gian hơn cho việc tối ưu hóa.

0

Một câu trả lời rất ngắn gọn: Với một ngân sách cố định, bạn sẽ đạt được ứng dụng java hoạt động tốt hơn ứng dụng C ++ (cân nhắc ROI) Ngoài ra, nền tảng Java có nhiều trình cấu hình tốt hơn, điều đó sẽ giúp bạn xác định điểm phát sóng của mình nhanh hơn

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.