Hiểu sự khác biệt: trình thông dịch truyền thống, trình biên dịch JIT, trình thông dịch JIT và trình biên dịch AOT


130

Tôi đang cố gắng tìm hiểu sự khác biệt giữa trình thông dịch truyền thống, trình biên dịch JIT, trình thông dịch JIT và trình biên dịch AOT.

Trình thông dịch chỉ là một máy (ảo hoặc vật lý) thực thi các hướng dẫn trong một số ngôn ngữ máy tính. Theo nghĩa đó, JVM là một trình thông dịch và CPU vật lý là các trình thông dịch.

Biên dịch trước thời gian chỉ đơn giản là biên dịch mã sang một số ngôn ngữ trước khi thực hiện (giải thích) nó.

Tuy nhiên tôi không chắc chắn về các định nghĩa chính xác của trình biên dịch JIT và trình thông dịch JIT.

Theo một định nghĩa tôi đọc, biên dịch JIT chỉ đơn giản là biên dịch mã chỉ trước khi giải thích nó.

Vậy về cơ bản, biên dịch JIT là biên dịch AOT, được thực hiện ngay trước khi thực hiện (diễn giải)?

Và một trình thông dịch JIT, là một chương trình chứa cả trình biên dịch JIT và trình thông dịch, và biên dịch mã (JITs) ngay trước khi nó diễn giải nó?

Hãy làm rõ sự khác biệt.


4
Tại sao bạn lại tin rằng có một sự khác biệt giữa "trình biên dịch JIT" và "trình thông dịch JIT"? Về cơ bản, chúng là hai từ khác nhau cho cùng một thứ. Khái niệm chung về JIT là như nhau, nhưng có rất nhiều kỹ thuật triển khai không thể đơn giản được chia thành "trình biên dịch" và "trình thông dịch".
Greg Hewgill

2
Đọc các wiki về biên dịch Just In Time , trình biên dịch AOT , Trình biên dịch , Phiên dịch , mã byte và cả cuốn sách của Queinnec Lisp in Small Pieces
Basile Starynkevitch

Câu trả lời:


198

Tổng quan

Một phiên dịch cho ngôn ngữ X là một chương trình (hoặc một cái máy, hoặc chỉ một số loại cơ chế nói chung) mà thực hiện bất kỳ chương trình p viết bằng ngôn ngữ X như vậy mà nó thực hiện các hiệu ứng và đánh giá kết quả theo quy định của thông số kỹ thuật của X . CPU thường là thông dịch viên cho các bộ hướng dẫn tương ứng của chúng, mặc dù CPU máy trạm hiệu suất cao hiện đại thực sự phức tạp hơn thế; họ thực sự có thể có một tập lệnh riêng độc quyền cơ bản và dịch (biên dịch) hoặc giải thích tập lệnh công khai có thể nhìn thấy bên ngoài.

Một trình biên dịch từ X đến Y là một chương trình (hoặc một cái máy, hoặc chỉ một số loại cơ chế nói chung) mà dịch bất kỳ chương trình p từ một số ngôn ngữ X vào một chương trình ngữ nghĩa tương đương p ' trong một số ngôn ngữ Y theo cách như vậy mà ngữ nghĩa của chương trình được bảo quản, tức là giải thích p ' với một thông dịch viên cho Y sẽ mang lại kết quả tương tự và có những ảnh hưởng tương tự như giải thích p với một thông dịch viên cho X . (Lưu ý rằng XY có thể là cùng một ngôn ngữ.)

Các thuật ngữ Ahead-of-Time (AOT)Just-in-Time (JIT) đề cập đến khi quá trình biên dịch diễn ra: "thời gian" được đề cập trong các thuật ngữ đó là "runtime", tức là trình biên dịch JIT biên dịch chương trình như hiện tại đang chạy , trình biên dịch AOT biên dịch chương trình trước khi nó chạy . Lưu ý rằng điều này đòi hỏi trình biên dịch JIT từ ngôn ngữ X sang ngôn ngữ Y bằng cách nào đó phải hoạt động cùng với trình thông dịch cho ngôn ngữ Y, nếu không sẽ không có cách nào để chạy chương trình. (Vì vậy, ví dụ, trình biên dịch JIT biên dịch mã JavaScript thành mã máy x86 không có ý nghĩa nếu không có CPU x86; nó biên dịch chương trình trong khi nó đang chạy, nhưng không có CPU x86 thì chương trình sẽ không chạy.)

