Python và cách tiết kiệm dung lượng cho phần mềm nhúng

Khi tôi bắt đầu bước chân vào mảng IoT đầu năm 2016, tôi bắt đầu lân la tiếp xúc với cộng đồng làm IoT Việt Nam (trên Facebook là chính). Ở Việt Nam, lực lượng tech làm IoT đa phần là dân điện tử đi lên, thế nên tôi hay gặp những câu hỏi như "nên dùng ngôn ngữ nào để lập trình nhúng" từ những bạn sinh viên mới. Tôi cũng ngạc nhiên khi thấy các bạn kháo nhau dùng NodeJS, theo phong trào.

Nếu cần dùng các ngôn ngữ biên dịch như C++, Go thì không nói. Nhưng nếu dùng ngôn ngữ thông dịch thì Python nên được dùng hơn NodeJS/JavaScript. Một khía cạnh nên tính đến là độ lớn file cần lưu trữ. Các board máy tính nhúng chạy Linux thường dùng thẻ nhớ, hoặc bộ nhớ flash trong (còn có cách gọi khác là NAND Flash, SPI Flash, eMMC) để thay thế ổ cứng (HDD) nên có dung lượng khiêm tốn. Nếu cách lưu trữ không phù hợp, có khi thư viện đã ngốn hết không gian lưu trữ đáng lẽ phải dành cho chương trình và dữ liệu (data).

Thứ nhất, cách quản lý các gói thư viện phụ thuộc của NodeJS cực kỳ tốn dung lượng, với sự trùng lắp các file thư viện.

node_modules

Hình bên trên là ví dụ về cách lưu trữ thư viện khi cài bằng yarn. Tình hình còn tệ hơn nếu cài bằng npm, vốn có truyền thống không thèm phân giải mối ràng buộc phiên bản (dependency version resolve) mà cứ lồng các gói phụ thuộc vào nhau, dẫn đến câu châm biếm sau:

heaviest

Ghi chú: Có vẻ, gần đây, sau khi nhận nhiều châm biếm thì npm đang cải thiện vụ quản lý thư viện này.

Thứ hai, NodeJS không hỗ trợ phân phối gói thư viện ở dạng binary. Đối với cả Python và NodeJS, có nhiều thư viện được viết bằng C/C++. Khi cài đặt những thư viện này thì phải có bước biên dịch mã nguồn C/C++ đó thành file binary (*.so hay *.dll). Nếu ngôn ngữ đó không có quy chuẩn về phân phối thư viện biên dịch trước ở dạng binary, thì trình quản lý gói, sẽ phải download source code và tiến hành biên dịch ngay trên máy người dùng cuối. Và để làm việc đó thì máy người dùng cuối phải cài trước trình biên dịch, code generator (như Swig), các source code của thư viện C/C++ mà gói thư viện Python/NodeJS kia phụ thuộc vào. Như vậy là dung lượng khiêm tốn của máy tính nhúng lại phải dành chỗ cho những thứ nặng nề này.

Python giúp cắt bỏ những thứ nặng nề kia bằng cách định ra một phương pháp đóng gói và phân phối qua file wheel, trong đó các thư viện viết bằng C/C++ sẽ được biên dịch trước, file nhị phân thành phẩm (cùng các file metadata phụ trợ) sẽ được đóng gói và xuất bản trên kênh phân phối chính thức của Python (https://pypi.org/). Khi cài đặt trên máy người dùng cuối thì pip (trình quản lý gói của Python) chỉ việc download file đã dịch này, không phải download bộ source code C/C++, người dùng cũng không phải cài trình biên dịch và mớ mã nguồn. Vừa tiết kiệm không gian lưu trữ, vừa tiết kiệm thời gian biên dịch. Đặc biệt, vì các máy tính nhúng dùng CPU ARM, hơi yếu nên thời gian biên dịch cực kỳ lâu. Nếu bạn triển khai cho nhiều khách hàng, tức là phải cài phần mềm trên nhiều board, thì thời gian tiêu phí cho quá trình biên dịch càng lớn.

Ghi chú: Có một vài thư viện NodeJS cũng thấy nỗi nhức nhối này và họ đã workaround, bằng cách phân phối một script "installer" chứ không phân phối bản thân thư viện. Khi người dùng cài gói, script này sẽ chạy và download file binary biên dịch sẵn từ website của tác giả thư viện về. Nguồn download là website của tác giả chứ không phải kho chính thức của NodeJS (https://www.npmjs.com/).

Gần đây, khi tham gia vào dự án SUSI.AI Smart Speaker, một nhiệm vụ của tôi là giảm tổng dung lượng của phần mềm trên Raspberry Pi. Tổng dung lượng ở đây là tính tất cả những thứ phải cài đặt trên Raspperry Pi (bao gồm cả hệ điều hành). Trước khi tôi tham gia, dự án đã có một script install.sh để tiến hành cài tự động các phần mềm cần thiết, cho phép người mới vào dự án có thể bắt tay lập trình luôn. Có điều, do thiếu người mạnh dẫn dắt từ đầu nên dự án trở nên lộn xộn, ngay cả file install.sh cũng không chạy thành công. Trước khi file install.sh đó được sửa (mà người phụ trách là tôi) thì một bạn thành viên đề xuất sẽ copy thẻ nhớ (chạy Raspberry Pi) của anh ta ra một file image. Tất cả những thành viên mới chỉ việc nạp image đó vô thẻ nhớ của mình rồi bắt đầu công việc. Thế nhưng, khi copy ra xong thì file đó nặng tới 16GB! Cho dù có nén kiểu gì thì dung lượng đó quá lớn một cách lố bịch. Đóng góp vào con số khủng khiếp đó là một đống những phần mềm công cụ, mã nguồn thư viện. Dó có liên quan đến AI nên phải dùng đến một số thư viện tính toán đại số, và ở dạng mã nguồn thì nó rất lớn. Chưa kể, thành viên đó lại không thạo Linux, nên dùng một công cụ Windows để tạo image thẻ nhớ, khiến không có một phương pháp tạo image tối ưu (đục bỏ phần dữ liệu rơi rớt của những file đã xóa). Đây cũng là một trong các lí do mà tôi luôn khuyên các bạn làm IoT, lập trình nhúng phải chuyển sang Linux. Sau vài tháng miệt mài sửa file install.sh, chuyển các thư viện sang dạng đóng gói binary, thậm chí đóng góp code vào cho một thư viện bên thứ ba, viết script để build hệ điều hành một cách tự động (khi chúng tôi cập nhật code mới) thì dung lượng image đã giảm đáng kể, nay còn 781MB sau khi nén.

Ngoài ra, trong quá trình làm việc tại AgriConnect, FOSSASIA thì tôi cũng build trước một số ứng dụng và thư viện thành dạng binary cho ARM, để tiết kiệm thời gian cài đặt, triển khai trên BeagleBone, Raspberry Pi. Các kho này là public nên ai có nhu cầu tương tự cũng có thể tận dụng: https://repo.fury.io/agriconnect, https://repo.fury.io/fossasia.


Cập nhật: