paint-brush
“Trăn chậm chạp” và những huyền thoại khác về một kỷ nguyên đang hấp hốitừ tác giả@oleksandrkaleniuk
13,581 lượt đọc
13,581 lượt đọc

“Trăn chậm chạp” và những huyền thoại khác về một kỷ nguyên đang hấp hối

từ tác giả Oleksandr Kaleniuk7m2023/01/11
Read on Terminal Reader

dài quá đọc không nổi

Lập trình không đơn giản như trước đây. Python được thông dịch và chậm, còn C++ được biên dịch nhanh. Ngôn ngữ không phải là trình biên dịch hay trình thông dịch, nó là bản chất của nó – ngôn ngữ: một bộ quy tắc về cách chúng ta nên nói cho máy tính biết chúng ta nên làm gì.
featured image - “Trăn chậm chạp” và những huyền thoại khác về một kỷ nguyên đang hấp hối
Oleksandr Kaleniuk HackerNoon profile picture


Khi tôi còn nhỏ, lập trình rất đơn giản. Bạn tôi có một máy tính và có Cơ bản và Lắp ráp. Bạn có thể viết chương trình của mình bằng Cơ bản, cách này dễ thực hiện hơn nhưng chương trình của bạn sẽ chậm hoặc bạn có thể viết thứ gì đó bằng Hợp ngữ, cách này khó hơn nhưng chương trình của bạn sẽ chạy nhanh hơn đáng kể.


Lời giải thích cho điều này cũng đơn giản. Basic là một trình thông dịch, để chạy chương trình của bạn, nó phải duyệt qua mã của bạn mỗi khi bạn gọi nó và diễn giải mã đó từng dòng một. Nếu nó nói "PRINT X", trình thông dịch phải tìm một biến có tên là “X”, tìm một quy trình in và gọi quy trình tìm thấy cho biến đã tìm thấy.

Hội là, tốt, hội. Nó cũng diễn giải chương trình của bạn theo một cách nào đó, nhưng chỉ một lần khi bạn chạy trình dịch hợp ngữ. Sau đó, chương trình của bạn sẽ được thực thi mà không cần thông dịch. Các chương trình cần thông dịch chạy chậm hơn các chương trình không cần thông dịch. Tất nhiên, nếu chúng là các chương trình tương đương.

Và trong Cơ bản và Lắp ráp, chúng thường như vậy. Cơ bản là một ngôn ngữ bắt buộc, thậm chí không quá thân thiện với lập trình cấu trúc. Ngay cả một thứ chủ yếu như "hàm" cũng không phải là cấu trúc ngôn ngữ tích hợp ở đó mà là một mẫu: "GOSUB ... RETURN", khá giống với "call ... ret" trong Hợp ngữ.

Bây giờ nhanh chóng chuyển tiếp 30 năm. Ngôn ngữ rất nhiều. Máy tính ở khắp mọi nơi. Lập trình không còn đơn giản nữa. Bộ phận của tôi kiếm tiền bằng cách viết lại mã của nhà nghiên cứu ban đầu được viết bằng Python bằng C++ để đạt hiệu suất vì kiến thức phổ biến là Python được thông dịch và chậm, còn C++ được biên dịch nhanh và nhanh. Nhưng bằng cách nào đó, mỗi năm, việc viết lại này ngày càng khó giành được bất kỳ suất diễn nào. Một cái gì đó thay đổi và thay đổi nhanh chóng. Tuy nhiên, kiến thức chung không thay đổi vì vậy chúng tôi tiếp tục viết lại.

Nhưng bây giờ chúng tôi buộc phải tối ưu hóa mọi thứ như điên chỉ để biện minh cho những gì chúng tôi làm. Một thuật toán xuất hiện trong Python, chúng tôi viết lại nó bằng C++ tương đương, và đột nhiên nó trở nên chậm hơn 3 lần. Đó... không phải lý do chúng ta ở đây. Vì vậy, chúng tôi thiết kế lại thuật toán để thúc đẩy và tăng hiệu suất như chúng tôi đã hứa. Và hầu hết thời gian, vì các nhà nghiên cứu hoàn toàn không quan tâm đến hiệu suất và thông minh về thuật toán nên họ để lại một số kết quả thấp phía sau, nên điều này hiệu quả.

Tuy nhiên, toàn bộ hoạt động kinh doanh này bây giờ trông giống như một trò lừa đảo. Chúng tôi đang làm cho mã chậm hơn bằng cách viết lại bằng C++ để chúng tôi có thể làm cho mã nhanh hơn bằng cách tái cấu trúc mã. Tại sao chúng ta không tái thiết kế nó trực tiếp bằng Python? Ah! Vấn đề là, chúng tôi không biết Python. Chúng tôi biết một chút Python, đủ để đọc và hiểu, nhưng không đủ để tạo các chương trình cực nhanh trong đó.

Vậy có gì để biết?

thư viện

