C # có thực sự chậm hơn C ++ không?


78

Tôi đã tự hỏi về vấn đề này trong một thời gian.

Tất nhiên, có những thứ trong C # không được tối ưu hóa về tốc độ, vì vậy việc sử dụng các đối tượng hoặc các chỉnh sửa ngôn ngữ đó (như LinQ) có thể khiến mã chậm hơn.

Nhưng nếu bạn không sử dụng bất kỳ tinh chỉnh nào trong số đó, mà chỉ cần so sánh các đoạn mã giống nhau trong C # và C ++ (Rất dễ dàng để dịch một cách dễ dàng). Nó thực sự sẽ chậm hơn nhiều phải không?

Tôi đã thấy các so sánh cho thấy rằng C # có thể còn nhanh hơn trong một số trường hợp, vì về lý thuyết, trình biên dịch JIT nên tối ưu hóa mã trong thời gian thực và thu được kết quả tốt hơn:

Được quản lý hay không được quản lý?

Chúng ta nên nhớ rằng trình biên dịch JIT biên dịch mã tại thời gian thực, nhưng đó là chi phí 1 lần, cùng một mã (sau khi được tiếp cận và biên dịch) không cần phải được biên dịch lại trong thời gian chạy.

GC cũng không thêm nhiều chi phí, trừ khi bạn tạo và phá hủy hàng nghìn đối tượng (như sử dụng String thay vì StringBuilder). Và làm điều đó trong C ++ cũng sẽ rất tốn kém.

Một điểm khác mà tôi muốn đưa ra là giao tiếp tốt hơn giữa các DLL được giới thiệu trong .Net. Nền tảng .Net giao tiếp tốt hơn nhiều so với các DLL dựa trên Managed COM.

Tôi không thấy bất kỳ lý do cố hữu nào khiến ngôn ngữ phải chậm hơn và tôi không thực sự nghĩ rằng C # chậm hơn C ++ (cả từ kinh nghiệm và thiếu một lời giải thích tốt) ..

Vì vậy, một đoạn mã giống nhau được viết bằng C # sẽ chậm hơn đoạn mã tương tự trong C ++?
Nếu vậy, thì TẠI SAO?

Một số tài liệu tham khảo khác (Có nói về điều đó một chút, nhưng không có giải thích về TẠI SAO):

Tại sao bạn muốn sử dụng C # nếu nó chậm hơn C ++?


Bởi vì C # là dễ dàng hơn để sử dụng hơn so với C ++, đặc biệt là khi GUI là có liên quan
Armen Tsirunyan

3
Thực sự ... Nó phụ thuộc ... Một số thứ nhanh hơn, một số thứ chậm hơn. C / C ++ là "xác định" hơn (không có bộ thu gom rác ở phía sau của bạn). Nếu bạn muốn tạo ra 100 luồng, tôi có thể nói với bạn rằng GC sẽ ám ảnh bạn với sự chậm chạp của anh ta (và trước khi nói với tôi rằng 100 luồng là quá nhiều, hãy biết rằng Skype và McAfee AV hiện có 40 luồng trên PC của tôi). .. Marshaling trong C # là một nỗi đau (và nó chậm hơn). Mã hóa khá nhanh. Không, đây không phải là ngọn lửa. Tôi thực sự thích C # hơn.
xanatos

1
Tôi nghĩ điều này nên được chỉnh sửa và mở lại. Tôi nghĩ rằng người đặt câu hỏi có thể không hiểu rằng một câu hỏi cơ bản hơn ảnh hưởng đến sự khác biệt về hiệu suất giữa C # và các ngôn ngữ như C hoặc C ++, đó sẽ là "Sự khác biệt giữa mã được tạo (và cách nó được thực thi) bởi trình biên dịch C và mã được tạo bởi Trình biên dịch C #? " C # / Java và các ngôn ngữ VM hoặc được thông dịch khác có thể có một số bước trung gian khi chạy mã bytecode của chúng hoàn toàn không có trong chương trình C tương đương.
Jonathon Faust

3
Mặc dù bạn có thể tranh luận rằng (như đã nói) điều này đủ điều kiện là "không phải là một câu hỏi thực sự", nhưng việc khẳng định nó mang tính chủ quan là vô nghĩa. "A có nhanh hơn B không" là một cái gì đó mở cho phép đo khách quan. Ngay cả khi câu trả lời thu được bằng cách đo lường như vậy là mơ hồ (một số thứ nhanh hơn trong một số, một số khác), nó vẫn khách quan.
Jerry Coffin,

1
Xem trò chơi điểm chuẩn ngôn ngữ máy tính để so sánh kỹ lưỡng nhiều ngôn ngữ trên nhiều dạng bài toán khác nhau.
Assaf Lavie

Câu trả lời:


144

Cảnh báo: Câu hỏi bạn đã hỏi thực sự khá phức tạp - có thể nhiều hơn những gì bạn nhận ra. Kết quả là, đây là một câu trả lời thực sự dài.

Từ một quan điểm lý thuyết thuần túy, có lẽ có một câu trả lời đơn giản cho điều này: (có lẽ) không có gì về C # thực sự ngăn cản nó nhanh như C ++. Tuy nhiên, mặc dù lý thuyết, có một số lý do thực tế rằng nó chậm hơn ở một số điều dưới một số trường hợp.

Tôi sẽ xem xét ba lĩnh vực khác biệt cơ bản: tính năng ngôn ngữ, thực thi máy ảo và thu gom rác. Hai cái sau thường đi cùng nhau, nhưng có thể độc lập, vì vậy tôi sẽ xem xét chúng một cách riêng biệt.

Tính năng ngôn ngữ