Lưu ý rằng sự khác biệt này không có ý nghĩa đối với người phiên dịch: một thông dịch viên chạy chương trình. Ý tưởng về trình thông dịch AOT chạy chương trình trước khi nó chạy hoặc trình thông dịch JIT chạy chương trình trong khi nó đang chạy là vô nghĩa.

Vì vậy chúng tôi có:

  • Trình biên dịch AOT: biên dịch trước khi chạy
  • Trình biên dịch JIT: biên dịch trong khi chạy
  • thông dịch viên: chạy

Trình biên dịch JIT

Trong họ các trình biên dịch JIT, vẫn còn nhiều khác biệt về việc khi nào chúng biên dịch chính xác , tần suất và mức độ chi tiết.

Ví dụ, trình biên dịch JIT trong CLR của Microsoft chỉ biên dịch mã một lần (khi được tải) và biên dịch toàn bộ một cụm tại một thời điểm. Các trình biên dịch khác có thể thu thập thông tin trong khi chương trình đang chạy và biên dịch lại mã nhiều lần khi có thông tin mới cho phép chúng tối ưu hóa nó tốt hơn. Một số trình biên dịch JIT thậm chí có khả năng tối ưu hóa mã. Bây giờ, bạn có thể tự hỏi tại sao một người muốn làm điều đó? Tối ưu hóa cho phép bạn thực hiện tối ưu hóa rất mạnh mẽ mà thực sự không an toàn: nếu hóa ra bạn quá hung hăng, bạn có thể sao lưu lại, trong khi đó, với trình biên dịch JIT không thể tối ưu hóa, bạn không thể chạy tối ưu hóa tích cực ở nơi đầu tiên.

Trình biên dịch JIT có thể biên dịch một số đơn vị mã tĩnh trong một lần (một mô-đun, một lớp, một hàm, một phương thức, ví dụ, chúng thường được gọi là JIT phương thức tại một thời điểm ) hoặc chúng có thể theo dõi động thực thi mã để tìm dấu vết động (thường là các vòng lặp) mà sau đó chúng sẽ biên dịch (chúng được gọi là JIT theo dõi ).

Kết hợp phiên dịch và trình biên dịch

Trình thông dịch và trình biên dịch có thể được kết hợp thành một công cụ thực thi ngôn ngữ duy nhất. Có hai kịch bản điển hình trong đó điều này được thực hiện.

Kết hợp một trình biên dịch AOT từ X đến Y với một thông dịch viên cho Y . Ở đây, điển hình X là một số ngôn ngữ cấp cao hơn được con người tối ưu hóa để dễ đọc, trong khi Ylà một ngôn ngữ nhỏ gọn (thường là một số loại mã byte) được tối ưu hóa cho tính dễ hiểu của máy móc. Ví dụ, công cụ thực thi Python CPython có trình biên dịch AOT biên dịch mã nguồn Python thành mã byte CPython và trình thông dịch phiên dịch mã byte của CPython. Tương tự, công cụ thực thi YARV Ruby có trình biên dịch AOT biên dịch mã nguồn Ruby thành mã byte YARV và trình thông dịch phiên dịch mã byte YARV. Tại sao bạn muốn làm điều đó? Ruby và Python đều là những ngôn ngữ cấp cao và hơi phức tạp, vì vậy trước tiên chúng tôi biên dịch chúng thành một ngôn ngữ dễ phân tích và dễ diễn giải hơn, sau đó diễn giải ngôn ngữ đó .

Cách khác để kết hợp trình thông dịch và trình biên dịch là một công cụ thực thi chế độ hỗn hợp . Ở đây, chúng "trộn" hai "chế độ" thực hiện cùng một ngôn ngữ với nhau, tức là một thông dịch viên cho X và một trình biên dịch JIT từ X đến Y . (Vì vậy, sự khác biệt ở đây là trong trường hợp trên, chúng tôi đã có nhiều "giai đoạn" với trình biên dịch biên dịch chương trình và sau đó đưa kết quả vào trình thông dịch, ở đây chúng tôi có hai hoạt động song song trên cùng một ngôn ngữ. ) Mã được biên dịch bởi trình biên dịch có xu hướng chạy nhanh hơn mã được thực thi bởi trình thông dịch, nhưng thực sự biên dịch mã trước tiên cần có thời gian (và đặc biệt, nếu bạn muốn tối ưu hóa mạnh mã để chạythực sự nhanh, mất rất nhiều thời gian). Vì vậy, để kết nối lần này khi trình biên dịch JIT đang bận biên dịch mã, trình thông dịch có thể bắt đầu chạy mã và khi JIT kết thúc biên dịch, chúng ta có thể chuyển thực thi sang mã được biên dịch. Điều này có nghĩa là chúng tôi nhận được cả hiệu suất tốt nhất có thể của mã được biên dịch, nhưng chúng tôi không phải đợi quá trình biên dịch kết thúc và ứng dụng của chúng tôi bắt đầu chạy ngay lập tức (mặc dù không nhanh như có thể).

