CFSDN nhấn mạnh vào giá trị tạo ra nguồn mở và chúng tôi cam kết xây dựng nền tảng chia sẻ tài nguyên để mọi nhân viên CNTT có thể tìm thấy thế giới tuyệt vời của bạn tại đây.
Bài viết blog CFSDN này Tối ưu hóa hiệu suất JVM của máy ảo Java (2): Trình biên dịch được tác giả sưu tầm và biên soạn Nếu bạn quan tâm đến bài viết này, hãy nhớ thích nó.
Bài viết này sẽ là bài viết thứ hai trong loạt bài tối ưu hóa hiệu suất JVM (bài viết đầu tiên: Cổng thông tin) và trình biên dịch Java sẽ là nội dung cốt lõi được thảo luận trong bài viết này.
Trong bài viết này, tác giả (Eva Andreasson) lần đầu tiên giới thiệu các loại trình biên dịch khác nhau và so sánh hiệu suất chạy của trình biên dịch phía máy khách, trình biên dịch phía máy chủ và trình biên dịch nhiều lớp. Sau đó, ở cuối bài viết, một số phương pháp tối ưu hóa JVM phổ biến sẽ được giới thiệu, chẳng hạn như loại bỏ mã chết, nhúng mã và tối ưu hóa nội dung vòng lặp.
Tính năng đáng tự hào nhất của Java, “độc lập nền tảng”, bắt nguồn từ trình biên dịch Java. Các nhà phát triển phần mềm cố gắng hết sức để viết các ứng dụng Java tốt nhất và trình biên dịch chạy ẩn sẽ tạo ra mã thực thi hiệu quả dựa trên nền tảng đích. Các trình biên dịch khác nhau phù hợp với các yêu cầu ứng dụng khác nhau, do đó tạo ra các kết quả tối ưu hóa khác nhau. Do đó, nếu bạn có thể hiểu rõ hơn về cách hoạt động của trình biên dịch và biết nhiều loại trình biên dịch hơn thì bạn có thể tối ưu hóa chương trình Java của mình tốt hơn.
Bài viết này nêu bật và giải thích sự khác biệt giữa các trình biên dịch máy ảo Java khác nhau. Đồng thời, tôi cũng sẽ thảo luận về một số giải pháp tối ưu hóa thường được sử dụng bởi các trình biên dịch đúng lúc (JIT).
Trình biên dịch là gì?
Nói một cách đơn giản, trình biên dịch lấy một chương trình ngôn ngữ lập trình làm đầu vào và một chương trình ngôn ngữ thực thi khác làm đầu ra. Javac là trình biên dịch phổ biến nhất. Nó tồn tại trong tất cả các JDK. Javac lấy mã java làm đầu ra và chuyển đổi nó thành mã thực thi JVM - mã byte. Các mã byte này được lưu trữ trong các tệp có đuôi .class và được tải vào môi trường thời gian chạy java khi chương trình java khởi động.
Mã byte không thể được CPU đọc trực tiếp. Nó cũng cần được dịch sang ngôn ngữ lệnh máy mà nền tảng hiện tại có thể hiểu được. Có một trình biên dịch khác trong JVM chịu trách nhiệm dịch mã byte thành các hướng dẫn có thể thực thi được trên nền tảng đích. Một số trình biên dịch JVM yêu cầu một số cấp độ của giai đoạn mã bytecode. Ví dụ, một trình biên dịch có thể cần phải trải qua một số dạng giai đoạn trung gian khác nhau trước khi dịch mã byte thành các lệnh máy.
Từ quan điểm của thuyết bất khả tri về nền tảng, chúng tôi hy vọng rằng mã của chúng tôi có thể độc lập với nền tảng nhất có thể.
Để đạt được điều này, chúng tôi làm việc ở cấp độ dịch thuật cuối cùng—từ biểu diễn mã byte thấp nhất đến mã máy thực—mức thực sự liên kết mã thực thi với kiến trúc của một nền tảng cụ thể. Ở cấp độ cao nhất, chúng ta có thể chia trình biên dịch thành trình biên dịch tĩnh và trình biên dịch động. Chúng ta có thể chọn trình biên dịch thích hợp dựa trên môi trường thực thi mục tiêu, kết quả tối ưu hóa mà chúng ta mong muốn và những hạn chế về tài nguyên mà chúng ta cần đáp ứng. Trong bài viết trước, chúng tôi đã thảo luận ngắn gọn về trình biên dịch tĩnh và trình biên dịch động, và trong các phần sau, chúng tôi sẽ giải thích chúng sâu hơn.
Biên dịch tĩnh VS biên dịch động.
javac mà chúng tôi đã đề cập trước đó là một ví dụ về biên dịch tĩnh. Với trình biên dịch tĩnh, mã đầu vào được diễn giải một lần và đầu ra là dạng chương trình sẽ được thực thi trong tương lai. Trừ khi bạn cập nhật mã nguồn và biên dịch lại (thông qua trình biên dịch), kết quả thực thi của chương trình sẽ không bao giờ thay đổi: điều này là do đầu vào là đầu vào tĩnh và trình biên dịch là trình biên dịch tĩnh.
Với trình biên dịch tĩnh, chương trình sau:
Sao chép mãMã này như sau:
staticint add7(int x ){ trả về x+7;}
。
sẽ được chuyển đổi thành mã byte tương tự như sau:
Sao chép mãMã này như sau:
iload0 bipush 7 iadd ireturn
Trình biên dịch động biên dịch động một ngôn ngữ sang ngôn ngữ khác Cái gọi là động có nghĩa là nó biên dịch trong khi chương trình đang chạy - biên dịch trong khi chạy! Ưu điểm của việc biên dịch và tối ưu hóa động là nó có thể xử lý một số thay đổi khi ứng dụng được tải. Thời gian chạy Java thường chạy trong các môi trường không thể đoán trước hoặc thậm chí thay đổi, do đó việc biên dịch động rất phù hợp với thời gian chạy Java. Hầu hết các JVM đều sử dụng trình biên dịch động, chẳng hạn như trình biên dịch JIT. Điều đáng chú ý là việc biên dịch động và tối ưu hóa mã yêu cầu sử dụng một số cấu trúc dữ liệu, luồng và tài nguyên CPU bổ sung. Trình phân tích bối cảnh mã byte hoặc trình tối ưu hóa càng tiên tiến thì càng tiêu tốn nhiều tài nguyên. Nhưng những chi phí này không đáng kể so với những cải thiện hiệu suất đáng kể.
。
Các loại JVM và tính độc lập của nền tảng Java.
Tất cả các triển khai JVM đều có một tính năng chung: biên dịch mã byte thành các lệnh máy. Một số JVM diễn giải mã khi ứng dụng được tải và sử dụng bộ đếm hiệu suất để tìm mã "nóng"; một số khác thực hiện việc này thông qua quá trình biên dịch. Vấn đề chính của việc biên dịch là việc tập trung hóa đòi hỏi nhiều tài nguyên nhưng nó cũng có thể dẫn đến tối ưu hóa hiệu suất tốt hơn.
Nếu bạn là người mới làm quen với Java, sự phức tạp của JVM chắc chắn sẽ khiến bạn bối rối. Nhưng tin tốt là bạn không cần phải tìm hiểu nó! JVM sẽ quản lý việc biên dịch và tối ưu hóa mã và bạn không cần phải lo lắng về hướng dẫn máy cũng như cách viết mã sao cho phù hợp nhất với kiến trúc của nền tảng mà chương trình đang chạy.
Từ mã byte java đến tệp thực thi.
Sau khi mã java của bạn được biên dịch thành mã byte, bước tiếp theo là dịch hướng dẫn mã byte thành mã máy. Bước này có thể được thực hiện thông qua trình thông dịch hoặc trình biên dịch.
giải thích.
Giải thích là cách đơn giản nhất để biên dịch mã byte. Trình thông dịch tìm lệnh phần cứng tương ứng với từng lệnh mã byte dưới dạng bảng tra cứu, sau đó gửi nó đến CPU để thực thi.
Bạn có thể coi trình thông dịch giống như một cuốn từ điển: với mỗi từ cụ thể (lệnh bytecode), có một bản dịch cụ thể (lệnh mã máy) tương ứng với nó. Bởi vì trình thông dịch ngay lập tức thực thi một lệnh mỗi khi đọc nó nên phương pháp này không thể tối ưu hóa một tập hợp các lệnh. Đồng thời, mỗi khi một bytecode được gọi, nó phải được thông dịch ngay lập tức nên trình thông dịch chạy rất chậm. Trình thông dịch thực thi mã theo cách rất chính xác, nhưng do tập lệnh đầu ra không được tối ưu hóa nên nó có thể không tạo ra kết quả tối ưu cho bộ xử lý của nền tảng đích.
Biên dịch.
Trình biên dịch tải tất cả mã sẽ được thực thi vào thời gian chạy. Bằng cách này, nó có thể tham chiếu đến toàn bộ hoặc một phần bối cảnh thời gian chạy khi dịch mã byte. Các quyết định mà nó đưa ra dựa trên phân tích biểu đồ mã. Chẳng hạn như so sánh các nhánh thực thi khác nhau và tham chiếu dữ liệu bối cảnh thời gian chạy.
Sau khi chuỗi mã byte được dịch sang tập lệnh mã máy, việc tối ưu hóa có thể được thực hiện dựa trên tập lệnh mã máy này. Tập lệnh được tối ưu hóa được lưu trữ trong một cấu trúc gọi là bộ đệm mã. Khi các mã byte này được thực thi lại, mã được tối ưu hóa có thể được lấy trực tiếp từ bộ đệm mã này và được thực thi. Trong một số trường hợp, trình biên dịch không sử dụng trình tối ưu hóa để tối ưu hóa mã mà sử dụng trình tự tối ưu hóa mới - "đếm hiệu suất".
Ưu điểm của việc sử dụng bộ đệm mã là các hướng dẫn tập hợp kết quả có thể được thực thi ngay lập tức mà không cần diễn giải lại hoặc biên dịch! .
Điều này có thể giảm đáng kể thời gian thực thi, đặc biệt đối với các ứng dụng Java trong đó một phương thức được gọi nhiều lần.
tối ưu hóa.
Với việc giới thiệu tính năng biên dịch động, chúng ta có cơ hội chèn các bộ đếm hiệu suất. Ví dụ: trình biên dịch chèn một bộ đếm hiệu suất được tăng lên mỗi khi một khối mã byte (tương ứng với một phương thức cụ thể) được gọi. Trình biên dịch sử dụng các bộ đếm này để tìm "khối nóng" để có thể xác định khối mã nào có thể được tối ưu hóa nhằm mang lại sự cải thiện hiệu suất lớn nhất cho ứng dụng. Dữ liệu phân tích hiệu suất thời gian chạy có thể giúp trình biên dịch đưa ra nhiều quyết định tối ưu hóa hơn ở trạng thái trực tuyến, từ đó cải thiện hơn nữa hiệu quả thực thi mã. Bởi vì chúng tôi nhận được dữ liệu phân tích hiệu suất mã ngày càng chính xác hơn, chúng tôi có thể tìm thấy nhiều điểm tối ưu hóa hơn và đưa ra quyết định tối ưu hóa tốt hơn, chẳng hạn như: cách sắp xếp các lệnh tốt hơn và liệu có nên sử dụng bộ lệnh hiệu quả hơn hay không. có nên loại bỏ các hoạt động dư thừa, v.v.
Ví dụ.
Hãy xem xét mã java sau.
Sao chép mãMã này như sau:
staticint add7(int x ){ trả về x+7;}
Javac sẽ dịch tĩnh nó sang mã byte sau:
Sao chép mãMã này như sau:
iload0 .
。
bipush 7.
iadd
trở về
Khi phương thức này được gọi, mã byte sẽ được biên dịch động thành các lệnh của máy. Phương pháp này có thể được tối ưu hóa khi bộ đếm hiệu suất (nếu có) đạt đến ngưỡng được chỉ định. Các kết quả được tối ưu hóa có thể trông giống như tập lệnh máy sau:
Sao chép mãMã này như sau:
lea rax,[rdx+7] ret
。
Các trình biên dịch khác nhau phù hợp cho các ứng dụng khác nhau.
Các ứng dụng khác nhau có nhu cầu khác nhau. Các ứng dụng phía máy chủ doanh nghiệp thường cần chạy trong thời gian dài nên chúng thường muốn tối ưu hóa hiệu suất hơn; trong khi các ứng dụng phía máy khách có thể muốn thời gian phản hồi nhanh hơn và tiêu thụ ít tài nguyên hơn. Hãy thảo luận về ba trình biên dịch khác nhau cũng như ưu và nhược điểm của chúng.
Trình biên dịch phía máy khách.
C1 là một trình biên dịch tối ưu hóa nổi tiếng. Khi khởi động JVM, hãy thêm tham số -client để khởi động trình biên dịch. Theo tên của nó, chúng ta có thể thấy rằng C1 là một trình biên dịch máy khách. Đó là lý tưởng cho các ứng dụng khách có ít tài nguyên hệ thống sẵn có hoặc yêu cầu khởi động nhanh. C1 thực hiện tối ưu hóa mã bằng cách sử dụng bộ đếm hiệu suất. Đây là một phương pháp tối ưu hóa đơn giản, ít can thiệp vào mã nguồn.
Trình biên dịch phía máy chủ.
Đối với các ứng dụng chạy dài (chẳng hạn như ứng dụng doanh nghiệp phía máy chủ), việc sử dụng trình biên dịch phía máy khách có thể không đủ. Lúc này chúng ta nên chọn trình biên dịch phía máy chủ như C2. Trình tối ưu hóa có thể được khởi động bằng cách thêm –server vào dòng khởi động JVM. Vì hầu hết các ứng dụng phía máy chủ thường chạy trong thời gian dài nên bạn sẽ có thể thu thập nhiều dữ liệu tối ưu hóa hiệu suất hơn bằng cách sử dụng trình biên dịch C2 so với các ứng dụng phía máy khách nhẹ, chạy ngắn. Do đó, bạn cũng sẽ có thể áp dụng các kỹ thuật và thuật toán tối ưu hóa nâng cao hơn.
Mẹo: Khởi động trình biên dịch phía máy chủ của bạn.
Đối với việc triển khai phía máy chủ, trình biên dịch có thể mất chút thời gian để tối ưu hóa các mã "nóng" đó. Vì vậy việc triển khai phía máy chủ thường yêu cầu giai đoạn "khởi động". Vì vậy, khi thực hiện đo lường hiệu suất khi triển khai phía máy chủ, hãy luôn đảm bảo ứng dụng của bạn đã đạt đến trạng thái ổn định! Việc cho trình biên dịch đủ thời gian để biên dịch sẽ mang lại nhiều lợi ích cho ứng dụng của bạn.
Trình biên dịch phía máy chủ có thể thu được nhiều dữ liệu điều chỉnh hiệu suất hơn trình biên dịch phía máy khách, do đó nó có thể thực hiện phân tích nhánh phức tạp hơn và tìm đường dẫn tối ưu hóa với hiệu suất tốt hơn. Bạn càng có nhiều dữ liệu phân tích hiệu suất thì kết quả phân tích ứng dụng của bạn sẽ càng tốt. Tất nhiên, việc thực hiện phân tích hiệu suất sâu rộng đòi hỏi nhiều tài nguyên trình biên dịch hơn. Ví dụ: nếu JVM sử dụng trình biên dịch C2, nó sẽ cần sử dụng nhiều chu kỳ CPU hơn, bộ đệm mã lớn hơn, v.v.
Biên dịch đa cấp.
Quá trình biên dịch nhiều tầng kết hợp quá trình biên dịch phía máy khách và quá trình biên dịch phía máy chủ. Azul là người đầu tiên triển khai tính năng biên dịch nhiều lớp trong Zing JVM của mình. Gần đây, công nghệ này đã được Oracle Java Hotspot JVM (sau Java SE7) áp dụng. Trình biên dịch nhiều tầng kết hợp các ưu điểm của trình biên dịch phía máy khách và phía máy chủ. Trình biên dịch máy khách hoạt động trong hai trường hợp: khi ứng dụng khởi động và khi bộ đếm hiệu suất đạt đến ngưỡng cấp thấp hơn để thực hiện tối ưu hóa hiệu suất. Trình biên dịch phía máy khách cũng chèn bộ đếm hiệu suất và chuẩn bị tập lệnh để trình biên dịch phía máy chủ sử dụng sau này, chịu trách nhiệm tối ưu hóa nâng cao. Biên dịch nhiều lớp là một phương pháp phân tích hiệu suất với mức sử dụng tài nguyên cao. Vì nó thu thập dữ liệu trong quá trình hoạt động của trình biên dịch có tác động thấp nên dữ liệu này có thể được sử dụng sau này trong các hoạt động tối ưu hóa nâng cao hơn. Cách tiếp cận này cung cấp nhiều thông tin hơn so với việc phân tích các bộ đếm bằng cách sử dụng mã được giải thích.
Hình 1 mô tả so sánh hiệu suất của trình thông dịch, biên dịch phía máy khách, biên dịch phía máy chủ và biên dịch nhiều lớp. Trục X là thời gian thực hiện (đơn vị thời gian) và trục Y là hiệu suất (số lượng thao tác trên một đơn vị thời gian).

