Статьи

Google Guava Cache с шаблонами регулярных выражений


Здравствуй!
Всем счастливого Рождества ? Совсем недавно я видел
прекрасную презентацию о Google Guava, и в нашем проекте мы пришли к выводу, что было бы действительно интересно использовать
функцию Cache . Давайте посмотрим на класс Pattern регулярных выражений и его
функцию компиляции . Довольно часто в коде мы видим, что каждый раз, когда используется регулярное выражение, программист неоднократно вызывает вышеупомянутую функцию Pattern.compile () с одним и тем же аргументом, таким образом компилируя одно и то же регулярное выражение снова и снова. Однако, что можно сделать, это кешировать результаты таких компиляций — давайте посмотрим на класс утилит RegexpUtils:

RegexpUtils.java

package pl.grzejszczak.marcin.guava.cache.utils;
 
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
 
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
import static java.lang.String.format;
 
public final class RegexpUtils {
 
    private RegexpUtils() {
        throw new UnsupportedOperationException("RegexpUtils is a utility class - don't instantiate it!");
    }
 
    private static final LoadingCache<String, Pattern> COMPILED_PATTERNS =
            CacheBuilder.newBuilder().build(new CacheLoader<String, Pattern>() {
                @Override
                public Pattern load(String regexp) throws Exception {
                    return Pattern.compile(regexp);
                }
            });
 
    public static Pattern getPattern(String regexp) {
        try {
            return COMPILED_PATTERNS.get(regexp);
        } catch (ExecutionException e) {
            throw new RuntimeException(format("Error when getting a pattern [%s] from cache", regexp), e);
        }
    }
 
    public static boolean matches(String stringToCheck, String regexp) {
        return doGetMatcher(stringToCheck, regexp).matches();
    }
 
    public static Matcher getMatcher(String stringToCheck, String regexp) {
        return doGetMatcher(stringToCheck, regexp);
    }
 
    private static Matcher doGetMatcher(String stringToCheck, String regexp) {
        Pattern pattern = getPattern(regexp);
        return pattern.matcher(stringToCheck);
    }
 
}

Как вы можете видеть, загрузочный кэш Guava с CacheBuilder используется для заполнения кеша новым скомпилированным шаблоном, если он не найден. Из-за кэширования скомпилированного шаблона, если компиляция уже состоялась, он больше не повторится (в нашем случае, так как у нас не было никакого набора срока действия). Теперь простой тест

GuavaCache.java

package pl.grzejszczak.marcin.guava.cache;
 
 
import com.google.common.base.Stopwatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pl.grzejszczak.marcin.guava.cache.utils.RegexpUtils;
 
import java.util.regex.Pattern;
 
import static java.lang.String.format;
 
public class GuavaCache {
    private static final Logger LOGGER = LoggerFactory.getLogger(GuavaCache.class);
    public static final String STRING_TO_MATCH = "something";
 
    public static void main(String[] args) {
        runTestForManualCompilationAndOneUsingCache(1);
        runTestForManualCompilationAndOneUsingCache(10);
        runTestForManualCompilationAndOneUsingCache(100);
        runTestForManualCompilationAndOneUsingCache(1000);
        runTestForManualCompilationAndOneUsingCache(10000);
        runTestForManualCompilationAndOneUsingCache(100000);
        runTestForManualCompilationAndOneUsingCache(1000000);
    }
 
    private static void runTestForManualCompilationAndOneUsingCache(int firstNoOfRepetitions) {
        repeatManualCompilation(firstNoOfRepetitions);
        repeatCompilationWithCache(firstNoOfRepetitions);
    }
 
    private static void repeatManualCompilation(int noOfRepetitions) {
        Stopwatch stopwatch = new Stopwatch().start();
        compileAndMatchPatternManually(noOfRepetitions);
        LOGGER.debug(format("Time needed to compile and check regexp expression [%d] ms, no of iterations [%d]", stopwatch.elapsedMillis(), noOfRepetitions));
    }
 
    private static void repeatCompilationWithCache(int noOfRepetitions) {
        Stopwatch stopwatch = new Stopwatch().start();
        compileAndMatchPatternUsingCache(noOfRepetitions);
        LOGGER.debug(format("Time needed to compile and check regexp expression using Cache [%d] ms, no of iterations [%d]", stopwatch.elapsedMillis(), noOfRepetitions));
    }
 
