OraGP: translate vs. regexp_replace


Dzisiaj do wyścigu przystępują:

popularna funkcja REGEXP_REPLACE
oraz stary dobry TRANSLATE.

TOR- usunięcie ze stringu wszystkich wartości numerycznych.
3 RUNDY na dystansach: 
        1000 rekordów
      10000 rekordów 
    100000 rekordów

Kto wygra wyścig? Która funkcja szybciej wytnie wszystkie cyferki? (kod źródłowy na końcu posta). 

Wyścig nr 1
Runda  1
DYSTANS: 1000 rekordów

Pierwszy na metę wpada TRANSLATE, ale dosłownie tuż po nim REGEXP_REPLACE.

Czas: 
translate 1000:      0,9 sec
regexp_replace: 1000:     0,25 sec

Runda 2
DYSTANS: 10000 rekordów

Zwiększenie ilości rekordów nie wpłynęło zbytnio na wyniki wyścigu, znów obie funkcję wpadły na metę łeb w łeb, choć tym razem o sekundę szybszy był TRANSLATE.

Czas:
translate 10000:     1 sec
regexp_replace 10000:     2 sec

Runda 3
DYSTANS: 100 000 rekordów

Zwiększenie dystansu do 100 000 nie wyłoniło ostatecznego zwycięzcy, znów wyniki były bardzo zbliżone. 

translate 1000000:   19 sec
regexp_replace 1000000:  23 sec

Wyścig nierozstrzygnięty.

Czyżby obie funkcję w tym zadaniu były tak samo szybkie?

Postanowiłam ponowić wyścig, tym razem zmieniając nieco warunki zewnętrzne, ze słonecznej pogody na deszcz...

Wyścig nr 2
Runda 1
DYSTANS: 1000 rekordów

Pierwszy na metę wpada TRANSLATE wyprzedzając REGEXP'a.

translate 1000:      0,2
regexp_replace 1000:    4,7

Runda 2
DYSTANS: 10000 rekordów

TRANSLATE zdecydowanie wygrywa tę rundę, REGEXP zadyszany wpada na metę daleko za rywalem.

translate 10000:     1 sec
regexp_replace 10000:     45 sec

Runda 3
DYSTANS: 100 000 rekordów

TRANSLATE nie daje szans rywalowi! Do czasu, gdy REGEXP wpada na linie mety, TRANSLATE zdarzył wziąć prysznic i odpocząć przy kolorowym drinku!

translate 100000:   19 sec
regexp_replace 100000:   454 sec

Zdecydowany zwycięzca rundy deszczowej: TRANSLATE!!!

Spójrzmy jeszcze raz na wyniki: 




W wyścigu nr 2 czas funkcji REGEXP_REPLACE wydłużył się dwudziestokrotnie!
Kto tak obstawiał?

Ale co się zmieniło? Dlaczego w pierwszym teście TRANSLATE i REGEXP_REPLACE szły łeb w łeb a w teście drugim TRANSLATE zostawił rywala daleko w tyle? Co wpłynęło na tak drastyczne spowolnienie REGEXP_REPLECE'a?

Czas na rozwiązanie: w wyścigu nr 2 zmieniłam jedynie ustawienia NLS_LANGUAGE sesji.

Pierwszy test, gdzie obie funkcje miały podobne czasy, został przeprowadzony przy ustawieniu NLS_LANGUAGE = ENGLISH, drugi zaś, gdzie TRANSLATE zdecydowanie zwyciężył, został przeprowadzony dla NLS_LANGUAGE = POLISH Jak widać, wydajność funkcji REGEXP zależy w bardzo dużym stopniu od ustawionego języka!

Jeśli używacie funkcji REGEXP - zwróćcie uwagę na ustawienia NLS_LANGUAGE - być może zmiana tych ustawień lub rezygnacja z funkcji  REGEXP na rzecz np. standardowej funkcji TRANSLATE pozwoli wam oszczędzić cenne minuty!

Skrypt testowy: 
Testy przeprowadziłam na bazie Oracle Database 18c Express Edition Release 18.0.0.0.0 - Production.
 set serveroutpu on

DECLARE
    TYPE tab IS TABLE OF VARCHAR2(4000);
    t         tab;
    cnLoops  constant  NUMBER := 100;
    nLoops number := 0;   
    vLang varchar2(20);
    vTestLang varchar2(20); 
    vChar varchar2(4000);
    
    cursor cur is 
    SELECT dbms_random.string('x',2000)
    FROM dual
    CONNECT BY level <= nLoops;
    cnt number := 0;
    
    nTimeStart number := dbms_utility.get_time;  
    nTimeEnd number;  
   
BEGIN

for ncnt in 1..3 loop
  nLoops := case ncnt when  1 then   1000
                      when  2 then  10000 
                      when  3 then 100000  
            end ;         
  nTimeStart  := dbms_utility.get_time;  
  open cur; 
  fetch   cur bulk collect into t; 
  nTimeEnd  := dbms_utility.get_time;  
  
  DBMS_OUTPUT.PUT_LINE('Fetch '|| nLoops|| ': '|| trunc( (nTimeEnd - nTimeStart)/100,2) ); 
 
    for vTestLang in 1..2 loop
    
        if vTestLang = 2 then
            vLang := 'POLISH';
        else
            vLang := 'ENGLISH';
        end if;
       
        execute immediate  'ALTER SESSION SET NLS_LANGUAGE  = '||vLang;
            cnt := 0;
 
            nTimeStart  := dbms_utility.get_time;  
            FOR i IN 1..t.count LOOP
                vchar := translate(t(i),' 0987654321',' ');
                cnt := cnt + 1;
            END LOOP;
       
            nTimeEnd  := dbms_utility.get_time;  
            
            DBMS_OUTPUT.PUT_LINE('Translate '|| cnt ||';' || vLang || ';'|| nLoops|| ': '|| trunc( (nTimeEnd - nTimeStart)/100,2)) ; 

            cnt := 0;
           
            nTimeStart  := dbms_utility.get_time;  

            FOR i IN 1..t.count LOOP
                vchar := regexp_replace(t(i),'\d','');
                cnt := cnt + 1;
            END LOOP;
     
            nTimeEnd  := dbms_utility.get_time;  

           DBMS_OUTPUT.PUT_LINE('regexp '|| cnt ||';' || vLang || ';'|| nLoops|| ': '|| trunc((nTimeEnd - nTimeStart)/100,2));      
       
    end loop;
   close cur;
        
end loop;

exception when others then
    dbms_output.put_line(sqlerrm);
END;
/

Komentarze