Đây thực sự chỉ là ứng dụng đơn giản nhất có thể có của một công cụ thực thi chế độ hỗn hợp. Chẳng hạn, nhiều khả năng thú vị hơn là không bắt đầu biên dịch ngay mà hãy để trình thông dịch chạy một chút và thu thập số liệu thống kê, thông tin hồ sơ, thông tin loại, thông tin về khả năng các nhánh có điều kiện cụ thể được thực hiện, phương thức nào được gọi thường xuyên nhất vv và sau đó cung cấp thông tin động này cho trình biên dịch để nó có thể tạo mã được tối ưu hóa hơn. Đây cũng là một cách để thực hiện tối ưu hóa mà tôi đã nói ở trên: nếu hóa ra bạn quá tích cực trong việc tối ưu hóa, bạn có thể vứt bỏ (một phần) mã và quay lại phiên dịch. JVM HotSpot làm điều này, ví dụ. Nó chứa cả trình thông dịch cho mã byte JVM cũng như trình biên dịch cho mã byte JVM. (Trong thực tế,hai trình biên dịch!)

Nó cũng có thể và trên thực tế phổ biến để kết hợp hai phương pháp: hai giai đoạn với người đầu tiên trở thành một biên dịch AOT rằng biên dịch X đến Y và giai đoạn thứ hai là một động cơ hỗn hợp chế độ mà cả hai dịch Y và biên dịch Y đến Z . Công cụ thực thi Rubinius Ruby hoạt động theo cách này, ví dụ: nó có trình biên dịch AOT biên dịch mã nguồn Ruby thành mã byte Rubinius và một công cụ chế độ hỗn hợp lần đầu tiên diễn giải mã byte của Rubinius và một khi nó đã thu thập một số thông tin sẽ biên dịch các phương thức thường được gọi mã máy.

Lưu ý rằng vai trò của trình thông dịch trong trường hợp công cụ thực thi chế độ hỗn hợp, cụ thể là cung cấp khởi động nhanh, và cũng có khả năng thu thập thông tin và cung cấp khả năng dự phòng cũng có thể được trình biên dịch JIT thứ hai thay thế. Đây là cách V8 hoạt động, ví dụ. V8 không bao giờ giải thích, nó luôn luôn biên dịch. Trình biên dịch đầu tiên là một trình biên dịch rất nhanh, rất mỏng, khởi động rất nhanh. Mã nó tạo ra không phải là rất nhanh, mặc dù. Trình biên dịch này cũng tiêm mã hồ sơ vào mã mà nó tạo ra. Trình biên dịch khác chậm hơn và sử dụng nhiều bộ nhớ hơn, nhưng tạo ra mã nhanh hơn nhiều và nó có thể sử dụng thông tin lược tả được thu thập bằng cách chạy mã được biên dịch bởi trình biên dịch đầu tiên.


1
Các trình biên dịch mã byte Python và Ruby có thực sự được tính là AOT không? Vì cả hai ngôn ngữ đều cho phép tải các mô-đun động, được biên dịch khi chúng được tải, chúng chạy trong thời gian chạy chương trình.
Sebastian Redl

1
@SebastianRedl, với CPython bạn có thể chạy python -m compileall .hoặc tải các mô-đun một lần. Ngay cả trong trường hợp sau, vì các tệp vẫn còn và được sử dụng lại sau lần chạy đầu tiên, nó có vẻ như AOT.
Paul Draper

Bạn có bất kỳ tài liệu tham khảo để đọc thêm? Tôi muốn biết thêm về V8.
Vince Panuccio

@VincePanuccio Bài đăng tham khảo trình biên dịch Full-Codegen và Crankshaft đã được thay thế . Bạn có thể tìm thấy về họ trực tuyến.
eush77

CLR Jit biên dịch phương thức theo phương pháp, không phải toàn bộ lắp ráp
Grigory
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.