Viết code tối ưu là kỹ năng cần thiết khi học một ngôn ngữ mới. Việc viết code tối ưu nhằm tăng khả năng xử lý của chương trình, từ đó giúp ta tiết kiệm được thời gian phân tích dữ liệu. Điều này đặc biệt quan trọng với các thuật toán phức tạp với số lượng các trường hợp cần thử lên đến hàng triệu, hàng tỷ.
Giới thiệu
Trong mục này, tôi sẽ đưa ra một số ví dụ về cách viết code không tối ưu trong SAS. Từ đó, ta sẽ tìm cách giải quyết các vấn đề này trong các mục phía dưới.
Data step
Với ví dụ về Cross validation, bài toán đặt ra là: làm thế nào để tạo ra 1000 dữ liệu mới, mỗi dữ liệu có train và validate chiếm 70% và 30% số lượng quan sát ban đầu (và thêm cột ID). Ý tưởng đơn giản nhất có thể nghĩ ra như sau:
- Chia dữ liệu đã cho theo tỷ lệ 70/30 bằng một hàm random.
- Thực hiện bước ở trên 1000 lần, mỗi lần nối dữ liệu vừa chia vào một dữ liệu tổng hợp
Ta thực hiện trên code SAS như sau:
%MACRO BOOSTRAP;
%DO I=1 %TO 1000;
%IF &I=1 %THEN
%DO;
DATA OUTPUT;
SET SASHELP.CARS;
ID=&I;
IF RANUNI(ID)<0.70 THEN
ROLE="T";
ELSE ROLE="V";
OUTPUT;
RUN;
%END;
%ELSE
%DO;
DATA TEMP00;
SET SASHELP.CARS;
ID=&I;
IF RANUNI(ID)<0.70 THEN
ROLE="T";
ELSE ROLE="V";
OUTPUT;
RUN;
DATA OUTPUT;
SET OUTPUT TEMP00;
RUN;
%END;
%END;
%MEND;
%BOOSTRAP;
Thời gian chạy: 46.609s
. Với dữ liệu nhẹ như SASHELP.CARS, thời gian chạy trên là quá lâu.
Variable Aggregation
Với dữ liệu OUTPUT
bên trên, giả sử ta muốn tính giá trị MSRP
trung bình theo từng biến Make, Type, Origin, DriveTrain
. Ta thử tiếp dụng dùng Loop như sau:
Hồi quy
Tính toán hiệu năng cao (cơ bản)
Sơ đồ sau đây minh họa cách thức SAS xử lý dữ liệu:
SAS lưu trữ dữ liệu trên ổ cứng và đọc dữ liệu vào RAM khi có yêu cầu xử lý. Với quy trình trên, công đoạn tốn nhiều thời gian nhất chính là đọc dữ liệu SAS từ đĩa (disk) vào RAM. Do đó, để tăng tốc độ xử lý của SAS, ta nên thực hiện các bước như sau:
Hạn chế các bước thừa
Các bước tính toán trên cùng một dữ liệu nên được gôp lại thành một lần Datastep. Ví dụ không nên viết:
Dont
DATA VIETNAM;
SET ALL_COUNTRY;
WHERE COUNTRY="Viet Nam";
RUN;
DATA FRANCE;
SET ALL_COUNTRY;
WHERE COUNTRY="France";
RUN;
Mà nên viết là;
Do
DATA VIETNAM FRANCE;
SET ALL_COUNTRY;
IF WHERE COUNTRY="France" THEN OUTPUT FRANCE;
ELSE IF COUNTRY="Viet Nam" THEN OUTPUT VIETNAM;
RUN;
Hoặc không nên viết:
Dont
DATA OUTPUT;
SET INPUT;
SUM_AB=A+B;
RUN;
DATA OUTPUT;
SET INPUT;
SUM_ABC=SUM_AB+C;
RUN;
Mà hãy viết lại thành:
Do
DATA OUTPUT;
SET INPUT;
SUM_AB=A+B;
SUM_ABC=SUM_AB+C;
RUN;
Sử dụng If-else thay cho If
Nguyên tắc của việc này là khi viết if
, chương trình phải quét toàn bộ các quan sát trong bảng. Ngược lại khi viết else-if
thì chương trình chỉ cần kiểm tra các quan sát “còn lại” sau lần if
trước. Ví dụ không nên viết:
Dont
DATA OUTPUT;
SET INPUT;
IF COUNTRY="Viet Nam" THEN CAPITAL="Ha Noi";
IF COUNTRY="France" THEN CAPITAL="Paris";
IF COUNTRY="England" THEN CAPITAL="London";
RUN;
Mà nên viết là:
Do
DATA OUTPUT;
SET INPUT;
IF COUNTRY="Viet Nam" THEN CAPITAL="Ha Noi";
ELSE IF COUNTRY="France" THEN CAPITAL="Paris";
ELSE CAPITAL="London";
RUN;
Chú ý độ dài của biến character
Một số dữ liệu khi import từ SQL sẽ gặp tình trạng biến Character có độ dài quá lớn. Nguyên nhân thường là các cột này được để định dạng nvarchar(max)
trong khi tạo ở SQL. Với trình trạng này, ta thường thực hiện một số phương pháp như sau:
Sử dụng độ dài hợp lý
DATA OUTPUT;
SET INPUT;
LENGTH NEW_VAR $20.;
NEWVAR=SUBSTR(OLD_VAR, 1, 20);
RUN;
Sử dụng macro để giảm bớt kích thước của dữ liệu, ví dụ macro Data Reduce Size
Hạn chế các dữ liệu thừa
Việc hạn chế các dữ liệu thừa sẽ làm giảm dung lượng dữ liệu, từ đó khiến chương trình chạy nhanh hơn. Trong cú pháp của DATA STEP
thì tất cả dữ liệu ở mệnh đề SET
sẽ được đọc vào RAM. Do đó, một số thủ thuật nên thực hiện như sau:
Chỉ giữ các biến cần thiết khi đọc dữ liệu
DATA OUTPUT;
SET INPUT (KEEP= A B C);
/********OTHER CODE---------*/
RUN;
Chỉ đọc phần dữ liệu cần thiết
DATA OUTPUT;
SET INPUT (WHERE=(COUNTRY="Viet Nam"));
/********OTHER CODE---------*/
RUN;
Xóa các dữ liệu không cần thiết
PROC DELETE DATA=DATA_A DATA_B DATA_C;
RUN;
hoặc
PROC DATASETS LIBRARY=WORK NOPRINT;
DELETE TEMP:;
RUN;
Câu lệnh trên sẽ xóa tất cả các data có tên bắt đầu bằng TEMP. Do đó thủ thuật khi viết code là đặt tên các bẳng tạm bắt đầu bằng TEMP ví dụ TEMP00, TEMP01, …
Sử dụng procedure một cách hiệu quả
Các procedure đã được SAS tối ưu cho việc xử lý dữ liệu. Do đó, nên sử dụng các Procedure ngay khi có thể. Ví trong minh họa ở phần trên, sử dụng Proc Append sẽ cho kết quả nhanh hơn set data. Ví dụ: Nếu thay
DATA OUTPUT;
SET OUTPUT TEMP00;
RUN;
bằng code
PROC APPEND BASE=OUTPUT DATA=TEMP00; RUN;
Thì thời gian chạy macro BOOSTRAP
giảm xuống còn 14.439s
, nghĩa là nhanh gấp 3 lần.
Ngoài ra, SAS có sẵn các procedure tính toán hiệu năng cao (bắt đầu bằng HP - high performance
). Luôn sử dụng các procedure này khi có thể. Bảng dưới đây liệt kê một số procedure tính toán hiệu năng cao:
Data Preparation | Data Exploration/ Transform | Analysis and Modeling |
---|---|---|
HPSAMPLE | HPSUMMARY | HPLOGISTIC |
HPBIN | HPSPLIT | |
HPCORR | HPREG |
Lưu ý rằng các chương trình này có thể không có sẵn trên SAS phiên phản University.
Tính toán hiệu năng cao (nâng cao)
Cấu hình SAS
Để tăng tốc độ xử lý của SAS, ta có thể tùy chỉnh một số tham số như sau:
Điều chỉnh số lượng CPU là số lượng sô nhân (core) CPU sẽ tham gia vào thực thi câu lệnh. Các máy tính đời mới thường trang bị CPU với 4/8/16 nhân. Ta dùng lệnh:
OPTIONS CPUCOUNT=16;
Điều chỉnh RAM tối đa có thể dùng là dung lượng RAM tối đa mà SAS có thể sử dụng. Mặc định SAS có thể sử dụng 2GB RAM. Để điều chỉnh, ta tìm file config của SAS tại thư mục:
C:\Program Files\SASHome\SASFoundation\9.4\nls\en\
Sau đó mở file sasv9.cfg và sửa tham số sau: -MEMSIZE 2G
thành -MEMSIZE 8G
nghĩa là SAS có thể sử dụng tối đa 8GB RAM.
Sử dụng ổ cứng SSD: Như ta đã biết, SAS có thể dữ lý dữ liệu trên ổ cứng. Do đó, tốc độ ổ cứng càng cao thì SAS xử lý càng nhanh.
Điều chỉnh thư viện WORK: thư viện WORK là thư viện tạm của SAS (xem thêm SAS Enterprise Guide). Thư việc này được đặt mặc định tại thư mục TEMP ở ổ C:. Để thay đổi (ví dụ muốn đặt thư viện này ở ổ cứng SSD là ổ D:) ta mở file sasv9.cfg và thay đổi -WORK "path"
trong đó path
là đường dẫn đến thư mục cần chuyển. Ví dụ -WORK "E:\SASWORKSPACE\WORK"
Đừng LOOP bằng Macro
Khi phân tích Macro Boostrap
phía trên, ta thấy sơ đồ như sau:
Dễ dàng thấy rằng, số lần đọc và ghi dữ liệu quá lớn. Thay vào đó, ta sử dụng loop trong datastep như sau:
DATA OUTPUT;
DO ID = 1 TO 1000;
DO J = 1 TO N;
SET SASHELP.CARS NOBS=N POINT=J;
IF RANUNI(ID)<0.70 THEN
ROLE="T";
ELSE ROLE="V";
OUTPUT;
END;
END;
STOP;
RUN;
Thời gian thực thi câu lệnh là 0.534s
. Trong ví dụ trên, ta cần lưu ý một số vấn để sau:
DO J = 1 TO N; SET SASHELP.CARS NOBS=N POINT=J;
câu lệnh này sẽ loop qua tất cả các quan sát của dữ liệuSASHELP.CARS
. Trong đóJ
là phần tử loop cònN
là số cố định thể hiện tổng số quan sát trong dữ liệuSASHELP.CARS
. Từ dưới mục này ta có thể thực hiện tính toán bất kỳ trên mỗi quan sát củaSASHELP.CARS
.STOP;
là cú phát bắt buộc khi muốn loop qua dữ liệu bằng phương pháp trên. Nếu không có STOP thì câu lệnh sẽ chạy không bao giờ dừng.
Để tăng tính hiệu quả hơn nữa, ta có thể load dataset vào RAM bằng cú pháp đầy đủ như sau:
SASFILE SASHELP.CARS LOAD;
DATA OUTPUT;
DO ID = 1 TO 1000;
DO J = 1 TO N;
SET SASHELP.CARS NOBS=N POINT=J;
IF RANUNI(ID)<0.70 THEN
ROLE="T";
ELSE ROLE="V";
OUTPUT;
END;
END;
STOP;
RUN;
SASFILE SASHELP.CARS CLOSE;
Thời gian thực thi câu lệnh là 0.293s
. Chú ý rằng cú pháp SASFILE SASHELP.CARS LOAD/CLOSE
dùng để load và unload dữ liệu SASHELP.CARS
vào RAM.