5年ぶりに自宅のPCをリプレースしました。これまでEliteの省スペースPCでしたが、最上位でなくても良いと思ったのでビジネス用で1ランクしたのProに乗り換えました。世代を示す番号はG3からG9に進化しています。Windows10から11への移行は面倒で、ドキュメントの位置がOne Driveとローカルで分散していたのが厄介なところです。ファイルを整理しないといけない。
ざっくりと2-3倍程度のスピードアップです。ベンチマークの結果と体感がどれだけ違うか、試してみます。
SASプログラムとログを解析するユーティリティを開発するための備忘録です。This is a memo to develop utility that analyzes the SAS log.
5年ぶりに自宅のPCをリプレースしました。これまでEliteの省スペースPCでしたが、最上位でなくても良いと思ったのでビジネス用で1ランクしたのProに乗り換えました。世代を示す番号はG3からG9に進化しています。Windows10から11への移行は面倒で、ドキュメントの位置がOne Driveとローカルで分散していたのが厄介なところです。ファイルを整理しないといけない。
ざっくりと2-3倍程度のスピードアップです。ベンチマークの結果と体感がどれだけ違うか、試してみます。
LIBNAMEのJSONエンジンの欠点は、32KBを超える長さのデータを抽出できない点です。できないものは仕方ないので、32KBを超えてもSASプログラムを抽出するコードを書きました。
字句解析の状態遷移を考えて "code": の値を抽出してバックスラッシュのエンコードを解いてファイルの保存します。入力のJSONファイルは整形されていないコンパクトや書式の前提です。昔、Lex, Yaccを使っていた記憶を掘り起こした。
/* Code extraction from JSON file of job definition */
%macro extractJobCode(infile=, outfile=, debug=0);
%local/readonly DQ='22'x;
%local/readonly BSL='5c'x;
%local/readonly BS='08'x;
%local/readonly FF='0c'x;
%local/readonly NL='0a'x;
%local/readonly CR='0d'x;
%local/readonly TB='09'x;
%local/readonly SP='20'x;
data _NULL_;
attrib filein length=8 label='input file id';
attrib fileid length=8 label='output file id';
attrib stat length=8 label='State of lexical analysis';
attrib token length=$256 label='Token';
attrib ucode length=$4 label='Unicode';
attrib count length=8;
/* Open input/output files */
filein=fopen("&infile" , 'I', 1, 'B');
fileid=fopen("&outfile", 'O', 1, 'B');
count=0;
stat=0;
rec=&SP;
do while(fread(filein)=0);
rc=fget(filein, rec, 1);
%if &debug=1 %then
%do;
if count < 512 then
put stat=rec=token=;
count=count + 1;
%end;
if stat eq 0 and rec eq &DQ then
stat=1;
else if stat eq 1 then
do;
if rec eq &DQ then
do;
/* Double quotes */
if token eq 'code' then
stat=2;
else
stat=0;
token='';
end;
else
do;
token=catt(token, rec);
stat=1;
end;
end;
else if stat eq 2 then
do;
if rec in (&SP, &TB, &NL) then
stat=2;
else if rec eq ':' then
stat=3;
else
stat=0;
end;
else if stat eq 3 then
do;
if rec in (&SP, &TB, &NL) then
stat=3;
else if rec eq &DQ then
stat=4;
else
stat=0;
end;
else if stat eq 4 then
do;
if rec eq &DQ then
stat=0;
else if rec eq &BSL then
stat=5;
else
do;
rc=fput(fileid, rec);
rc=fwrite(fileid);
end;
end;
else if stat eq 5 then
do;
/* Handling of backslash-escaped characters */
if rec eq 'b' then
do;
rc=fput(fileid, &BS);
rc=fwrite(fileid);
stat=4;
end;
else if rec eq &DQ then
do;
rc=fput(fileid, &DQ);
rc=fwrite(fileid);
stat=4;
end;
else if rec eq &BSL then
do;
rc=fput(fileid, &BSL);
rc=fwrite(fileid);
stat=4;
end;
else if rec eq 'f' then
do;
rc=fput(fileid, &FF);
rc=fwrite(fileid);
stat=4;
end;
else if rec eq 'n' then
do;
rc=fput(fileid, &NL);
rc=fwrite(fileid);
stat=4;
end;
else if rec eq 'r' then
do;
rc=fput(fileid, &CR);
rc=fwrite(fileid);
stat=4;
end;
else if rec eq 't' then
do;
rc=fput(fileid, &TB);
rc=fwrite(fileid);
stat=4;
end;
else if rec eq 'u' then
do;
ucode='';
do i=1 to 4;
rc=fread(filein);
rc=fget(filein, rec, 1);
ucode=catt(ucode, rec);
end;
if ucode eq '0026' then
do;
rc=fput(fileid, '&');
rc=fwrite(fileid);
end;
else if ucode eq '003c' then
do;
rc=fput(fileid, '<');
rc=fwrite(fileid);
end;
else if ucode eq '003e' then
do;
rc=fput(fileid, '>');
rc=fwrite(fileid);
end;
else
do;
rc=fput(fileid, &BSL);
rc=fput(fileid, 'u');
rc=fput(fileid, ucode);
rc=fwrite(fileid);
end;
stat=4;
end;
end;
end;
/* Close input/output files */
rc=fclose(filein);
rc=fclose(fileid);
run;
%mend;
PROC SQLに 昔からあるオプション、機能だが使うのは初めて _METHOD _TREEを試しました。処理時間に問題ありということで、PROC SQL _METHOD _TREE; という具合にオプションを足すと、クエリを評価する順序や評価のツリー構造をログに出力してくれます。
これ DATAステップでインデクス作ったのに使っていないとかログを見ると判ります。性能調整のヒントに FULLSTIMERと一緒に合わせて使えます。
マクロを書いていて地味に困るのがテンポラリのファイル参照名、ライブラリ参照名を定義することです。ユニークでぶつからない名前を定義するために、小さいコードを書きました。参照するファイルが、SASファイルでも動くようにアンダースコアで始まるようにしています。filename関数でテンポラリの名前を作ると #LN00058 のような名前になります。これだと正しくアサインできないときがあるので、アンダースコア始まりにしています。
それと、マクロの定義を削除するステートメントを良く忘れるのでメモしておきます。%SYSMACDELETE で定義したマクロを削除します。AUTOEXECの中で使うけど最後には後始末しておきたいときに使います。
%macro _uname(); %local n; %let n=%sysfunc(monotonic()); _LN%sysfunc(putn(&n, z5.)) %mend; %put %_uname;
CPORTで移送ファイルを作成するときに圧縮できることを実験で確認した。移送ファイル形式は圧縮されてはいるが圧縮率は高くない。なので、FILENAMEステートメントでZIPの圧縮を試してみた。圧縮した移送ファイルからCIMPORTできて /tmp/new のディレクトリにもSASファイルがあることを確認できた。。
filename myfile1 '/tmp/foo.xpt'; proc cport lib=sashelp file=myfile1 memtype=data; select air baseball class; run; filename myfile2 zip "/tmp/foo.xpt.zip"; proc cport lib=sashelp file=myfile2 memtype=data; select air baseball class; run; libname newlib '/tmp/new'; proc cimport lib=newlib infile=myfile2; run;
リッチな本番環境だと問題が起きるが、開発環境だと問題が起きにくいというのはよくある。本番のデータは大きいが開発環境はダミーで小さなものしかない。そんなときは、メモリとかのパラメータをわざと低くしたりすると再現しやすくなる。メモリが不足していると辺りが付いていたら使える手です。
追記: タイムアウト系のエラーは、起動のスクリプトに sleep を入れることで再現させることができます。例えば、Workspace Serverの起動のタイムアウトは *_usermods.sh にシェルのコマンド sleep 120; とか入れると再現できます。
負荷が高いというのは Linuxであれば stress(1) とかで疑似的な負荷を掛ける手もあります。負荷掛けると狙っているのとは別なエラーも出るので見極め必要です。