    private static void compileAndMatchPatternManually(int limit) {
        for (int i = 0; i < limit; i++) {
            Pattern.compile("something").matcher(STRING_TO_MATCH).matches();
            Pattern.compile("something1").matcher(STRING_TO_MATCH).matches();
            Pattern.compile("something2").matcher(STRING_TO_MATCH).matches();
            Pattern.compile("something3").matcher(STRING_TO_MATCH).matches();
            Pattern.compile("something4").matcher(STRING_TO_MATCH).matches();
            Pattern.compile("something5").matcher(STRING_TO_MATCH).matches();
            Pattern.compile("something6").matcher(STRING_TO_MATCH).matches();
            Pattern.compile("something7").matcher(STRING_TO_MATCH).matches();
            Pattern.compile("something8").matcher(STRING_TO_MATCH).matches();
            Pattern.compile("something9").matcher(STRING_TO_MATCH).matches();
        }
    }
 
 
    private static void compileAndMatchPatternUsingCache(int limit) {
        for (int i = 0; i < limit; i++) {
            RegexpUtils.matches(STRING_TO_MATCH, "something");
            RegexpUtils.matches(STRING_TO_MATCH, "something1");
            RegexpUtils.matches(STRING_TO_MATCH, "something2");
            RegexpUtils.matches(STRING_TO_MATCH, "something3");
            RegexpUtils.matches(STRING_TO_MATCH, "something4");
            RegexpUtils.matches(STRING_TO_MATCH, "something5");
            RegexpUtils.matches(STRING_TO_MATCH, "something6");
            RegexpUtils.matches(STRING_TO_MATCH, "something7");
            RegexpUtils.matches(STRING_TO_MATCH, "something8");
            RegexpUtils.matches(STRING_TO_MATCH, "something9");
        }
    }
 
}

Мы проводим серию тестов и проверяем время их выполнения. Обратите внимание, что результаты этих тестов не являются точными из-за того, что приложение не запускается изолированно, поэтому многочисленные условия могут повлиять на время выполнения. Мы заинтересованы в том, чтобы показать некоторую степень проблемы, а не точное время выполнения. Для заданного числа итераций (1,10,100,1000,10000,100000,1000000) мы либо компилируем 10 регулярных выражений, либо используем кеш Guava для извлечения скомпилированного шаблона, а затем сопоставляем их со строкой для сопоставления. Это журналы:

pl.grzejszczak.marcin.guava.cache.GuavaCache:34 Time needed to compile and check regexp expression [1] ms, no of iterations [1]
pl.grzejszczak.marcin.guava.cache.GuavaCache:40 Time needed to compile and check regexp expression using Cache [35] ms, no of iterations [1]
pl.grzejszczak.marcin.guava.cache.GuavaCache:34 Time needed to compile and check regexp expression [1] ms, no of iterations [10]
pl.grzejszczak.marcin.guava.cache.GuavaCache:40 Time needed to compile and check regexp expression using Cache [0] ms, no of iterations [10]
pl.grzejszczak.marcin.guava.cache.GuavaCache:34 Time needed to compile and check regexp expression [8] ms, no of iterations [100]
pl.grzejszczak.marcin.guava.cache.GuavaCache:40 Time needed to compile and check regexp expression using Cache [3] ms, no of iterations [100]
pl.grzejszczak.marcin.guava.cache.GuavaCache:34 Time needed to compile and check regexp expression [10] ms, no of iterations [1000]
pl.grzejszczak.marcin.guava.cache.GuavaCache:40 Time needed to compile and check regexp expression using Cache [10] ms, no of iterations [1000]
pl.grzejszczak.marcin.guava.cache.GuavaCache:34 Time needed to compile and check regexp expression [83] ms, no of iterations [10000]
pl.grzejszczak.marcin.guava.cache.GuavaCache:40 Time needed to compile and check regexp expression using Cache [33] ms, no of iterations [10000]
pl.grzejszczak.marcin.guava.cache.GuavaCache:34 Time needed to compile and check regexp expression [800] ms, no of iterations [100000]
pl.grzejszczak.marcin.guava.cache.GuavaCache:40 Time needed to compile and check regexp expression using Cache [279] ms, no of iterations [100000]
pl.grzejszczak.marcin.guava.cache.GuavaCache:34 Time needed to compile and check regexp expression [7562] ms, no of iterations [1000000]
pl.grzejszczak.marcin.guava.cache.GuavaCache:40 Time needed to compile and check regexp expression using Cache [3067] ms, no of iterations [1000000]

Вы можете найти
источники здесь в каталоге Guava / Cache или перейти по URL
https://bitbucket.org/gregorin1987/too-much-coding/src