C ++ chú trọng rất nhiều vào các khuôn mẫu và các tính năng trong hệ thống mẫu phần lớn nhằm mục đích cho phép thực hiện nhiều nhất có thể tại thời điểm biên dịch, vì vậy theo quan điểm của chương trình, chúng là "tĩnh". Lập trình meta mẫu cho phép thực hiện các phép tính hoàn toàn tùy ý tại thời điểm biên dịch (tức là hệ thống mẫu đã hoàn chỉnh Turing). Như vậy, về cơ bản bất kỳ thứ gì không phụ thuộc vào đầu vào từ người dùng đều có thể được tính toán tại thời điểm biên dịch, vì vậy trong thời gian chạy, nó chỉ đơn giản là một hằng số. Tuy nhiên, đầu vào cho điều này có thể bao gồm những thứ như thông tin kiểu, vì vậy phần lớn những gì bạn thực hiện thông qua phản chiếu trong thời gian chạy trong C # thường được thực hiện tại thời điểm biên dịch thông qua lập trình siêu mẫu trong C ++. Mặc dù vậy, chắc chắn có sự đánh đổi giữa tốc độ thời gian chạy và tính linh hoạt - những gì các mẫu có thể làm,

Sự khác biệt về đặc điểm ngôn ngữ có nghĩa là hầu hết mọi nỗ lực so sánh hai ngôn ngữ chỉ đơn giản bằng cách chuyển một số C # sang C ++ (hoặc ngược lại) đều có khả năng tạo ra kết quả ở đâu đó giữa vô nghĩa và sai lệch (và điều này cũng đúng với hầu hết các cặp ngôn ngữ khác cũng). Thực tế đơn giản là đối với bất kỳ thứ gì lớn hơn một vài dòng mã, hầu như không ai có khả năng sử dụng các ngôn ngữ theo cùng một cách (hoặc đủ gần với cùng một cách) mà so sánh như vậy cho bạn biết bất cứ điều gì về cách các ngôn ngữ đó làm việc trong cuộc sống thực.

Máy ảo

Giống như hầu hết mọi máy ảo hiện đại hợp lý, Microsoft dành cho .NET có thể và sẽ thực hiện biên dịch JIT (hay còn gọi là "động"). Tuy nhiên, điều này đại diện cho một số sự đánh đổi.

Về cơ bản, tối ưu hóa mã (giống như hầu hết các vấn đề tối ưu hóa khác) phần lớn là một vấn đề hoàn chỉnh NP. Đối với bất kỳ thứ gì ngoại trừ một chương trình đồ chơi / tầm thường, bạn gần như được đảm bảo rằng bạn sẽ không thực sự "tối ưu hóa" kết quả (tức là bạn sẽ không tìm thấy giá trị tối ưu thực sự) - trình tối ưu hóa sẽ đơn giản làm cho mã tốt hơn nó trước đây. Tuy nhiên, khá nhiều tối ưu hóa đã được biết đến rộng rãi, cần một lượng thời gian đáng kể (và thường là bộ nhớ) để thực thi. Với trình biên dịch JIT, người dùng đang đợi trong khi trình biên dịch chạy. Hầu hết các kỹ thuật tối ưu hóa đắt tiền hơn đều bị loại trừ. Biên dịch tĩnh có hai ưu điểm: trước hết, nếu nó chậm (ví dụ: xây dựng một hệ thống lớn), nó thường được thực hiện trên một máy chủ và không aidành thời gian chờ đợi nó. Thứ hai, một tệp thực thi có thể được tạo một lần và được nhiều người sử dụng nhiều lần. Đầu tiên giảm thiểu chi phí tối ưu hóa; lần thứ hai phân bổ chi phí nhỏ hơn nhiều so với số lần thực hiện lớn hơn nhiều.

Như đã đề cập trong câu hỏi ban đầu (và nhiều trang web khác) Việc biên dịch JIT có khả năng nâng cao nhận thức về môi trường mục tiêu, điều này nên (ít nhất là về mặt lý thuyết) bù đắp lợi thế này. Không nghi ngờ gì rằng yếu tố này có thể bù đắp ít nhất một phần nhược điểm của biên dịch tĩnh. Đối với một số loại mã và môi trường đích khá cụ thể, nó có thểthậm chí còn vượt trội hơn những lợi thế của biên dịch tĩnh, đôi khi khá đáng kể. Tuy nhiên, ít nhất là trong thử nghiệm và kinh nghiệm của tôi, điều này khá bất thường. Các tối ưu hóa phụ thuộc vào mục tiêu hầu hết dường như tạo ra sự khác biệt khá nhỏ hoặc chỉ có thể được áp dụng (tự động, dù sao) cho các loại vấn đề khá cụ thể. Lần hiển nhiên điều này sẽ xảy ra nếu bạn đang chạy một chương trình tương đối cũ trên một máy hiện đại. Một chương trình cũ được viết bằng C ++ có thể đã được biên dịch sang mã 32 bit và sẽ tiếp tục sử dụng mã 32 bit ngay cả trên bộ xử lý 64 bit hiện đại. Một chương trình được viết bằng C # sẽ được biên dịch thành mã byte, sau đó VM sẽ biên dịch thành mã máy 64-bit. Nếu chương trình này thu được lợi ích đáng kể từ việc chạy dưới dạng mã 64-bit, thì điều đó có thể mang lại lợi thế đáng kể. Trong một thời gian ngắn khi các bộ vi xử lý 64-bit còn khá mới, điều này đã xảy ra khá nhiều. Mặc dù vậy, mã gần đây có khả năng được hưởng lợi từ bộ xử lý 64 bit sẽ được biên dịch tĩnh thành mã 64 bit.

Sử dụng máy ảo cũng có khả năng cải thiện việc sử dụng bộ nhớ cache. Hướng dẫn cho một máy ảo thường nhỏ gọn hơn so với hướng dẫn máy bản địa. Nhiều mã trong số chúng có thể vừa với một lượng bộ nhớ đệm nhất định, vì vậy bạn có cơ hội tốt hơn để bất kỳ mã nhất định nào được đưa vào bộ nhớ đệm khi cần. Điều này có thể giúp duy trì việc thực thi mã VM được diễn giải cạnh tranh hơn (về tốc độ) so với hầu hết mọi người mong đợi ban đầu - bạn có thể thực thi rất nhiều lệnh trên một CPU hiện đại trong thời gian bỏ lỡ một bộ nhớ cache.

