Những thách thức liên quan đến việc gõ trong việc viết một trình biên dịch cho một ngôn ngữ được gõ động là gì?


9

Trong buổi nói chuyện này , Guido van Rossum đang nói (27:30) về những nỗ lực viết trình biên dịch cho mã Python, nhận xét về nó nói:

Hóa ra không dễ để viết một trình biên dịch duy trì tất cả các thuộc tính gõ động tốt và cũng duy trì tính chính xác ngữ nghĩa của chương trình của bạn, để nó thực sự làm điều tương tự cho dù bạn có làm điều gì kỳ lạ ở đâu đó dưới vỏ bọc và thực sự chạy nhanh hơn

Những thách thức (có thể) liên quan đến việc gõ một trình biên dịch cho một ngôn ngữ được gõ động như Python là gì?


Trong trường hợp này, gõ động không phải là vấn đề lớn nhất. Đối với python đó là một phạm vi năng động.
SK-logic

Điều đáng chú ý là những người khác đã lập luận rằng việc xây dựng kiểu gõ động vào nền tảng là câu trả lời đúng ở đây. Microsoft đã đầu tư rất nhiều tiền vào DLR vì lý do này chính xác là vì vậy và NeXT / Apple đã đi được một nửa trên băng đảng đó trong nhiều thập kỷ. Điều đó không giúp ích gì cho CPython, nhưng IronPython chứng minh rằng bạn có thể biên dịch tĩnh Python một cách hiệu quả và PyPy chứng minh rằng bạn không cần phải làm vậy.
abarnert

2
@ SK-logic Phạm vi động trong Python? Lần cuối tôi kiểm tra, tất cả các cấu trúc trong ngôn ngữ đều sử dụng phạm vi từ vựng.

1
@ SK-logic Bạn có thể tự động tạo mã và thực thi mã, nhưng mã đó cũng chạy theo phạm vi từ vựng. Đối với mỗi biến đơn trong chương trình Python, bạn có thể dễ dàng xác định phạm vi của biến đó chỉ bằng cách kiểm tra AST. Bạn có thể đang nghĩ về exectuyên bố đã mất từ ​​3.0 và do đó nằm ngoài sự cân nhắc của tôi (và có lẽ là của Guido, vì cuộc nói chuyện là từ năm 2012). Bạn có thể cho một ví dụ? Và định nghĩa của bạn về "phạm vi động", nếu nó [khác với của tôi] (en.wikipedia.org/wiki/Docate_scoping).

1
@ SK-logic Điều duy nhất đó là chi tiết triển khai đối với tôi là các thay đổi để trả về giá trị của các locals()cuộc gọi đến locals. Những gì được ghi lại và chắc chắn không phải là một chi tiết triển khai là thậm chí không localshoặc globalscó thể thay đổi trong phạm vi mà mỗi biến được tìm kiếm. Đối với mỗi lần sử dụng một biến, phạm vi được đề cập được xác định tĩnh. Mà làm cho nó quyết định phạm vi từ vựng. (Và btw, evalexecchắc chắn cũng không phải là chi tiết triển khai - hãy xem câu trả lời của tôi!)

Câu trả lời:


16

Bạn quá đơn giản tuyên bố của Guido trong việc đặt câu hỏi của bạn. Vấn đề không phải là viết một trình biên dịch cho một ngôn ngữ gõ động. Vấn đề là viết một (tiêu chí 1) luôn luôn đúng, (tiêu chí 2) giữ kiểu gõ động (tiêu chí 3) nhanh hơn đáng kể cho một số lượng mã đáng kể.

Thật dễ dàng để thực hiện 90% (không đạt tiêu chí 1) của Python và luôn luôn nhanh chóng với nó. Tương tự, thật dễ dàng để tạo một biến thể Python nhanh hơn bằng cách gõ tĩnh (không đạt tiêu chí 2). Việc thực hiện 100% cũng dễ dàng (trong khi thực hiện một ngôn ngữ phức tạp thì dễ), nhưng cho đến nay, mọi cách dễ dàng để thực hiện đều trở nên tương đối chậm (không đạt tiêu chí 3).

