Round 4
First we need a table called plr_modules:
CREATE TABLE plr_modules (
modseq int4,
modsrc text
);
Then we add the necessary entries:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
INSERT INTO plr_modules VALUES (0, 'library(e1071)'); | |
INSERT INTO plr_modules VALUES (1, 'mysvm <- readRDS("mysvm.RDS")'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CREATE OR REPLACE FUNCTION r_predict4(inp integer) | |
RETURNS text AS | |
$BODY$ | |
result <- predict(mysvm, inp) | |
return(as.character(result[1:1])) | |
$BODY$ | |
LANGUAGE plr IMMUTABLE STRICT | |
COST 100; |
select s.*, r_predict4(s.*) from generate_series(1,1000) s;
541 ms for the first run. 281 ms for each of the following two. Average: 368 ms.
That's only a 73% improvement compared to the original code. predict3() is faster then predict4().
Since the only difference is, that the mysvm object is now global, that might explain the difference. Maybe it is more expensive to work with global than local objects in R?
But there is another way, suggested by a reader of the first installment of this series.
Push the whole loop into R and wrap it into an SRF.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CREATE OR REPLACE FUNCTION r_predict5(inp integer[]) | |
RETURNS SETOF text AS | |
$BODY$ | |
if (pg.state.firstpass) | |
{ | |
library(e1071) | |
mysvm <- readRDS("mysvm.rds") | |
assign("pg.state.firstpass", FALSE, env=.GlobalEnv) | |
} | |
pred <- predict(mysvm, inp) | |
return (levels(pred)[pred]) | |
$BODY$ | |
LANGUAGE plr IMMUTABLE STRICT | |
COST 100 | |
ROWS 1000; |
Let's see and run this statement three times again:
select * from r_predict5(array(select * from generate_series(1,1000)));
341 ms for the first run. 31 ms for each of the following two. Average: 134 ms.
Now that's a 90% improvement compared to the original code. Ten times faster.
If only the post initialization runs are taken into account it's even better: about 45x.
If you process sets anyway and you can live with passing arrays we have a winner here!
Bottom line:
- Do expensive initializations only once.
- Pre-can R objects that are immutable and expensive to create with saveRDS().
- If you process sets, push the whole loop down to R and stay there as long as possible.