4

Я пытаюсь оптимизировать функциональную операцию на Java 8 по сравнению с ее процедурным эквивалентом, но у меня возникла серьезная проблема с производительностью.Оптимизация функционального алгоритма Java 8 по сравнению с его процедурной частью счетчика

Ситуация

Я должен разобрать заголовки HTTP String, List<String> от значения данного перечисления, которые отображающая HeaderName для многих возможных вариантов String, Set<String>.

Пример

Учитывая следующие HttpHeaders:

public static final Map<String, List<String>> httpHeaders = new HashMap<>(); 

httpHeaders.put("Content-Type", Arrays.asList("application/json", "text/x-json")); 
httpHeaders.put("SID", Arrays.asList("ABC123")); 
httpHeaders.put("CORRELATION-ID", Arrays.asList("ZYX666")); 

И мой обычай перечисления:

LogHeaders

protected final String key; 
protected final Set<String> variation; 

SESSION_ID("_sid", Arrays.asList("SESSION-ID", "SID"));  
CORRELATION_ID("cid", Arrays.asList("CORRELATION-ID", "CID")), 


    private LogHeaders(final String logKey, final List<String> logKeyVariations) { 

     this.logKey = logKey; 
     this.logKeyVariations = new HashSet<>(logKeyVariations); 
    } 

@Override 
    public String toString() { 

     return this.logKey; 
    } 

The R esult должна быть карта «LogHeaders.key» со значением, установленным для соответствующих вариантов из HttpHeaders. Только один вариант возможен для данного заголовка:

// {LogHeaders.key : HttpHeaderValue> 
{ 
_sid=[ABC123], 
_cid=[ZYX666] 
} 

Процедурный код

final Map<String, List<String>> logHeadersToValue = new HashMap<>(); 

for (final LogHeaders header : LogHeaders.values()) { 
    for (final String variation : header.getLogKeyVariations()) { 
    final List<String> headerValue = httpHeaders.get(variation); 
     if (headerValue != null) { 
     logHeadersToValue.put(header.logKey, headerValue); 
     break; 
     } 
    } 
} 

Функциональный код

final Map<String, List<String>> logHeadersToValue = 
EnumSet.allOf(LogHeaders.class) 
    .stream() 
    .collect(Collectors.toMap(
    LogHeaders::toString, 
    logHeader -> logHeader.getLogKeyVariations().stream() 
     .map(variation -> httpHeaders.get(variation)).filter(Objects::nonNull) 
     .collect(singletonCollector()))); 


public static <T> Collector<T, ?, T> singletonCollector() { 

    return Collectors.collectingAndThen(Collectors.toList(), list -> { 
     if (list.size() < 1) { 
     return null; 
     } 
     return list.get(0); 
    }); 
} 

Текущий Benchmark

FunctionalParsing : 0.086s 

ProceduralParsing : 0.001s 

У вас есть какие-либо идеи, как Я мог бы оптимизировать свои функции часть?

Спасибо

UPDATE ЭТАЛОН

Я побежал 100k + 100k прогрева итерацию с @Tagir Валеев код:

FunctionalParsing : 0.040s 

ProceduralParsing : 0.010s 

UPDATE ЭТАЛОН # 2

Я побежал 100k прогрев + 100k итерации с кодом @Misha:

FunctionalParsing : 0.025s 

ProceduralParsing : 0.017s 
+3

Первый вопрос, очевидно, как вы оцениваете производительность, во-вторых, насколько велики эти коллекции примерно? – biziclop

+1

Вы, кажется, жертва классической ошибки, когда дело доходит до любой новой и блестящей функциональности f: используйте f для использования f, а вы f; ваш старый код работает просто отлично, так зачем пытаться использовать f, когда он явно не имеет преимуществ? – fge

+1

Никто никогда не говорил, что функциональный код работает быстрее. – AlexWien

ответ

2