Hầu hết các thư viện Python được viết bằng C hoặc Fortran. Lõi NumPy được viết bằng C; Gấu trúc - trong Cython và C; SciPy - trong Fortran, C, và một phần C++. Chúng không có lý do gì để chậm hơn bất cứ thứ gì được viết bằng C++, Rust hoặc Julia. Họ có thể nhanh hơn mặc dù.

Trong công ty của mình, chúng tôi phục vụ cả dịch vụ đám mây và ứng dụng máy tính để bàn. Và người dùng máy tính để bàn đang tức giận khi phiên bản mới của ứng dụng yêu thích của họ ngừng hoạt động trên phần cứng của họ mà rõ ràng là không có lý do. Vì vậy, chúng tôi giữ mục tiêu xây dựng máy tính để bàn cũ. Thực sự cũ, giống như tiền Nehalem cũ. Bằng cách này, không ai tức giận nhưng cũng không ai thích thú với SSE3.


Tất nhiên, một thư viện tính toán được xây dựng cho một mục tiêu phù hợp thường sẽ nhanh hơn một thư viện tương đương được xây dựng cho một máy tính 15 tuổi chung chung với các khả năng siêu vô hướng hạn chế.

Tin vui là nếu bạn đang xây dựng cho một đám mây, bạn có thể đặt nền tảng xây dựng mục tiêu của mình chính xác là máy bạn mua, sau đó các thư viện C++ của bạn sẽ chạy với tốc độ tương tự như Python và thậm chí có thể nhanh hơn một chút.

Trình biên dịch

Thành thật mà nói, toàn bộ cuộc tranh luận về ngôn ngữ nào nhanh hơn là vô lý. Một ngôn ngữ không phải là trình biên dịch hay trình thông dịch, nó là như vậy – ngôn ngữ: một bộ quy tắc xác định cách chúng ta nên nói với máy tính những gì chúng ta muốn nếu thực hiện. Một ngôn ngữ chỉ là một tập hợp các quy tắc, một đặc điểm kỹ thuật. Và không có gì khác.


Chính sự khác biệt giữa diễn giải và biên dịch là điều đã có từ thế kỷ trước. Ngày nay, có các trình thông dịch C như IGCC , PicoC hoặc CCons và có các trình biên dịch Python. Các trình biên dịch JIT chẳng hạn như [PyPy] và các trình biên dịch biên dịch trước khi bạn chạy cổ điển như Codon (cũng có khả năng JIT nếu bạn chỉ muốn một phần mã của mình được biên dịch).


Codon được xây dựng dựa trên LLVM, cơ sở hạ tầng tương tự như Rust, Julia hoặc Clang được xây dựng trên đó. Mã được tạo bằng Codon chạy, cho hoặc nhận, ở cùng mức hiệu suất như được tạo bằng bất kỳ mã nào trong số đó. Có thể có những nhược điểm về hiệu suất do bộ sưu tập rác của Python hoặc các loại dữ liệu gốc lớn nhưng chúng ta không nói về 100x hoặc 10x nữa. LLVM thực hiện phép thuật của nó. nó biến mã Python thành mã máy cho bạn.


Ngoài ra còn có những lầm tưởng về việc biên dịch đúng lúc hoặc JIT. Một số người nói rằng nó vượt trội hơn so với kỹ thuật biên dịch trước khi bạn chạy thông thường bởi vì nó luôn biên dịch cho kiến trúc mà người dùng có, và do đó khai thác nó một cách tối ưu. Một số ý kiến cho rằng vẫn còn chi phí biên dịch mà việc biên dịch đúng lúc cũng thuộc về người dùng. Điều này làm cho các chương trình chạy chậm vì chúng phải chạy và tự biên dịch cùng một lúc.


Vấn đề với cả hai huyền thoại là chúng đều đúng và đều vô ích. Có, JIT thường biên dịch thành mã máy tốt hơn trừ khi bạn xây dựng mã nhị phân của mình một cách rõ ràng cho máy đích, điều này xảy ra khá thường xuyên khi bạn triển khai trên đám mây. Và vâng, có một hình phạt biên dịch trong thời gian chạy, nhưng nó không đáng kể nếu thời gian chạy của bạn được tính bằng tháng, một lần nữa, khi bạn triển khai trên đám mây, đây không phải là điều chưa từng xảy ra.


Vì vậy, có những ưu và nhược điểm. Điều quan trọng, Python (cụ thể là Codon) hỗ trợ cả chế độ biên dịch trước khi bạn chạy và chế độ JIT để bạn có thể chọn chế độ phù hợp với nhu cầu của mình nhất. Trình biên dịch truyền thống, chẳng hạn như Clang, không có tùy chọn JIT.

Numba và mô hình hạt nhân của nó