Hình 1. So sánh hiệu năng của trình biên dịch.
So với mã được diễn giải thuần túy, việc sử dụng trình biên dịch máy khách có thể cải thiện hiệu suất gấp 5 đến 10 lần. Mức tăng hiệu suất mà bạn đạt được tùy thuộc vào hiệu quả của trình biên dịch, các loại trình tối ưu hóa có sẵn và mức độ thiết kế của ứng dụng phù hợp với nền tảng mục tiêu. Nhưng đối với các nhà phát triển chương trình, mục cuối cùng thường có thể bị bỏ qua.
So với trình biên dịch phía máy khách, trình biên dịch phía máy chủ thường có thể cải thiện hiệu suất khoảng 30% đến 50%. Trong hầu hết các trường hợp, cải thiện hiệu suất thường phải trả giá bằng việc tiêu thụ tài nguyên.
Biên dịch đa cấp kết hợp các ưu điểm của cả hai trình biên dịch. Quá trình biên dịch phía máy khách có thời gian khởi động ngắn hơn và có thể thực hiện tối ưu hóa nhanh chóng; quá trình biên dịch phía máy chủ có thể thực hiện các hoạt động tối ưu hóa nâng cao hơn trong quá trình thực hiện tiếp theo.
Một số tối ưu hóa trình biên dịch phổ biến.
Cho đến nay, chúng ta đã thảo luận về ý nghĩa của việc tối ưu hóa mã cũng như cách thức và thời điểm JVM thực hiện tối ưu hóa mã. Tiếp theo, tôi sẽ kết thúc bài viết này bằng cách giới thiệu một số phương pháp tối ưu hóa thực sự được các trình biên dịch sử dụng. Tối ưu hóa JVM thực sự xảy ra ở giai đoạn mã byte (hoặc giai đoạn biểu diễn ngôn ngữ cấp thấp hơn), nhưng ngôn ngữ Java sẽ được sử dụng ở đây để minh họa các phương pháp tối ưu hóa này. Tất nhiên, không thể đề cập đến tất cả các phương pháp tối ưu hóa JVM trong phần này; tôi hy vọng rằng những phần giới thiệu này sẽ truyền cảm hứng cho bạn tìm hiểu hàng trăm phương pháp tối ưu hóa nâng cao hơn và đổi mới công nghệ trình biên dịch.
Loại bỏ mã chết.
Loại bỏ mã chết, như tên cho thấy, là loại bỏ mã sẽ không bao giờ được thực thi - tức là mã "chết".
Nếu trình biên dịch tìm thấy một số lệnh dư thừa trong quá trình hoạt động, nó sẽ loại bỏ các lệnh này khỏi tập lệnh thực thi. Ví dụ, trong Liệt kê 1, một trong các biến sẽ không bao giờ được sử dụng sau khi gán cho nó, do đó câu lệnh gán có thể bị bỏ qua hoàn toàn trong khi thực thi. Tương ứng với hoạt động ở cấp độ bytecode, giá trị biến không bao giờ cần được tải vào thanh ghi. Không phải tải nghĩa là tiêu tốn ít thời gian CPU hơn, do đó tăng tốc độ thực thi mã, cuối cùng dẫn đến ứng dụng nhanh hơn - nếu mã tải được gọi nhiều lần mỗi giây, hiệu quả tối ưu hóa sẽ rõ ràng hơn.
Liệt kê 1 sử dụng mã Java để minh họa một ví dụ về việc gán một giá trị cho một biến sẽ không bao giờ được sử dụng.
Liệt kê 1. Mã chết.
Sao chép mãMã này như sau:
int timeToScaleMyApp(boolean endlessOfResources){
int reArchitect =24;
int patchByClustering =15;
int useZing =2;
nếu(tài nguyên vô tận)
trả về reArchitect + useZing;
khác
trả lại useZing;
}
。
Ở giai đoạn mã byte, nếu một biến được tải nhưng không bao giờ được sử dụng, trình biên dịch có thể phát hiện và loại bỏ mã chết, như trong Liệt kê 2. Nếu thao tác tải này không bao giờ được thực hiện thì thời gian CPU có thể được tiết kiệm và tốc độ thực thi của chương trình có thể được cải thiện.
Liệt kê 2. Mã được tối ưu hóa.
Sao chép mãMã này như sau:
int timeToScaleMyApp(boolean endlessOfResources){
int reArchitect =24; //hoạt động không cần thiết đã bị xóa ở đây…
int useZing =2;
nếu(tài nguyên vô tận)
trả về reArchitect + useZing;
khác
trả lại useZing;
}
。
Loại bỏ sự dư thừa là một phương pháp tối ưu hóa giúp cải thiện hiệu suất ứng dụng bằng cách loại bỏ các hướng dẫn trùng lặp.
Nhiều tối ưu hóa cố gắng loại bỏ các hướng dẫn nhảy mức lệnh của máy (chẳng hạn như JMP trong kiến trúc x86). Lệnh nhảy này là một lệnh tiêu tốn rất nhiều tài nguyên so với các lệnh ASSEMBLY khác. Đó là lý do tại sao chúng tôi muốn giảm bớt hoặc loại bỏ loại hướng dẫn này. Nhúng mã là một phương pháp tối ưu hóa rất thực tế và nổi tiếng để loại bỏ các hướng dẫn chuyển. Bởi vì việc thực hiện các lệnh nhảy rất tốn kém nên việc nhúng một số phương thức nhỏ thường được gọi là vào thân hàm sẽ mang lại nhiều lợi ích. Danh sách 3-5 thể hiện những lợi ích của việc nội tuyến.
Liệt kê 3. Gọi phương thức.
Sao chép mãMã này như sau:
int whenToEvaluateZing(int y){ trả về daysLeft(y)+ daysLeft(0)+ daysLeft(y+1);}
Liệt kê 4. Phương thức được gọi.
Sao chép mãMã này như sau:
int daysLeft(int x){ nếu(x == 0) trả về 0; nếu không thì trả về x -1;}
Liệt kê 5. Các phương thức nội tuyến.
Sao chép mãMã này như sau:
int khiĐánh giáZing(int y){
int nhiệt độ = 0;
nếu(y ==0)
nhiệt độ +=0;
khác
nhiệt độ += y -1;
nếu(0==0)
nhiệt độ +=0;
khác
nhiệt độ +=0-1;
nếu(y+1==0)
nhiệt độ +=0;
khác
nhiệt độ +=(y +1)-1;
nhiệt độ trở về;
}
Trong Liệt kê 3-5, chúng ta có thể thấy rằng một phương thức nhỏ được gọi ba lần trong một thân phương thức khác và điều chúng ta muốn minh họa là: chi phí của việc nhúng phương thức được gọi trực tiếp vào mã sẽ ít hơn so với việc thực hiện ba lần nhảy. chuyển hướng dẫn.
。
Việc nhúng một phương thức không thường được gọi có thể không tạo ra sự khác biệt lớn, nhưng việc nhúng một phương thức được gọi là "nóng" (một phương thức thường được gọi) có thể mang lại nhiều cải tiến về hiệu suất. Mã nhúng thường có thể được tối ưu hóa hơn nữa, như trong Liệt kê 6.
Liệt kê 6. Sau khi mã được nhúng, sẽ đạt được sự tối ưu hóa hơn nữa.
Sao chép mãMã này như sau:
int whenToEvaluateZing(int y){ nếu(y ==0) trả về y; nếu không thì nếu(y ==-1) trả về y -1; nếu không thì trả về y + y -1;}
。
Tối ưu hóa vòng lặp.
Tối ưu hóa vòng lặp đóng vai trò quan trọng trong việc giảm chi phí bổ sung khi thực hiện phần thân vòng lặp. Chi phí tăng thêm ở đây liên quan đến những bước nhảy tốn kém, nhiều lần kiểm tra điều kiện và các quy trình không được tối ưu hóa (nghĩa là một loạt tập lệnh không có hoạt động thực tế và tiêu tốn thêm chu kỳ CPU). Có nhiều loại tối ưu hóa vòng lặp Dưới đây là một số cách tối ưu hóa vòng lặp phổ biến hơn:
Hợp nhất thân vòng lặp: Khi hai thân vòng lặp liền kề thực hiện cùng số vòng lặp, trình biên dịch sẽ cố gắng hợp nhất hai thân vòng lặp. Nếu hai thân vòng lặp hoàn toàn độc lập với nhau thì chúng cũng có thể được thực thi đồng thời (song song).
Vòng lặp đảo ngược: Về cơ bản nhất, bạn thay thế vòng lặp while bằng vòng lặp do-while. Vòng lặp do-while này được đặt bên trong câu lệnh if. Sự thay thế này sẽ làm giảm hai thao tác nhảy nhưng nó sẽ làm tăng khả năng phán đoán có điều kiện, do đó làm tăng số lượng mã. Kiểu tối ưu hóa này là một ví dụ tuyệt vời về việc trao đổi nhiều tài nguyên hơn để lấy mã hiệu quả hơn - trình biên dịch cân nhắc chi phí và lợi ích, đồng thời đưa ra quyết định linh hoạt trong thời gian chạy.
Sắp xếp lại phần thân vòng lặp: Sắp xếp lại phần thân vòng lặp để toàn bộ phần thân vòng lặp có thể được lưu trữ trong bộ đệm.
Bỏ cuộn thân vòng lặp: Giảm số lần kiểm tra và nhảy điều kiện vòng lặp. Bạn có thể coi điều này như việc thực hiện một số lần lặp "nội tuyến" mà không cần phải thực hiện kiểm tra có điều kiện. Việc hủy cuộn vòng lặp cũng mang lại những rủi ro nhất định vì nó có thể làm giảm hiệu suất bằng cách ảnh hưởng đến đường dẫn và một số lượng lớn lệnh tìm nạp dư thừa. Một lần nữa, việc hủy kiểm soát phần thân vòng lặp có được quyết định bởi trình biên dịch trong thời gian chạy hay không và việc hủy kiểm soát sẽ rất đáng giá nếu nó mang lại sự cải thiện hiệu suất cao hơn.
Trên đây là tổng quan về cách các trình biên dịch ở cấp mã byte (hoặc cấp thấp hơn) có thể cải thiện hiệu suất của các ứng dụng trên nền tảng đích. Những gì chúng ta đã thảo luận là một số phương pháp tối ưu hóa phổ biến và phổ biến. Do không gian có hạn nên chúng tôi chỉ đưa ra một số ví dụ đơn giản. Mục đích của chúng tôi là khơi dậy sự quan tâm của bạn đến việc nghiên cứu chuyên sâu về tối ưu hóa thông qua cuộc thảo luận đơn giản ở trên.
Kết luận: Những điểm cần suy ngẫm và nổi bật.
Chọn các trình biên dịch khác nhau tùy theo mục đích khác nhau.
1. Trình thông dịch là hình thức đơn giản nhất để dịch mã byte thành hướng dẫn máy. Việc thực hiện nó dựa trên bảng tra cứu hướng dẫn. 2. Trình biên dịch có thể tối ưu hóa dựa trên bộ đếm hiệu suất, nhưng nó yêu cầu tiêu tốn một số tài nguyên bổ sung (bộ đệm mã, luồng tối ưu hóa, v.v.). 3. Trình biên dịch máy khách có thể cải thiện hiệu suất gấp 5 đến 10 lần so với trình thông dịch. 4. Trình biên dịch phía máy chủ có thể cải thiện hiệu suất từ 30% đến 50% so với trình biên dịch phía máy khách, nhưng nó đòi hỏi nhiều tài nguyên hơn. 5. Biên dịch nhiều lớp kết hợp các ưu điểm của cả hai. Sử dụng trình biên dịch phía máy khách để có thời gian phản hồi nhanh hơn, sau đó sử dụng trình biên dịch phía máy chủ để tối ưu hóa mã được gọi thường xuyên.
Có nhiều cách tối ưu hóa mã có thể thực hiện được ở đây. Một công việc quan trọng của trình biên dịch là phân tích tất cả các phương pháp tối ưu hóa có thể có, sau đó cân nhắc chi phí của các phương pháp tối ưu hóa khác nhau so với việc cải thiện hiệu suất do các lệnh cuối cùng của máy mang lại.
Cuối cùng, bài viết này về Tối ưu hóa hiệu suất JVM của máy ảo Java (2): Trình biên dịch kết thúc tại đây. Nếu bạn muốn biết thêm về Tối ưu hóa hiệu suất JVM của máy ảo Java (2): Trình biên dịch, vui lòng tìm kiếm bài viết CFSDN. Tôi hy vọng bạn sẽ ủng hộ blog của tôi trong tương lai! .
Tôi là một lập trình viên xuất sắc, rất giỏi!