HashSet <T> .removeAll 메서드는 놀랍도록 느립니다.


92

Jon Skeet은 최근 자신의 블로그에서 흥미로운 프로그래밍 주제를 제기했습니다. "추상화에 구멍이 있습니다 . Liza에게, Liza에게" (강조 추가됨) :

나는 세트가있다 – HashSet사실. 일부 항목을 제거하고 싶습니다… 많은 항목이 존재하지 않을 수 있습니다. 사실, 우리의 테스트 케이스에, 아무도 은 "제거"컬렉션의 항목의 원래 세트에 없습니다. 이 소리 - 참하고 있습니다 매우 쉽게 코드 -. 결국 우리는 우리 Set<T>.removeAll를 도와야합니다, 그렇죠?

명령 줄에서 "소스"집합의 크기와 "제거"컬렉션의 크기를 지정하고 둘 다 빌드합니다. 소스 세트에는 음이 아닌 정수만 포함됩니다. 제거 세트에는 음의 정수만 포함됩니다. 를 사용하여 모든 요소를 ​​제거하는 데 걸리는 시간을 측정합니다. System.currentTimeMillis()는 세계에서 가장 정확한 스톱워치는 아니지만이 경우에는 보시다시피 충분합니다. 코드는 다음과 같습니다.

import java.util.*;
public class Test 
{ 
    public static void main(String[] args) 
    { 
       int sourceSize = Integer.parseInt(args[0]); 
       int removalsSize = Integer.parseInt(args[1]); 
        
       Set<Integer> source = new HashSet<Integer>(); 
       Collection<Integer> removals = new ArrayList<Integer>(); 
        
       for (int i = 0; i < sourceSize; i++) 
       { 
           source.add(i); 
       } 
       for (int i = 1; i <= removalsSize; i++) 
       { 
           removals.add(-i); 
       } 
        
       long start = System.currentTimeMillis(); 
       source.removeAll(removals); 
       long end = System.currentTimeMillis(); 
       System.out.println("Time taken: " + (end - start) + "ms"); 
    }
}

먼저 100 개 항목으로 구성된 소스 세트와 제거 할 100 개를 쉽게 작업 할 수 있습니다.

c:UsersJonTest>java Test 100 100
Time taken: 1ms

좋아요, 그래서 우리는 속도가 느릴 것이라고 예상하지 못했습니다. 분명히 우리는 조금씩 증가시킬 수 있습니다. 100 만 개의 항목과 300,000 개의 항목을 제거해야하는 소스는 어떻습니까?

c:UsersJonTest>java Test 1000000 300000
Time taken: 38ms

흠. 여전히 꽤 빠른 것 같습니다. 이제는 내가 조금 잔인하다고 느낍니다. 모든 제거 작업을 요청합니다. 좀 더 쉽게 만들어 보겠습니다. 소스 항목 300,000 개 및 제거 300,000 개 :

c:UsersJonTest>java Test 300000 300000
Time taken: 178131ms

실례합니다? 거의 3 ? 이런! 38ms에서 관리 한 것보다 작은 컬렉션 에서 항목을 제거하는 것이 더 쉬울 까요?

누군가 이것이 왜 일어나는지 설명 할 수 있습니까? HashSet<T>.removeAll방법 이 왜 그렇게 느린가요?


2
나는 당신의 코드를 테스트했고 그것은 빠르게 작동했습니다. 당신의 경우 완료하는 데 ~ 12ms가 걸렸습니다. 또한 두 입력 값을 10 씩 늘렸고 36ms가 걸렸습니다. 테스트를 실행하는 동안 PC가 집중적 인 CPU 작업을 수행 할 수 있습니까?
Slimu

4
나는 그것을 테스트했고 OP와 같은 결과를 얻었습니다 (글쎄, 나는 그것을 끝내기 전에 중지했습니다). 정말 이상합니다. Windows, JDK 1.7.0_55
JB Nizet 2015

2
이에 대한 공개 티켓이 있습니다 : JDK-6982173
Haozhun

44
Meta 에서 논의 된 것처럼 이 질문은 원래 Jon Skeet의 블로그에서 표절되었습니다 (현재 중재자의 편집으로 인해 질문에서 직접 인용되고 연결되었습니다). 미래의 독자들은 표절 된 블로그 게시물이 실제로 여기에서 받아 들여진 답변과 유사하게 행동의 원인을 설명하고 있다는 점에 유의해야합니다. 따라서 여기에서 답변을 읽는 대신 단순히 클릭 하여 전체 블로그 게시물을 읽을 수 있습니다.
Mark Amery

1
이 버그는 Java 15 : JDK-6394757
ZhekaKozlov

답변:


139

동작은 (다소) javadoc에 문서화되어 있습니다 .

이 구현은 각각에 대해 size 메서드를 호출하여이 집합과 지정된 컬렉션 중 더 작은 것을 결정합니다. 이 집합에 더 적은 요소가있는 경우 구현은이 집합을 반복하여 반복기가 반환 한 각 요소를 차례로 확인 하여 지정된 컬렉션에 포함되어 있는지 확인합니다 . 포함 된 경우 반복기의 remove 메서드를 사용하여이 집합에서 제거됩니다. 지정된 컬렉션에 더 적은 수의 요소가있는 경우 구현은 지정된 컬렉션을 반복하여이 집합의 remove 메서드를 사용하여이 집합에서 반환 된 각 요소를 제거합니다.

이것이 실제로 의미하는 바는 source.removeAll(removals);다음과 같습니다.

  • 경우 생성 removals컬렉션은보다 작은 크기 인 source상기 remove의 방법은 HashSet빠른 불린다.

  • 경우 생성 removals회수가 같거나 더 큰 크기이다 sourceremovals.containsArrayList를 느린 인 불린다.

빠른 수정:

Collection<Integer> removals = new HashSet<Integer>();

있음을 유의 오픈 버그 당신이 설명하는 것과 매우 유사하다. 결론은 아마도 좋지 않은 선택이지만 javadoc에 문서화되어 있기 때문에 변경할 수 없다는 것입니다.


참고로 다음 코드는 다음과 같습니다 removeAll(Java 8에서는 다른 버전을 확인하지 않음).

public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c);
    boolean modified = false;

    if (size() > c.size()) {
        for (Iterator<?> i = c.iterator(); i.hasNext(); )
            modified |= remove(i.next());
    } else {
        for (Iterator<?> i = iterator(); i.hasNext(); ) {
            if (c.contains(i.next())) {
                i.remove();
                modified = true;
            }
        }
    }
    return modified;
}

15
와. 나는 오늘 뭔가를 배웠다. 이것은 나에게 나쁜 구현 선택처럼 보입니다. 다른 컬렉션이 세트가 아니면 그렇게해서는 안됩니다.
JB Nizet 2015

2
@JBNizet 예 그거 이상합니다- 여기 에 제안과 함께 논의되었습니다 -왜 통과하지 않았는지 확실하지 않습니다 ...
assylias

2
고마워요 @assylias ..하지만 어떻게 알아 냈는지 궁금합니다 .. :) 정말 멋지네요 ....이 문제에 직면 했습니까 ???

8
@show_stopper 방금 프로파일 러를 실행했고 그게 ArrayList#contains범인 임을 알았습니다 . 코드를 살펴보면 AbstractSet#removeAll나머지 답변이 제공됩니다.
assylias
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.