Cũng cần nhắc lại rằng yếu tố này không nhất thiết phải khác nhau giữa hai thứ. Không có gì ngăn cản (ví dụ) trình biên dịch C ++ tạo ra đầu ra dự định chạy trên một máy ảo (có hoặc không có JIT). Trên thực tế, C ++ / CLI của Microsoft gần như vậy - một (gần như) trình biên dịch C ++ phù hợp (mặc dù, với rất nhiều phần mở rộng) tạo ra đầu ra dự định chạy trên một máy ảo.

Điều ngược lại cũng đúng: Microsoft hiện có .NET Native, biên dịch mã C # (hoặc VB.NET) thành một tệp thực thi gốc. Điều này mang lại hiệu suất nói chung giống C ++ hơn nhiều, nhưng vẫn giữ được các tính năng của C # / VB (ví dụ: C # được biên dịch sang mã gốc vẫn hỗ trợ phản chiếu). Nếu bạn có mã C # chuyên sâu về hiệu suất, điều này có thể hữu ích.

Thu gom rác thải

Từ những gì tôi đã thấy, tôi muốn nói rằng thu gom rác là yếu tố được hiểu kém nhất trong ba yếu tố này. Chỉ cho một ví dụ rõ ràng, câu hỏi ở đây đề cập: "GC cũng không thêm nhiều chi phí, trừ khi bạn tạo và phá hủy hàng nghìn đối tượng [...]". Trong thực tế, nếu bạn tạo phá hủy hàng nghìn đối tượng, chi phí thu gom rác nói chung sẽ khá thấp. .NET sử dụng một trình quét thế hệ, là một trình thu thập sao chép đa dạng. Bộ thu gom rác hoạt động bằng cách bắt đầu từ "địa điểm" (ví dụ: thanh ghi và ngăn xếp thực thi) mà con trỏ / tham chiếu được biết đếnđể có thể truy cập. Sau đó, nó "đuổi theo" các con trỏ đó đến các đối tượng đã được cấp phát trên heap. Nó kiểm tra các đối tượng đó để tìm các con trỏ / tham chiếu xa hơn, cho đến khi nó đi theo tất cả chúng đến tận cùng của bất kỳ chuỗi nào và tìm thấy tất cả các đối tượng có thể truy cập (ít nhất là có thể). Trong bước tiếp theo, nó lấy tất cả các đối tượng (hoặc ít nhất có thể ) đang được sử dụng và thu gọn heap bằng cách sao chép tất cả chúng vào một đoạn liền kề ở một đầu của bộ nhớ đang được quản lý trong heap. Phần còn lại của bộ nhớ sau đó sẽ được giải phóng (phải chạy các trình hoàn thiện mô-đun, nhưng ít nhất là trong mã được viết tốt, chúng đủ hiếm để tôi bỏ qua chúng trong thời điểm này).

Điều này có nghĩa là nếu bạn tạo và phá hủy nhiều đối tượng, việc thu gom rác sẽ tăng thêm rất ít chi phí. Thời gian thực hiện của một chu kỳ thu gom rác phụ thuộc gần như hoàn toàn vào số lượng các đối tượng đã được tạo ra nhưng không bị phá hủy. Hệ quả chính của việc tạo và phá hủy các đối tượng một cách vội vàng đơn giản là GC phải chạy thường xuyên hơn, nhưng mỗi chu kỳ vẫn sẽ nhanh. Nếu bạn tạo các đối tượng và không phá hủy chúng, GC sẽ chạy thường xuyên hơn mỗi chu kỳ sẽ chậm hơn đáng kể vì nó dành nhiều thời gian hơn để theo đuổi các con trỏ đến các đối tượng có khả năng sống dành nhiều thời gian hơn để sao chép các đối tượng vẫn đang được sử dụng.

Để chống lại điều này, công việc nhặt rác theo thế hệ hoạt động dựa trên giả định rằng các vật thể vẫn còn "sống" trong một thời gian có khả năng tiếp tục sống sót trong một thời gian nữa. Dựa trên điều này, nó có một hệ thống trong đó các đối tượng tồn tại một số chu kỳ thu gom rác sẽ được "sử dụng" và bộ thu gom rác bắt đầu đơn giản cho rằng chúng vẫn đang được sử dụng, vì vậy thay vì sao chép chúng ở mỗi chu kỳ, nó chỉ cần rời đi họ một mình. Đây là một giả định hợp lệ thường đủ rằng việc nhặt rác theo thế hệ thường có chi phí thấp hơn đáng kể so với hầu hết các hình thức GC khác.

Quản lý bộ nhớ "thủ công" thường không được hiểu rõ. Chỉ đối với một ví dụ, nhiều nỗ lực so sánh giả định rằng tất cả việc quản lý bộ nhớ thủ công cũng tuân theo một mô hình cụ thể (ví dụ: phân bổ phù hợp nhất). Điều này thường ít (nếu có) gần với thực tế hơn niềm tin của nhiều người về việc thu gom rác (ví dụ, giả định phổ biến rằng nó thường được thực hiện bằng cách sử dụng phương pháp đếm tham chiếu).

Với sự đa dạng của các chiến lược cho cả thu gom rác quản lý bộ nhớ thủ công, rất khó để so sánh cả hai về tốc độ tổng thể. Việc cố gắng so sánh tốc độ phân bổ và / hoặc giải phóng bộ nhớ (tự nó) gần như được đảm bảo để tạo ra kết quả tốt nhất là vô nghĩa và tệ nhất là hoàn toàn sai lệch.

Chủ đề thưởng: Điểm chuẩn

Vì khá nhiều blog, trang web, bài báo trên tạp chí, v.v., tuyên bố cung cấp bằng chứng "khách quan" theo hướng này hay hướng khác, tôi cũng sẽ đưa vào chủ đề đó trị giá hai xu.

