Hỏi ChatGPT
Nếu C++ nhanh hơn Java, tại sao Java lại được sử dụng trong mọi ứng dụng doanh nghiệp?
Nhiều ứng dụng doanh nghiệp không được viết bằng Java, nhưng đủ nhiều để đây là một câu hỏi hợp lệ, vì vậy hãy giả định rằng đây là “tại sao nhiều ứng dụng doanh nghiệp được viết bằng Java…”
Vâng, C++ nhanh hơn theo một cách cụ thể. Nó có rất nhiều lower overhead abstractions (trừu tượng hóa có chi phí thấp) và nhiều cách để tránh sử dụng abstractions nếu bạn không cần chúng.
Vì vậy, C++ sẽ có xu hướng sử dụng ít chu kỳ CPU (CPU cycles) hơn để hoàn thành một công việc. Mô hình bộ nhớ (memory model) của C++ cũng khác, và nó sẽ có xu hướng sử dụng ít RAM hơn để hoàn thành một công việc. Cả hai điều này không phải là tuyệt đối, nhưng chúng thường đúng.
Mặc dù vậy, (a) CPU cycles không phải là toàn bộ câu chuyện, và (b) Java cũng có một số lợi thế khác.
Nhiều ứng dụng doanh nghiệp hoặc bị ràng buộc bởi đĩa cứng hoặc bị ràng buộc bởi mạng (hoặc cả hai) và không bị ràng buộc bởi CPU (hoặc RAM). Điều này có nghĩa là nếu bạn thay thế hệ thống mà chúng đang chạy bằng một hệ thống có CPU nhanh gấp đôi nhưng đĩa cứng và mạng không nhanh hơn, ứng dụng sẽ chỉ tiêu tốn thêm nhiều chu kỳ CPU chờ đĩa cứng hoặc mạng. Nếu công việc là “yêu cầu mạng cung cấp cho chúng ta một số linh kiện, báo lại số lượng có trong kho và số lượng đang đặt hàng và khi nào lô hàng tiếp theo sẽ đến bến tải”, thì khả năng sử dụng CPU sẽ rất thấp. Bạn sẽ tìm kiếm trong cơ sở dữ liệu về nội dung kho hàng và cơ sở dữ liệu về đơn đặt hàng, và có thể là cơ sở dữ liệu về chi tiết vận chuyển đến, hoặc thực hiện yêu cầu mạng ở nơi khác (hoặc sử dụng cơ sở dữ liệu cục bộ như bộ nhớ cache).
Bạn không gây áp lực lên một CPU hiện đại. Bạn có thể không thậm chí gây áp lực lên CPU của một chiếc smartphone hoặc Raspberry Pi với công việc đó. Bạn sẽ thực hiện một loạt các tương tác với cơ sở dữ liệu, có thể liên quan đến một bước nhảy qua mạng đến máy chủ nơi cơ sở dữ liệu được lưu trữ, hoặc nếu nó ở cục bộ, bạn sẽ thực hiện một loạt các thao tác đọc/ghi đĩa với một số cơ chế khóa phức tạp.
Nếu bạn viết nó bằng Java và nó có thể thực hiện 12 việc này mỗi giây, viết lại nó bằng C++ có thể sử dụng một phần mười chu kỳ CPU, nhưng bạn vẫn chỉ có thể thực hiện 12 việc này mỗi giây. Sự khác biệt duy nhất là phiên bản C++ sẽ mất nhiều thời gian hơn để chờ đĩa hoặc mạng. Ngoài ra, hầu hết các giây bạn có KHÔNG CÓ BẤT KỲ yêu cầu này để xử lý, và khi bạn có số không phải không, bạn có thể nhìn vào 8.
Điều này có nghĩa, chi phí hay gánh nặng do Java tập trung vào các lớp abstractions và sử dụng hệ thống thu gom rác (garbage-collected) cũng như biên dịch mã byte just-in-time là rất thấp, gần như bằng không. Điều này không có nghĩa là việc sử dụng Java là không đáng, mà chỉ đơn giản là làm cho việc áp dụng Java trở nên dễ dàng hơn.
Đã lập trình trong một thời gian dài. Tôi đã viết các ứng dụng doanh nghiệp khi CPU chạy ở “vài chục MHz” và GHz không phải là tốc độ CPU; nó dành cho lò vi sóng. Trong thời đại đó, dễ dàng bị giới hạn bởi CPU. Tôi đã viết rất nhiều C++ trong thời gian đó và tôi giỏi trong việc này. Một trong những thứ tôi viết là một proxy RADIUS thực hiện một số nhiệm vụ tương đối phức tạp và xử lý khoảng 150 yêu cầu mỗi giây trên một CPU 50 MHz. Tôi đã viết nó với thời hạn khoảng 4 tuần. Sau đó, tôi có cơ hội quay lại và tăng tốc nó. Tôi tin rằng tốc độ ban đầu thấp hơn nhiều, mặc dù nhu cầu ban đầu cũng thấp hơn. Tôi không thể làm điều đó bằng Java, một phần vì chúng tôi đang đẩy giới hạn của những gì máy có thể làm (và sự khác biệt lớn nhất mà tôi thực hiện là tạo ra các biểu diễn ngày càng gọn nhẹ hơn của một DFA), và một phần vì Java chưa được phát triển. Hệ thống cụ thể này cũng có một vấn đề đáng kể trong các ngôn ngữ không thu gom rác nhưng hầu như không phải là vấn đề trong các ngôn ngữ thu gom rác. Nó có một rò rỉ bộ nhớ, và đó là một rò rỉ chậm.
Trong khoảng thời gian từ sáu đến bảy tháng, nó sẽ rò rỉ đủ bộ nhớ khiến hệ điều hành phải “kill“ nó (Tôi không thể nhớ liệu nó đã đạt đến kích thước tối đa cho một process (tiến trình) hay nếu máy tính đã hết dung lượng trao đổi). Giải pháp ngắn hạn là duy trì một cửa sổ bảo trì ngắn một lần một tháng để khởi động lại máy hoặc đơn giản là kết thúc và khởi chạy lại tiến trình. Do tính chất chậm chạp của rò rỉ, việc gỡ lỗi trở nên khó khăn. Khoảng ba năm sau khi tôi viết nó, tôi đã tìm ra rò rỉ và sửa chữa nó. Hóa ra đó chỉ là một cái gì đó rất đơn giản, như một tập hợp chuỗi rỗng cụ thể nào đó hoặc một thứ tương tự không giải phóng một bộ đệm trong một đường dẫn thực hiện. Rò rỉ chỉ là vài byte mỗi lần xảy ra.
Vấn đề cụ thể đó có một giải pháp tạm thời, nhưng nếu không có giải pháp đó thì sao? Tôi đoán rằng tôi sẽ gỡ lỗi nhanh hơn một chút nếu đó là một trở ngại thực sự. Như là vài tháng, chứ không phải hàng năm, nhưng vẫn vậy, tôi đang nói về một lỗi sẽ mất hàng tháng để tìm ra nguyên nhân gốc rễ trong một chương trình có thời hạn chỉ vài tuần.
Java có nhiều khía cạnh trong thiết kế của nó giúp giảm khả năng xảy ra một số loại lỗi, và “tất cả” những gì nó yêu cầu bạn trả là một chút thời gian xử lý CPU. Ngoài ra, nó hơi dài dòng, nhưng đó là “cái giá nhỏ“ để phải trả để không phải theo dõi các rò rỉ bộ nhớ (được rồi, được rồi, bạn vẫn có thể có một tham chiếu treo (dangling reference) và bộ nhớ bị bỏ rơi (abandoned memory), nhưng những thứ đó dễ tìm thấy hơn rất nhiều).
Bây giờ bạn có thể giải quyết vấn đề đó trong C++ bằng cách “chỉ sử dụng đếm tham chiếu hoặc con trỏ thông minh thu gom rác mọi lúc cho mọi thứ”, nhưng đó chỉ là một trong số những vấn đề như vậy. Bạn nhanh chóng đến với “we are using C++ but nobody is allowed to use X, Y, or Z, and you must always do A, B, C.” Và nếu bạn cho phép các thư viện mà bạn import để tránh những quy tắc đó, bạn sẽ gặp vấn đề… Ngoài ra, nếu bạn luôn sử dụng cùng một loại abstractions khiến Java mất một số raw CPU performance, bạn sẽ phải trả cùng một giá. Như luôn sử dụng các vector được kiểm tra phạm vi? Tốt, bạn tránh được rất nhiều vấn đề về bảo mật (ngoại trừ dịch vụ cuối cùng), và bạn dễ dàng tìm thấy nhiều lỗi hơn, nhưng bạn không thể than phiền rằng Java chậm vì tất cả các truy cập mảng đều được kiểm tra phạm vi bởi vì đoán xem tất cả các truy cập mảng của bạn đang làm gì?
Dieter R.
Để lại một bình luận