Nói về JIT, Numba có lẽ là công nghệ thay đổi cuộc chơi nhiều nhất trong thế giới lập trình Python cực nhanh. Nó là một trình biên dịch nhưng nó chỉ nhắm mục tiêu các hạt nhân được chọn chứ không phải toàn bộ chương trình. Tất nhiên, bạn có thể chọn những gì sẽ được biên dịch và cho nền tảng nào. Trong thiết lập này, bạn có thể chạy các đoạn mã của mình trên CPU và các đoạn mã khác - trên GPGPU .


Về mặt kỹ thuật, người ta có thể tạo phụ trợ cho các thiết bị chuyên dụng khác như TPU của Google hoặc thậm chí là máy gia tốc quang tử của Lightmatters. Chưa có chương trình phụ trợ như vậy, thay vào đó, những người đó đã quyết định tung ra thư viện của riêng họ. Tuy nhiên, ngoài triệu chứng, họ cũng chọn cung cấp giao diện cho máy tính quang tử bằng Python để bạn có thể tương tác liền mạch với Pytorch, Tensorflow hoặc ONNX.


Vì vậy, Lightmatter vẫn chưa có. Nhưng NVidia thì có. Họ đã cung cấp phụ trợ CUDA cho Numba và giờ đây bạn có thể viết nhân bằng Python và chạy chúng trên phần cứng NVidia với hiệu quả tối đa. C ++ không có điều đó. Tuy nhiên, có một phương ngữ CU, đến từ NVidia , tất nhiên, mở rộng C++ cho vấn đề này. Trong Python, bạn không cần phải mở rộng ngôn ngữ đó. Vì Numba hoạt động như một trình biên dịch hạt nhân JIT, nên việc thêm phần phụ trợ chỉ là vấn đề vá thư viện.


Vì vậy, mô hình hạt nhân đang nhắm mục tiêu tính toán không đồng nhất. Bạn có thể chạy các đoạn mã trên các thiết bị khác nhau, điều này thật tuyệt. Nhưng có một khía cạnh khác của sự không đồng nhất mà bạn có thể chưa nghĩ tới. Với mô hình hạt nhân, bạn có thể nhắm mục tiêu các hạt nhân khác nhau cho các bối cảnh tính toán khác nhau và không nhất thiết phải là thiết bị phần cứng. Điều này có nghĩa là nếu bạn muốn một kernel nhanh nhưng không đặc biệt chính xác, bạn có thể xây dựng nó bằng tùy chọn “-fast-math”. Nhưng nếu, trong một số ngữ cảnh khác, bạn muốn hạt nhân đó chính xác hơn là nhanh, bạn có thể xây dựng lại chính mã đó mà không phải đánh đổi.


Đây là điều khó đạt được với các trình biên dịch truyền thống, nơi bạn không thể thay đổi các tùy chọn biên dịch ở giữa một đơn vị dịch thuật. Chà, với mô hình hạt nhân, mỗi hạt nhân là đơn vị dịch thuật của riêng nó.

Phần kết luận

Python không chậm. Nó cũng không nhanh. Nó chỉ là một ngôn ngữ, một bộ quy tắc và từ khóa. Nhưng có rất nhiều người đã quen với những quy tắc này và những từ khóa này. Họ cảm thấy thoải mái khi viết bằng Python và họ quan tâm đến việc làm cho Python tốt hơn cho họ.


Cơ sở người dùng này đủ lớn để thu hút cả những công ty khởi nghiệp mới với các công nghệ mang tính cách mạng như Lightmatter và máy tính quang tử của họ, cũng như các công ty lâu đời với chuyên môn hàng chục năm về điện toán hiệu suất cao như NVidia. Tất cả những người này đã đầu tư rất nhiều vào việc biến Python trở thành một ngôn ngữ tốt hơn... tất nhiên không phải, mà là môi trường. Môi trường trong đó viết các chương trình cực nhanh khó hơn một chút so với viết các chương trình chạy chậm.


Nhìn chung, họ đang đạt được tiến bộ lớn. Python đang trở nên nhanh hơn mỗi năm. Tại thời điểm này, hãy quên máy tính quang tử nếu bạn có thể, các chương trình viết bằng Python thường chạy ngang bằng với các chương trình viết bằng Julia, C ++ hoặc Rust. Nhưng Python sẽ không dừng lại ở đó. Nó đang trở nên nhanh hơn các trình biên dịch truyền thống đang trở nên thân thiện hơn với người dùng.


Hãy sẵn sàng để thấy rằng trong một vài năm tới, các trình biên dịch Python: PyPy, Numba hoặc một cái gì đó hoàn toàn mới, sẽ vay mượn các kỹ thuật từ Spiral hoặc Herbie để tạo mã hiệu quả hơn rất nhiều mà không trình biên dịch truyền thống nào có thể sánh được. Xét cho cùng, việc viết một chương trình phụ trợ JIT mới bằng Python dễ dàng hơn nhiều so với việc mô phỏng lại toàn bộ cơ sở hạ tầng LLVM.