Việc triển khai một trình thông dịch cộng với JIT là chính xác, thực hiện toàn bộ ngôn ngữ và nhanh hơn đối với một số mã hóa ra là khả thi, mặc dù khó hơn đáng kể (xem PyPy) và chỉ như vậy nếu bạn tự động tạo trình biên dịch JIT (Psyco đã làm mà không cần nó , nhưng rất hạn chế về mã có thể tăng tốc). Nhưng lưu ý rằng điều này rõ ràng nằm ngoài phạm vi, vì chúng ta đang nói về tĩnhtrình biên dịch (còn gọi là trước thời hạn). Tôi chỉ đề cập đến điều này để giải thích tại sao cách tiếp cận của nó không hoạt động đối với các trình biên dịch tĩnh (hoặc ít nhất là không có ví dụ hiện có): Trước tiên, nó phải diễn giải và quan sát chương trình, sau đó tạo mã cho một vòng lặp cụ thể của vòng lặp (hoặc một mã tuyến tính khác đường dẫn), sau đó tối ưu hóa địa ngục dựa trên các giả định chỉ đúng với lần lặp cụ thể đó (hoặc ít nhất, không phải cho tất cả các lần lặp có thể). Kỳ vọng là nhiều lần thực thi mã sau đó cũng sẽ phù hợp với kỳ vọng và do đó được hưởng lợi từ việc tối ưu hóa. Một số kiểm tra (tương đối rẻ) được thêm vào để đảm bảo tính chính xác. Để làm tất cả điều này, bạn cần một ý tưởng về những gì cần chuyên biệt, và thực hiện chậm nhưng chung chung để quay trở lại. Trình biên dịch AOT không có. Họ không thể chuyên môn hóa tất cảdựa trên mã họ không thể thấy (ví dụ mã được tải động) và chuyên biệt có nghĩa là tạo ra nhiều mã hơn, có một số vấn đề (sử dụng icache, kích thước nhị phân, thời gian biên dịch, các nhánh bổ sung).

Việc triển khai trình biên dịch AOT thực hiện chính xác toàn bộ ngôn ngữ cũng tương đối dễ dàng: Tạo mã gọi vào thời gian chạy để làm những gì trình thông dịch sẽ làm khi được cung cấp với mã này. Nuitka (chủ yếu) làm điều này. Tuy nhiên, điều này không mang lại nhiều lợi ích về hiệu suất (không đạt tiêu chí 3), vì bạn vẫn phải thực hiện nhiều công việc không cần thiết như một trình thông dịch, tiết kiệm cho việc gửi mã byte tới khối mã C thực hiện những gì bạn đã biên dịch. Nhưng đó chỉ là một chi phí khá nhỏ - đủ đáng kể để đáng để tối ưu hóa trong một trình thông dịch hiện có, nhưng không đủ quan trọng để biện minh cho việc thực hiện hoàn toàn mới với các vấn đề của chính nó.

Điều gì sẽ là cần thiết để đáp ứng cả ba tiêu chí? Chúng tôi không có ý tưởng. Có một số sơ đồ phân tích tĩnh có thể trích xuất một số thông tin về các loại cụ thể, luồng điều khiển, v.v. từ các chương trình Python. Những cái mang lại dữ liệu chính xác ngoài phạm vi của một khối cơ bản duy nhất là cực kỳ chậm và cần phải xem toàn bộ chương trình, hoặc ít nhất là phần lớn. Tuy nhiên, bạn không thể làm gì nhiều với thông tin đó, ngoài việc có thể tối ưu hóa một vài thao tác trên các loại dựng sẵn.

Tại sao vậy Nói một cách thẳng thắn, trình biên dịch sẽ loại bỏ khả năng thực thi mã Python được tải trong thời gian chạy (không đạt tiêu chí 1) hoặc không đưa ra bất kỳ giả định nào có thể bị vô hiệu hóa bởi bất kỳ mã Python nào. Thật không may, trong đó bao gồm khá nhiều tất cả mọi thứ hữu ích để tối ưu hóa các chương trình: Globals bao gồm các chức năng có thể được phục hồi, các lớp học có thể được biến đổi hoặc thay thế hoàn toàn, mô-đun có thể được sửa đổi tùy tiện quá, nhập khẩu có thể bị tấn công theo nhiều cách, vv Một chuỗi đơn truyền cho eval, exec, __import__hoặc nhiều chức năng khác, có thể thực hiện bất kỳ chức năng nào trong số đó. Trong thực tế, điều đó có nghĩa là hầu như không có tối ưu hóa lớn nào có thể được áp dụng, mang lại ít lợi ích hiệu suất (không đạt tiêu chí 3). Quay lại đoạn văn trên.


