프로그래밍 언어/JAVA

Java에서 Map 값을 증가시키는 가장 효율적인 방법

Rateye 2021. 11. 29. 00:23
728x90
반응형
질문 : Java에서 Map 값을 증가시키는 가장 효율적인 방법

이 질문이이 포럼에서 너무 기본적인 것으로 간주되지 않기를 바랍니다. 그러나 우리는 보게 될 것입니다. 여러 번 실행되는 더 나은 성능을 위해 일부 코드를 리팩터링하는 방법이 궁금합니다.

Map (아마도 HashMap)을 사용하여 단어 빈도 목록을 만들고 있다고 가정 해 보겠습니다. 여기서 각 키는 계산되는 단어가있는 문자열이고 값은 단어의 토큰이 발견 될 때마다 증가하는 정수입니다.

Perl에서 이러한 값을 증가시키는 것은 간단합니다.

$map{$word}++;

그러나 Java에서는 훨씬 더 복잡합니다. 여기 내가 현재하고있는 방식 :

int count = map.containsKey(word) ? map.get(word) : 0;
map.put(word, count + 1);

물론 최신 Java 버전의 자동 박싱 기능에 의존합니다. 이러한 값을 증가시키는보다 효율적인 방법을 제안 할 수 있는지 궁금합니다. Collections 프레임 워크를 피하고 대신 다른 것을 사용하는 좋은 성능 이유가 있습니까?

업데이트 : 몇 가지 답변을 테스트했습니다. 아래를 참조하십시오.

답변

일부 테스트 결과

저는 이 질문에 대한 많은 좋은 답을 얻었습니다 – 여러분 감사합니다 – 그래서 저는 몇 가지 테스트를 실행하고 어떤 방법이 실제로 가장 빠른지 알아 내기로 결정했습니다. 내가 테스트 한 다섯 가지 방법은 다음과 같습니다.

  • 질문 에서 제시 한 "ContainsKey"메소드
  • Aleksandar Dimitrov가 제안한 "TestForNull"메서드
  • 행크 게이가 제안한 "AtomicLong"방법
  • jrudolph가 제안한 "Trove"방법
  • phax.myopenid.com에서 제안한 "MutableInt"메소드

 

메서드

내가 한 일은 ...

  1. 아래 표시된 차이점을 제외하고는 동일한 5개의 클래스를 생성했습니다. 각 클래스는 내가 제시한 시나리오의 일반적인 작업을 수행해야 했습니다. 10MB 파일을 열고 읽은 다음 파일에 있는 모든 단어 토큰의 빈도 계산을 수행했습니다. 평균 3초 밖에 걸리지 않았기 때문에 I/O가 아닌 주파수 카운트를 10번 수행했습니다.
  2. I/O 작업이 아닌 10회 반복의 루프 시간을 측정하고 기본적으로  Java Cookbook에서 lan Darwin의 방법을 사용하여 소요된 총 시간(시계 초 단위)을 기록했습니다.
  3. 다섯 번의 테스트를 모두 직렬로 수행한 다음 세 번 더 수행했습니다.
  4. 각 방법에 대한 4개의 결과를 평균화했습니다.

 

결과

관심있는 사람들을 위해 먼저 결과와 아래 코드를 제시하겠습니다.

ContainsKey 메서드는 예상대로 가장 느렸으므로 해당 메서드의 속도와 비교하여 각 메서드의 속도를 제공하겠습니다.

  • ContainsKey : 30.654 초 (기준)
  • AtomicLong : 29.780 초 (1.03 배 빠름)
  • TestForNull : 28.804 초 (1.06 배 빠름)
  • 트루 브 : 26.313 초 (1.16 배 빠름)
  • MutableInt : 25.747 초 (1.19 배 빠름)

 

결론

MutableInt 메서드와 Trove 메서드 만 10 % 이상의 성능 향상을 제공한다는 점에서 훨씬 더 빠릅니다. 그러나 스레딩이 문제라면 AtomicLong이 다른 것보다 더 매력적일 수 있습니다 (정말 잘 모르겠습니다). final 변수로 TestForNull을 실행했지만 그 차이는 미미했습니다.

다른 시나리오에서 메모리 사용량을 프로파일 링하지 않았습니다. MutableInt 및 Trove 메서드가 메모리 사용에 어떻게 영향을 미칠 수 있는지에 대한 좋은 통찰력을 가진 사람의 의견을 듣고 기쁩니다.

개인적으로 MutableInt 메서드가 가장 매력적이라고 생각합니다. 타사 클래스를로드 할 필요가 없기 때문입니다. 그래서 내가 그것에 문제를 발견하지 않는 한, 내가 갈 가능성이 가장 높은 방법입니다.

 

코드

다음은 각 방법의 중요한 코드입니다.

ContainsKey

import java.util.HashMap;
import java.util.Map;
...
Map<String, Integer> freq = new HashMap<String, Integer>();
...
int count = freq.containsKey(word) ? freq.get(word) : 0;
freq.put(word, count + 1);

TestForNull

import java.util.HashMap;
import java.util.Map;
...
Map<String, Integer> freq = new HashMap<String, Integer>();
...
Integer count = freq.get(word);
if (count == null) {
    freq.put(word, 1);
}
else {
    freq.put(word, count + 1);
}

AtomicLong

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
...
final ConcurrentMap<String, AtomicLong> map = 
    new ConcurrentHashMap<String, AtomicLong>();
...
map.putIfAbsent(word, new AtomicLong(0));
map.get(word).incrementAndGet();

Trove

import gnu.trove.TObjectIntHashMap;
...
TObjectIntHashMap<String> freq = new TObjectIntHashMap<String>();
...
freq.adjustOrPutValue(word, 1, 1);

MutableInt

import java.util.HashMap;
import java.util.Map;
...
class MutableInt {
  int value = 1; // note that we start at 1 since we're counting
  public void increment () { ++value;      }
  public int  get ()       { return value; }
}
...
Map<String, MutableInt> freq = new HashMap<String, MutableInt>();
...
MutableInt count = freq.get(word);
if (count == null) {
    freq.put(word, new MutableInt());
}
else {
    count.increment();
}
출처 : https://stackoverflow.com/questions/81346/most-efficient-way-to-increment-a-map-value-in-java
728x90
반응형