我想找出在SAS中执行分组的最佳方法,以便我可以执行一些基准测试。我能想到的最简单的两种方法是Proc SQL
和Proc means
。以下是proc sql
proc sql noprint; /* took 6 mins */
create table summ as select
id,
sum(val)
from
randint
group by
id
;
quit;
我认为有很多方法可以让它快速运行
sasfile
命令将数据首先加载到内存中id
我还可以使用其他选项吗?我应该打开任何SAS选项,以尽可能快地运行?我并不依赖于proc sql和proc手段,所以如果有更快的方法,那么我很想知道它!
我的设置代码如下
options macrogen;
options obs=max sortsize=max source2 FULLSTIMER;
options minoperator SASTRACE=',,,d' SASTRACELOC=SASLOG;
options compress = binary NOSTSUFFIX;
options noxwait noxsync;
options LRECL=32767;
proc fcmp outlib=work.myfunc.sample;
function RandBetween(min, max);
return (min + floor((1 + max - min) * rand("uniform")));
endsub;
run;
options cmplib=work.myfunc;
data RandInt;
do i = 1 to 250000000;
id = RandBetween(1, 2500000);
val = rand("uniform");
output;
end;
drop i;
run;
我的SAS比较宏如下所示
%macro sasbench(dosql = N); %macro _; %mend;
%if &dosql. = Y %then %do;
proc sql noprint; /* took 6 mins */
create table summ as select
id,
sum(val)
from
randint
group by
id
;
quit;
%end;
proc means data=randint sum noprint;
var val ;
class id;
output out = summmeans(drop=_type_ _freq_) sum = /autoname;
run;
%mend;
%sasbench();
/**/
/*sasfile randint load;*/
/*%sasbench();*/
/*sasfile randint close;*/
proc datasets lib=work;
modify randint;
INDEX CREATE id / nomiss;
run;
%sasbench();
答案 0 :(得分:2)
sasfile
只是一个好处。我想如果您的基准测试在同一个sasfile中包含多个运行/不同的技术,那么这将是有意义的。
如果数据未按ID排序,则id的索引会有所帮助。当数据集按id预先排序时,id列元数据将具有设置的sortedby标志,该过程可用于其自身的内部优化,但是不能保证。对于索引,使用option msglevel=i
在日志中获取有关处理期间索引选择的信息性消息。
最快的方法是直接寻址,但需要足够的ram来处理最大的id值作为数组索引:
array ids(250000000) _temporary_
ids(id) + value
下一个最快的方法可能是基于手动编码阵列的散列:
下一个最快的哈希方式可能是具有键suminc的哈希组件对象。
编辑了DATA Step以与评论保持一致
data demo_data;
do rownum = 1 to 1000;
id = ceil(100*ranuni(123)); * NOTE: 100 different groups, disordered;
value = ceil(1000*ranuni(123)); * NOTE: want to sum value over group, for demonstration individual values integers from 1..1000;
output;
end;
run;
data _null_;
if 0 then set demo_data(keep=id value); %* prep pdv ;
length total 8; %* prep keysum variable ;
call missing (total); %* prevent warnings ;
declare hash ids (ordered:'a', suminc:'value', keysum:'total'); %* ordered ensures keys will be sorted ascending upon output ;
ids.defineKey('id');
*ids.defineData('id'); % * not having a defineData is an implicit way of adding only the keys as data, only data + keysum variables are .output;
ids.defineDone();
* read all records and touch each hash key in order to perform tacit total+value summation;
do until (end);
set demo_data end=end;
if ids.find() ne 0 then ids.add();
end;
ids.output(dataset:'sum_value_over_id'); * save the summation of each key combination;
stop;
run;
注意:只能有一个keysum变量。
如果suminc变量设置为1而不是value,则keysum将是count而不是total。
通过散列获取group和sum和count将需要一个显式的defineData用于count和sum变量以及稍微不同的语句,例如:
declare hash ids (ordered:'a');
...
ids.defineData('id', 'count', 'total');
...
if ids.find() ne 0 then do; count=0; total=0; end;
count+1;
total+value;
ids.replace();
...
然而,如果已知值始终是自然数,并且已知组大小是< 10 组大小限制您可以使用value + 10
-group size limit 的和,对数进行数字编码,并通过使用{{处理输出数据来数字解码计数1}} 组大小限制。
对于排序数据,最快的方式很可能是积累的DOW循环。
count = (total - int(total)) * 10
您可能会发现I / O是性能的最大组成部分。
答案 1 :(得分:1)
最佳答案因应用程序而异。在您的示例中,PROC SQL
至少在我的计算机上明显优于PROC MEANS
,但有很多情况下它不会这样做。在这种情况下,它能够在幕后构建哈希表,这很可能,而且非常快 - 只需要通过数据就可以完成所有这些操作。
如果您有足够的空间来存储整个数据集,那么通过使用SASFILE将完整的数据集放入内存中,您当然可以加快速度。尽管如此,你必须在内存中使用开始;只是为了这个目的将其读入内存并不会真正有用,因为无论如何你都在阅读它。
正如理查德所说,有很多方法可以做到这一点。我认为PROC SQL
通常是最快或类似于最简单的情况,因为它是多线程的(而不是数据步骤是单线程),因为它有一个快速的哈希表后端。
PROC MEANS
通常也会具有竞争力,你在示例中显示的情况几乎是最糟糕的情况,因为它有大量的类变量所以我认为它可能会创建一个临时表磁盘。它也是多线程的。将类变量类别减少到2500而不是2,500,000,并且PROC MEANS
比PROC SQL
快一点(但在误差范围内)。
在散列表或DoW循环中的数据步骤累积有时会优于上述两种情况,有时也不会再次取决于数据。在这里,它的表现略胜一筹。数据步骤累积的代码往往有点复杂,这就是为什么我通常不鼓励它,除非节省很多(通常更多的代码需要维护)。 PROC MEANS
和PROC SQL
需要较少的维护而不需要了解。但是在性能至关重要并且这些解决方案碰巧优越的应用中,走这条路线可能是值得的,特别是如果数据步骤有用的话。当然,哈希表方法仅限于将结果拟合到内存中,尽管通常是可管理的。
最终,我鼓励你使用最容易维护的方法,但仍能提供足够的性能;并且在可能的情况下尝试与其他代码保持一致。如果你的大部分代码都在SQL中,那可能就好了。可能不需要SASFILE和索引,除非您执行的操作比上面提到的更复杂。在很多情况下,求和实际上比I / O更多。不要过度复杂化,最终:程序员的时间和QA的难度应该胜过基本的表现,除非你说几个小时的差异。如果您是,那么只需对您的实际用例进行测试,看看哪种方法效果最好。
答案 2 :(得分:0)
如果您假设数据已排序,那么这是另一种解决方案
data sum_value_over_id_v2(keep=id total);
set a.randint(keep=id val);
by id;
total + val;
if last.id then do;
output;
total = 0;
end;
drop val;
run;