4

Vấn đề khó nhất là tìm ra loại mọi thứ có tại bất kỳ thời điểm nào.

Trong một ngôn ngữ tĩnh như C hoặc Java, một khi bạn đã thấy khai báo kiểu, bạn sẽ biết đối tượng đó là gì và nó có thể làm gì. Nếu một biến được khai báo int, nó là một số nguyên. Nó không phải là, ví dụ, một tham chiếu chức năng có thể gọi được.

Trong Python, nó có thể. Đây là Python khủng khiếp, nhưng hợp pháp:

i = 2
x = 3 + i

def prn(s):
    print(s)

i = prn
i(x)

Bây giờ, ví dụ này khá ngu ngốc, nhưng nó minh họa ý tưởng chung.

Thực tế hơn, bạn có thể thay thế một hàm dựng sẵn bằng một hàm do người dùng định nghĩa sẽ làm một cái gì đó hơi khác (như một phiên bản ghi lại các đối số của nó khi bạn gọi nó).

PyPy sử dụng trình biên dịch đúng lúc sau khi xem mã thực sự làm gì và điều này cho phép PyPy tăng tốc mọi thứ lên rất nhiều. PyPy có thể xem một vòng lặp và xác minh rằng mỗi khi vòng lặp chạy, biến fooluôn là một số nguyên; sau đó PyPy có thể tối ưu hóa mã tìm kiếm loại footrên mỗi lần đi qua vòng lặp và thậm chí có thể loại bỏ đối tượng Python đại diện cho một số nguyên và foocó thể trở thành một số trong một thanh ghi trên CPU. Đây là cách PyPy có thể nhanh hơn CPython; CPython thực hiện tra cứu nhanh nhất có thể, nhưng thậm chí không thực hiện tra cứu thậm chí còn nhanh hơn.

Tôi không biết chi tiết, nhưng tôi nhớ có một dự án tên là Unladen Swallow đang cố gắng áp dụng công nghệ trình biên dịch tĩnh để tăng tốc Python (sử dụng LLVM). Bạn có thể muốn Google tìm kiếm Unladen Swallow và xem liệu bạn có thể tìm thấy một cuộc thảo luận về lý do tại sao nó không hoạt động như họ đã hy vọng.


Unladen Swallow không phải là về biên dịch tĩnh hoặc các loại tĩnh; cuối cùng, việc chuyển giao trình thông dịch CPython một cách hiệu quả, với tất cả tính năng động của nó, sang LLVM, với một JIT mới lạ mắt (giống như Parrot, hoặc DLR cho .NET. làm là tìm thấy rất nhiều tối ưu hóa cục bộ trong CPython (một số trong đó đã đưa nó vào dòng chính 3.x). Shed Da có lẽ là dự án mà bạn nghĩ đến đó là suy luận kiểu tĩnh được sử dụng để biên dịch tĩnh Python (mặc dù với C ++, không trực tiếp với mã gốc).
abarnert

Một trong những tác giả của Unladen Swallow, Reid Kleckner, đã đăng một Hồi tưởng về Swallow Unladen , có thể đáng đọc trong bối cảnh này, mặc dù thực sự đó là về những thách thức về quản lý và tài trợ hơn là những thách thức kỹ thuật.
abarnert

0

Như câu trả lời khác nói, vấn đề chính là tìm ra thông tin loại. Trong phạm vi bạn có thể làm điều đó một cách tĩnh, bạn có thể trực tiếp tạo mã tốt.

Nhưng ngay cả khi bạn không thể làm điều đó một cách tĩnh, bạn vẫn có thể tạo mã hợp lý, chỉ trong thời gian chạy, khi bạn nhận được thông tin loại thực tế . Thông tin này hóa ra thường ổn định hoặc có nhiều nhất một vài giá trị khác nhau cho bất kỳ thực thể cụ thể nào tại một điểm mã cụ thể. Các ngôn ngữ lập trình TỰ đi tiên phong trong rất nhiều các ý tưởng của bộ sưu tập tích cực loại runtime và sinh mã chạy. Ý tưởng của nó được sử dụng rộng rãi trong các trình biên dịch dựa trên JIT hiện đại như Java và C #.

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.