Hầu hết các điểm chuẩn này hơi giống như việc thanh thiếu niên quyết định đua xe của họ và ai thắng sẽ được giữ cả hai xe. Mặc dù vậy, các trang web khác nhau ở một điểm quan trọng: họ cho rằng những người công bố điểm chuẩn được lái cả hai chiếc xe. Một cơ hội kỳ lạ nào đó, xe của anh ta luôn thắng, và những người khác phải giải quyết "tin tôi đi, tôi đã thực sự lái chiếc xe của bạn nhanh như nó sẽ đi."

Thật dễ dàng để viết một điểm chuẩn kém tạo ra kết quả có nghĩa là không có gì. Hầu như bất kỳ ai có đủ kỹ năng cần thiết để thiết kế một điểm chuẩn tạo ra bất kỳ điều gì có ý nghĩa, cũng có kỹ năng tạo ra một điểm chuẩn mang lại kết quả mà anh ta quyết định. Trên thực tế, viết mã để tạo ra một kết quả cụ thể có lẽ dễ hơn mã thực sự tạo ra kết quả có ý nghĩa.

Như người bạn của tôi, James Kanze đã nói, "đừng bao giờ tin tưởng vào một điểm chuẩn mà bạn đã không làm sai lệch bản thân."

Phần kết luận

Không có câu trả lời đơn giản. Tôi chắc chắn một cách hợp lý rằng tôi có thể tung đồng xu để chọn người chiến thắng, sau đó chọn một số từ (giả sử) 1 đến 20 để biết tỷ lệ phần trăm mà nó sẽ thắng và viết một số mã trông giống như một điểm chuẩn hợp lý và công bằng, và đưa ra kết luận bị bỏ qua (ít nhất là trên một số bộ xử lý đích - một bộ xử lý khác có thể thay đổi tỷ lệ phần trăm một chút).

Như những người khác đã chỉ ra, đối với hầu hết các mã, tốc độ gần như không liên quan. Hệ quả của điều đó (thường bị bỏ qua nhiều hơn) là trong đoạn mã nhỏ mà tốc độ quan trọng, nó thường quan trọng rất nhiều . Ít nhất theo kinh nghiệm của tôi, đối với mã mà nó thực sự quan trọng, C ++ hầu như luôn luôn là người chiến thắng. Chắc chắn có những yếu tố ủng hộ C #, nhưng trong thực tế, chúng dường như bị lấn át bởi những yếu tố có lợi cho C ++. Bạn chắc chắn có thể tìm thấy các điểm chuẩn sẽ cho biết kết quả lựa chọn của bạn, nhưng khi bạn viết mã thực, hầu như bạn luôn có thể làm cho nó trong C ++ nhanh hơn trong C #. Có thể (hoặc có thể không) cần nhiều kỹ năng và / hoặc nỗ lực hơn để viết, nhưng hầu như luôn có thể.


6
Tại một công ty trước đây, một số quyết định của chúng tôi dựa trên việc đo điểm chuẩn, nhưng việc đo điểm chuẩn được thực hiện kém vì nó bỏ lỡ bức tranh lớn hơn (ví dụ: tác động đến GC) hoặc chỉ được thực hiện không tốt. Hãy coi chừng điểm chuẩn ... rất hấp dẫn để đưa ra quyết định dựa trên những thứ được thực hiện ngoài ngữ cảnh. Nếu bạn làm điểm chuẩn, thì hãy cố gắng làm cho nó càng đại diện càng tốt :)
Mark Simpson

4
Thời gian chạy IIRC, .NET không bao giờ thông dịch nhưng luôn luôn là JIT. Việc tối ưu hóa được thực hiện bởi cả trình biên dịch tại thời điểm biên dịch (mã nguồn sang IL) và jitter trong thời gian chạy (IL sang hướng dẫn gốc). Jitter có thể chọn không tối ưu hóa nếu một phương thức sẽ mất quá nhiều thời gian để jit. Nếu cần tối ưu hóa hoàn toàn, bạn có thể sử dụng NGen để tạo hình ảnh gốc tại thời điểm cài đặt.
Dudu

2
Tôi nghĩ câu trả lời thực sự khá đơn giản: câu trả lời của tôi sẽ là, "Có, vì C # thực tế buộc bạn phải tạo các kiểu tham chiếu cấu trúc dữ liệu, điều này thêm một lớp hướng dẫn mới với mỗi lớp bao bọc, tăng lưu lượng bộ nhớ và giảm cục bộ bộ nhớ. Trong C ++, bất kể bạn đặt bao nhiêu lớp bao quanh một đối tượng, đối tượng sẽ ở cùng một vị trí trong bộ nhớ. " Bạn có đồng ý không?
user541686

2
@Mehrdad: Có và không. Từ quan điểm thực tế, vâng, điều đó có thể có lợi cho C ++ thường xuyên hơn không. Đồng thời, GC có thể giúp bạn dễ dàng chuyển các tham chiếu xung quanh để tránh sao chép dữ liệu trong nhiều trường hợp.
Jerry Coffin

2
@Mehrdad: Đúng vậy - đó là điều tôi đang nghĩ về những gì tôi đã nói: "Bạn hầu như luôn có thể làm cho nó nhanh hơn trong C ++ so với C #. Nó có thể (hoặc có thể không) cần nhiều kỹ năng và / hoặc nỗ lực hơn để viết, nhưng hầu như luôn luôn có thể. "
Jerry Coffin

41

Bởi vì bạn không phải lúc nào cũng cần sử dụng (và tôi sử dụng ngôn ngữ này một cách lỏng lẻo) "nhanh nhất"? Tôi không lái xe Ferrari đi làm chỉ vì nó nhanh hơn ...