Ваш функциональный код не делает то же самое, что и ваш оригинал. Если один из LogHeader s не соответствует заголовку, старый код просто пропустит его, а функциональный код будет вызывать NullPointerException.

Прямой перевод исходного кода потоков будет выглядеть следующим образом:

Map<String, List<String>> logHeadersToValue = Arrays.stream(LogHeaders.values()) 
    .collect(
     HashMap::new, 
     (map, logHeader) -> logHeader.getLogKeyVariations().stream() 
      .filter(httpHeaders::containsKey) 
      .findAny() 
      .ifPresent(x -> map.put(logHeader.key, httpHeaders.get(x))), 
     Map::putAll 
    ); 

Если вы хотите, чтобы это было более производительным и легко читать, рассматривать предварительно рассчитав с Map<String,String> каждого изменения к ключу. Вы можете сделать это, изменив enum так:

enum LogHeaders { 

    SESSION_ID("_sid", "SESSION-ID", "SID"), 
    CORRELATION_ID("cid", "CORRELATION-ID", "CID"); 

    final String key; 
    final Map<String, String> variations; 

    private LogHeaders(final String key, String... variation) { 
     this.key = key; 
     variations = Arrays.stream(variation).collect(collectingAndThen(
       toMap(x -> x, x -> key), 
       Collections::unmodifiableMap 
     )); 
    } 

    // unmodifiable map of every variation to its key 
    public final static Map<String, String> variationToKey = 
     Arrays.stream(LogHeaders.values()) 
      .flatMap(lh -> lh.variations.entrySet().stream()) 
      .collect(collectingAndThen(
          toMap(Map.Entry<String, String>::getKey, Map.Entry<String, String>::getValue), 
          Collections::unmodifiableMap 
      )); // will throw if 2 keys have the same variation 
} 

Этот подход имеет то преимущество, что не суметь быстро, если есть повторяющиеся изменения. И теперь код становится очень простым:

Map<String, List<String>> logHeadersToValue = LogHeaders.variationToKey.keySet().stream() 
    .filter(httpHeaders::containsKey) 
    .collect(toMap(LogHeaders.variationToKey::get, httpHeaders::get)); 
+0

Это разумно и устраняет сложность вычислений в алгоритме. Спасибо за ваш вклад. Я обновляю свой код и публикую новый тест. –

4

Я абсолютно уверен, что вы неправильно сделали тест. Вероятно, вы выполнили его только один раз. Вам все равно, работает ли ваша программа на 0,001 секунды или 0,086 секунды, верно? Это еще быстрее, чем вы можете моргнуть. Поэтому вы, вероятно, захотите запустить этот код много раз. Но кажется, что вы измерили время только один раз и ошибочно полагаете, что каждый последующий прогон займет примерно одно и то же время. Во время первого кода запуска в основном выполняется интерпретатором, а позже он будет скомпилирован JIT и будет работать намного быстрее. Это действительно важно для кода, связанного с потоком.

Что касается вашего кода, кажется, что сборщик не нужен.Вы можете реализовать это следующим образом:

final Map<String, List<String>> logHeadersToValue = 
     EnumSet.allOf(LogHeaders.class) 
      .stream() 
      .collect(Collectors.toMap(
      LogHeaders::toString, 
      logHeader -> logHeader.getLogKeyVariations().stream() 
       .map(httpHeaders::get).filter(Objects::nonNull) 
       .findFirst().orElse(null))); 

Это решение также может быть быстрее, так как он не будет читать больше, чем один заголовок HTTP (как это делается с помощью break в процедурном коде).

+0

Результаты взяты из Junit run. Я буду запускать их 10k раз и получить некоторые результаты для еще 10 k итераций, чтобы JIT выполнял свою работу. Благодарим вас за предложение. –

+0

Я сделал 100-килограммовую разминку + тест 100к. Функциональный синтаксический анализ работает в 0,046, а процедурный синтаксический анализ работает в 0,014 с. В основном 4 раза ~ медленнее, что не очень важно. –