17
Tôi nghĩ rằng sự tương tự này rất phù hợp. Nếu tôi lái một chiếc Ferrari đi làm, tôi sẽ không đi làm nhanh hơn. Chiếc xe chắc chắn có khả năng chạy nhanh hơn những gì tôi lái, nhưng khả năng của chiếc xe không phải là yếu tố hạn chế. Có giới hạn tốc độ, giao thông, điều kiện đường xá, v.v. Tương tự, phần mềm thường bị ràng buộc bởi những thứ như tương tác của người dùng, I / O, v.v. Ngôn ngữ bạn sử dụng sẽ không tạo ra nhiều khác biệt trong những trường hợp như vậy.
Fred Larson

3
Vâng, nhưng câu hỏi là "Xe nào là Ferrari, và xe nào là Volkswagen?" hoặc nếu cả hai đều là Ferrari hay không.
pepr

@pepr Câu hỏi trong tiêu đề là có, nhưng câu hỏi ở cuối bài là "tại sao bạn muốn sử dụng Volkswagen trong khi bạn có thể sử dụng Ferrari?".
Adam Houldsworth

@AdamHouldsworth: Có. Câu trả lời có thể tương tự - "vấn đề chi phí".
pepr

Không, tôi đi xe máy để đi làm chủ yếu vì nó nhanh hơn.
CashCow

20

C ++ luôn có lợi thế về hiệu suất. Với C #, tôi không xử lý được bộ nhớ và tôi thực sự có hàng tấn tài nguyên để thực hiện công việc của mình.

Điều bạn cần tự hỏi mình hơn là cái nào giúp bạn tiết kiệm thời gian. Máy móc hiện nay cực kỳ mạnh mẽ và hầu hết mã của bạn phải được thực hiện bằng ngôn ngữ cho phép bạn nhận được nhiều giá trị nhất trong thời gian ngắn nhất.

Nếu có một quá trình xử lý cốt lõi mất quá nhiều thời gian trong C #, thì bạn có thể xây dựng C ++ và tương tác theo cách của bạn với C #.

Ngừng suy nghĩ về hiệu suất mã của bạn. Bắt đầu xây dựng giá trị.


13
+1 cho "Ngừng suy nghĩ về hiệu suất mã của bạn. Bắt đầu xây dựng giá trị."
Thomas Matthews

2
Tôi đồng ý về tốc độ phát triển trong C #. Nó có giá trị hơn nhiều trong nhiều trường hợp.
Yochai Timmer

Nhà phát triển cần có sự lựa chọn tốt cho từng trường hợp. Trong một số trường hợp C # sẽ không phù hợp với nhu cầu. Giả sử ứng dụng dựa trên tính toán như Trình chuyển đổi video hoặc Trình trộn video hoặc ảo hóa 3D hoặc Tính toán luồng hoặc một trò chơi . Ngoài ra, bạn phải lưu ý dự án phần cứng mục tiêu. Một số ứng dụng phải được chạy trên HW cấp thấp.
SMMousavi

20

Circa 2005, hai chuyên gia hiệu suất MS từ cả hai phía của hàng rào bản địa / được quản lý đã cố gắng trả lời cùng một câu hỏi. Phương pháp và quy trình của họ vẫn hấp dẫn và các kết luận vẫn được giữ cho đến ngày nay - và tôi không biết có nỗ lực nào tốt hơn để đưa ra câu trả lời sáng suốt. Họ lưu ý rằng một cuộc thảo luận về các lý do tiềm ẩn cho sự khác biệt trong hiệu suất là giả thuyết và vô ích, và một cuộc thảo luận thực sự phải có một số cơ sở thực nghiệm về tác động thực tế của những khác biệt đó.

Vì vậy, Raymond ChenRico Mariani đã đặt ra các quy tắc cho một cuộc thi giao hữu. Từ điển tiếng Trung / tiếng Anh được chọn làm bối cảnh ứng dụng đồ chơi: đủ đơn giản để được mã hóa như một dự án phụ theo sở thích, nhưng đủ phức tạp để chứng minh các kiểu sử dụng dữ liệu không tầm thường. Các quy tắc bắt đầu đơn giản - Raymond mã hóa một triển khai C ++ đơn giản, Rico di chuyển nó sang C # từng dòng một , không có sự phức tạp nào và cả hai triển khai đều chạy một điểm chuẩn. Sau đó, một số lần lặp lại tối ưu hóa đã xảy ra sau đó.

Chi tiết đầy đủ có tại đây: 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 .

Cuộc đối thoại của những người khổng lồ này đặc biệt mang tính giáo dục và tôi thực lòng khuyên bạn nên đi sâu vào - nhưng nếu bạn thiếu thời gian hoặc sự kiên nhẫn, Jeff Atwood đã tổng hợp những điểm mấu chốt rất hay :

nhập mô tả hình ảnh ở đây

Cuối cùng, C ++ nhanh hơn gấp 2 lần - nhưng ban đầu, nó chậm hơn 13 lần.

Như Rico tổng kết :

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ý đã đạt được một 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 phiên bản được quản lý, Raymond phải:

  • Viết tệp / nội dung io 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.

Đó là kinh nghiệm của tôi vẫn còn, 11 năm và ai biết sau này có bao nhiêu phiên bản C # / C ++.

Tất nhiên, đó không phải là ngẫu nhiên, vì hai ngôn ngữ này đạt được những mục tiêu thiết kế khác nhau một cách ngoạn mục. C # muốn được sử dụng khi chi phí phát triển là yếu tố chính (vẫn là phần lớn phần mềm) và C ++ tỏa sáng ở nơi bạn không cần phải tiết kiệm chi phí để vắt từng chút hiệu suất cuối cùng ra khỏi máy của mình: trò chơi, giao dịch thuật toán, dữ liệu- các trung tâm, v.v.


1
Đây là câu trả lời hay nhất, và anh ấy tóm tắt rất hay!
Rodney P. Barbati,

6

C # nhanh hơn so với C ++. Viết nhanh hơn. Đối với thời gian thực thi, không có gì đánh bại được một hồ sơ.

Nhưng C # không có nhiều thư viện như C ++ có thể giao diện dễ dàng.

Và C # phụ thuộc nhiều vào cửa sổ ...


5
C # yêu cầu hỗ trợ nhiều hơn từ trình biên dịch, hệ điều hành và các nền tảng. Một mối quan tâm LỚN đối với các hệ thống nhúng.
Thomas Matthews

C # không chỉ chạy trên Windows mà còn chạy trên Mac, Linux, Android, iOS, ... hãy xem microsoft.com/net/multiple-platform-support , xamarin.com/platform , mono-project.com/docs/about-mono/ nền tảng được hỗ trợ .
yoyo

@yoyo: C # không chạy trên các nền tảng khác (và cũng có Mono trong Linux), nhưng các thư viện bạn có thể muốn sử dụng (ví dụ: Windows Forms) có thể không. Tính di động là một vấn đề đối với việc phát triển C #.
Alexandre C.

câu trả lời này bây giờ đã thay đổi. Thời gian chạy .NET Standard được Microsoft chính thức hỗ trợ để chạy trên Linux. Vì vậy, trừ khi bạn phải phát triển trên bộ vi điều khiển, tính di động không phải là một vấn đề. Chưa kể rằng nó cũng nhanh hơn rất nhiều.
MovGP0

3

BTW, các ứng dụng quan trọng về thời gian không được mã hóa bằng C # hoặc Java, chủ yếu do không chắc chắn về thời điểm thực hiện Thu gom rác.

Trong thời hiện đại, tốc độ ứng dụng hoặc thực thi không còn quan trọng như trước đây. Các lịch trình phát triển, tính đúng đắn và chắc chắn là những ưu tiên cao hơn. Phiên bản tốc độ cao của một ứng dụng sẽ không tốt nếu nó có nhiều lỗi, bị lỗi nhiều hoặc tệ hơn, bỏ lỡ cơ hội tiếp thị hoặc được triển khai.

Vì lịch trình phát triển là một ưu tiên, nên các ngôn ngữ mới đang ra đời giúp tăng tốc độ phát triển. C # là một trong số này. C # cũng hỗ trợ tính đúng đắn và mạnh mẽ bằng cách loại bỏ các tính năng khỏi C ++ gây ra các vấn đề phổ biến: một ví dụ là con trỏ.

Sự khác biệt về tốc độ thực thi của một ứng dụng được phát triển bằng C # và một ứng dụng được phát triển bằng C ++ là không đáng kể trên hầu hết các nền tảng. Điều này là do thực tế là tắc nghẽn thực thi không phụ thuộc vào ngôn ngữ mà thường phụ thuộc vào hệ điều hành hoặc I / O. Ví dụ, nếu C ++ thực hiện một chức năng trong 5 ms nhưng C # sử dụng 2ms và chờ dữ liệu mất 2 giây, thì thời gian dành cho hàm là không đáng kể so với thời gian chờ dữ liệu.

Chọn ngôn ngữ phù hợp nhất cho nhà phát triển, nền tảng và dự án. Làm việc hướng tới các mục tiêu chính xác, mạnh mẽ và triển khai. Tốc độ của một ứng dụng nên được coi là một lỗi: ưu tiên nó, so sánh với các lỗi khác và sửa khi cần thiết.


Sự hiểu biết của tôi là C # (cả Java nữa?) Không 'loại bỏ' sự tồn tại / khái niệm của con trỏ đến mức ẩn chúng khỏi lập trình viên bằng cách trừu tượng hóa các loại thông qua cú pháp thực tế. Tuy nhiên, đây là một điểm khá vụn vặt. Sự đơn giản tương đối của C # là không thể chối cãi.
Mike G

3

Một cách tốt hơn để nhìn vào nó, mọi thứ đều chậm hơn C / C ++ vì nó trừu tượng hóa thay vì tuân theo mô hình dính và bùn. Nó được gọi là lập trình hệ thống vì một lý do, bạn lập trình dựa trên hạt hoặc kim loại trần. Làm như vậy cũng mang lại cho bạn tốc độ mà bạn không thể đạt được với các ngôn ngữ khác như C # hoặc Java. Nhưng than ôi C root đều làm mọi thứ một cách khó khăn, vì vậy bạn chủ yếu sẽ viết nhiều mã hơn và dành nhiều thời gian hơn để gỡ lỗi nó.

C cũng phân biệt chữ hoa chữ thường, các đối tượng trong C ++ cũng tuân theo các bộ quy tắc nghiêm ngặt. Ví dụ một que kem màu tím có thể không giống với một que kem màu xanh lam, mặc dù chúng có thể là hình nón, chúng có thể không nhất thiết thuộc họ hình nón và nếu bạn quên xác định hình nón là gì thì bạn sẽ nhầm lẫn. Như vậy đặc tính của kem có thể nhái hoặc không. Bây giờ là đối số tốc độ, C / C ++ sử dụng cách tiếp cận ngăn xếp và đống, đây là nơi kim loại trần nhận được nó là kim loại.

Với thư viện tăng cường, bạn có thể đạt được tốc độ đáng kinh ngạc, tiếc là hầu hết các studio trò chơi đều tuân theo thư viện tiêu chuẩn. Lý do khác cho điều này có thể là do phần mềm được viết bằng C / C ++ có xu hướng có kích thước tệp lớn, vì đó là một bộ sưu tập tệp khổng lồ thay vì một tệp duy nhất. Cũng cần lưu ý rằng tất cả các hệ điều hành đều được viết bằng C nên nói chung tại sao chúng ta phải đặt câu hỏi cái gì có thể nhanh hơn ?!

Ngoài ra, bộ nhớ đệm không nhanh hơn quản lý bộ nhớ thuần túy, xin lỗi, nhưng điều này không xảy ra. Bộ nhớ là một cái gì đó vật lý, bộ nhớ đệm là một cái gì đó phần mềm làm để đạt được một cú hích về hiệu suất. Người ta cũng có thể lý do rằng nếu không có bộ nhớ đệm vật lý bộ nhớ đệm sẽ không tồn tại. Nó không làm mất tác dụng của bộ nhớ dữ kiện phải được quản lý ở một mức độ nào đó cho dù nó tự động hay thủ công.


1

Tại sao bạn lại viết một ứng dụng nhỏ không đòi hỏi nhiều về cách tối ưu hóa trong C ++, nếu có một lộ trình nhanh hơn (C #)?


Tại sao? Bởi vì tôi là một kẻ ngớ ngẩn khi xem mã của tôi chạy khi nào. Bao gồm cả các trình hủy. Ngay cả một ứng dụng nhỏ cũng được hưởng lợi từ việc được tối ưu hóa để dễ đọc.
IInspectable

1

Không thực sự có được câu trả lời chính xác cho câu hỏi của bạn trừ khi bạn thực hiện các điểm chuẩn trên các hệ thống cụ thể. Tuy nhiên, vẫn rất thú vị khi nghĩ về một số khác biệt cơ bản giữa các ngôn ngữ lập trình như C # và C ++.

Tổng hợp

Việc thực thi mã C # yêu cầu một bước bổ sung trong đó mã được JIT'ed. Về hiệu suất sẽ có lợi cho C ++. Ngoài ra, trình biên dịch JIT chỉ có thể tối ưu hóa mã được tạo trong đơn vị mã được JIT'ed (ví dụ: một phương thức) trong khi trình biên dịch C ++ có thể tối ưu hóa trên các lệnh gọi phương thức bằng cách sử dụng các kỹ thuật tích cực hơn.

Tuy nhiên, trình biên dịch JIT có thể tối ưu hóa mã máy được tạo để khớp chặt chẽ với phần cứng bên dưới cho phép nó tận dụng các tính năng phần cứng bổ sung nếu chúng tồn tại. Theo hiểu biết của tôi, trình biên dịch .NET JIT không làm được điều đó nhưng nó có thể tạo ra mã khác cho Atom thay vì Pentium CPU.

Truy cập bộ nhớ

Trong nhiều trường hợp, kiến ​​trúc thu thập rác có thể tạo ra các mẫu truy cập bộ nhớ tối ưu hơn so với mã C ++ tiêu chuẩn. Nếu vùng bộ nhớ được sử dụng cho thế hệ đầu tiên đủ nhỏ có thể nằm trong bộ nhớ cache của CPU, tăng hiệu suất. Nếu bạn tạo và phá hủy nhiều đối tượng nhỏ, chi phí bảo trì đống được quản lý có thể nhỏ hơn những gì được yêu cầu bởi thời gian chạy C ++. Một lần nữa, điều này phụ thuộc nhiều vào ứng dụng. Một nghiên cứu về hiệu suất Python chứng minh rằng một ứng dụng Python được quản lý cụ thể có thể mở rộng quy mô tốt hơn nhiều so với phiên bản đã biên dịch do kết quả của các mẫu truy cập bộ nhớ tối ưu hơn.


Có một bước bổ sung để JIT biên dịch mã, nhưng do quy tắc sử dụng lại, có thể bạn sẽ sử dụng đoạn mã đó nhiều hơn 10000 lần so với biên dịch đơn lẻ đó. (ví dụ: vòng lặp). Vì vậy, có một câu hỏi nếu JIT đã làm một công việc đủ tốt để biên dịch cho quá trình biên dịch phức tạp đó.
Yochai Timmer

0

Đừng để nhầm lẫn!

  • Nếu ứng dụng C # được viết trong trường hợp tốt nhất và ứng dụng C ++ được viết trong trường hợp tốt nhất, thì C ++ nhanh hơn.
    Nhiều lý do ở đây là tại sao C ++ nhanh hơn C # vốn có, chẳng hạn như C # sử dụng máy ảo tương tự như JVM trong Java. Về cơ bản, ngôn ngữ cấp cao hơn có hiệu suất kém hơn (nếu sử dụng trong trường hợp tốt nhất).

  • Nếu bạn là một lập trình viên C # chuyên nghiệp có kinh nghiệm cũng giống như bạn là một lập trình viên C ++ chuyên nghiệp có kinh nghiệm, việc phát triển một ứng dụng bằng C # dễ dàng và nhanh chóng hơn nhiều so với C ++.

Nhiều tình huống khác giữa những tình huống này có thể xảy ra. Ví dụ: bạn có thể viết ứng dụng C # và ứng dụng C ++ để ứng dụng C # chạy nhanh hơn ứng dụng C ++.

Để chọn ngôn ngữ, bạn nên lưu ý các trường hợp của dự án và chủ đề của nó. Đối với một dự án kinh doanh chung, bạn nên sử dụng C #. Đối với một dự án yêu cầu hiệu suất cao như dự án Chuyển đổi video hoặc Xử lý hình ảnh, bạn nên chọn C ++.

Cập nhật:

ĐỒNG Ý. Hãy so sánh một số lý do thực tế về lý do tại sao tốc độ có thể có của C ++ cao hơn C #. Hãy xem xét một ứng dụng C # được viết tốt và cùng một phiên bản C ++:

  • C # sử dụng một máy ảo làm lớp giữa để thực thi ứng dụng. Nó có chi phí cao.
  • AFAIK CLR không thể tối ưu hóa tất cả các mã C # trong máy đích. Ứng dụng C ++ có thể được biên dịch trên máy mục tiêu với hầu hết các tối ưu hóa.
  • Trong C #, tối ưu hóa nhất có thể cho thời gian chạy có nghĩa là máy ảo nhanh nhất có thể. VM có chi phí dù sao.
  • C # là một ngôn ngữ cấp cao hơn, do đó nó tạo ra nhiều dòng mã chương trình hơn cho quá trình cuối cùng. (hãy xem xét sự khác biệt giữa ứng dụng Assembly và Ruby! Điều kiện tương tự là giữa C ++ và một ngôn ngữ cấp cao hơn như C # / Java)

Nếu bạn muốn biết thêm một số thông tin trong thực tế với tư cách là một chuyên gia, hãy xem phần này . Nó là về Java nhưng nó cũng áp dụng cho C #.


0

Mối quan tâm chính sẽ không phải là tốc độ, mà là sự ổn định trên các phiên bản windows và nâng cấp. Win32 hầu như được miễn dịch trên các phiên bản windows nên nó có độ ổn định cao.

Khi máy chủ ngừng hoạt động và phần mềm được di chuyển, rất nhiều lo lắng xảy ra xung quanh bất kỳ thứ gì sử dụng .Net và thường là rất nhiều người hoang mang về các phiên bản .net nhưng một ứng dụng Win32 được xây dựng cách đây 10 năm vẫn tiếp tục chạy như không có gì xảy ra.


-1

Tôi đã chuyên về tối ưu hóa trong khoảng 15 năm và thường xuyên viết lại mã C ++, sử dụng nhiều nhất các bản chất của trình biên dịch càng nhiều càng tốt vì hiệu suất C ++ thường không bằng CPU có khả năng. Hiệu suất bộ nhớ cache thường cần được xem xét. Nhiều hướng dẫn toán học vectơ được yêu cầu để thay thế mã dấu phẩy động C ++ tiêu chuẩn. Rất nhiều mã STL được viết lại và thường chạy nhanh hơn nhiều lần. Các phép toán và mã sử dụng nhiều dữ liệu có thể được viết lại với kết quả ngoạn mục khi CPU đạt đến hiệu suất tối ưu.

Không điều gì có thể xảy ra trong C #. Để so sánh hiệu suất tương đối # thời gian thực # của họ thực sự là một câu hỏi đáng kinh ngạc. Đoạn mã nhanh nhất trong C ++ sẽ là khi mỗi lệnh trình hợp dịch đơn lẻ được tối ưu hóa cho tác vụ trong tay, không có lệnh nào không cần thiết - cả. Nơi mỗi phần bộ nhớ được sử dụng khi nó được yêu cầu, và không được sao chép n lần vì đó là những gì thiết kế ngôn ngữ yêu cầu. Nơi mỗi chuyển động bộ nhớ cần thiết hoạt động hài hòa với bộ nhớ cache. Trường hợp thuật toán cuối cùng không thể được cải thiện, dựa trên các yêu cầu chính xác về thời gian thực, xem xét độ chính xác và chức năng.

Sau đó, bạn sẽ tiếp cận một giải pháp tối ưu.

So sánh C # với tình huống lý tưởng này thật đáng kinh ngạc. C # không thể cạnh tranh. Trên thực tế, tôi hiện đang viết cả đống mã C # (khi tôi nói lại nghĩa là xóa và thay thế nó hoàn toàn) bởi vì nó thậm chí không ở cùng một thành phố, chứ đừng nói đến công viên bóng khi nói đến việc nâng nặng thời gian thực. hiệu suất.

Vì vậy, xin đừng tự lừa dối mình. C # chậm. Chết chậm. Tất cả các phần mềm đang chậm lại và C # đang làm cho tốc độ suy giảm này trầm trọng hơn. Tất cả phần mềm chạy bằng chu trình thực thi tìm nạp trong trình hợp dịch (bạn biết đấy - trên CPU). Bạn sử dụng gấp 10 lần hướng dẫn; nó sẽ chậm gấp 10 lần. Bạn làm tê liệt bộ nhớ cache; nó sẽ còn chậm hơn. Bạn thêm phần mềm thu thập rác vào một phần mềm thời gian thực, sau đó bạn thường bị đánh lừa rằng mã chạy 'ổn', thỉnh thoảng chỉ có vài khoảnh khắc đó khi mã 'hơi chậm một chút'.

Hãy thử thêm hệ thống thu gom rác vào mã nơi mọi chu kỳ đều được tính. Tôi tự hỏi liệu phần mềm giao dịch thị trường chứng khoán có thu gom rác không (bạn biết đấy - trên hệ thống chạy trên cáp ngầm mới có giá 300 triệu USD?). Chúng ta có thể dành 300 mili giây mỗi 2 giây không? Còn về phần mềm điều khiển chuyến bay trên tàu con thoi - GC có ổn không? Làm thế nào về phần mềm quản lý động cơ trong xe hiệu suất? (Nơi mà chiến thắng trong một mùa giải có thể trị giá hàng triệu).

Việc thu gom rác trong thời gian thực là một thất bại hoàn toàn.

Vì vậy, không, rõ ràng, C ++ nhanh hơn nhiều. C # là một bước nhảy vọt về phía sau.


1
C # không phải là một bước nhảy vọt về phía sau, bởi vì C # được thiết kế với một bộ yêu cầu hoàn toàn khác với C ++. C # giao dịch tốc độ thực thi để lấy tốc độ phát triển. Tất cả những ví dụ mà bạn đã chỉ ra C # sẽ thất bại (tàu con thoi, quản lý động cơ, v.v.) không bao giờ là ứng dụng mục tiêu của C #. C # cho phép cung cấp sản phẩm đã hoàn thành tương đối nhanh. C # cho phép sản phẩm chạy trên bất kỳ phần cứng nào mà máy ảo của nó chạy trên đó. Bạn có thể viết tối ưu hóa lắp ráp, biết rằng mã của bạn cần chạy trên hàng trăm cấu hình phần cứng khác nhau?
mag_zbc

Tôi đồng ý với những gì bạn nói mag_zbc, nhưng câu hỏi ban đầu là "C # có thực sự chậm hơn C ++ không?" Và nhận xét 'nhảy vọt về phía sau' của tôi liên quan đến tốc độ của C #, không phải là tính di động.
David Lloyd

Đây là một quan điểm thiên lệch, dựa trên các giả định thuần túy mang tính đầu cơ mà không cung cấp bất kỳ điểm chuẩn nào.
zmechanic
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.