건너 뛰기 목록 대 이진 검색 트리


답변:


257

건너 뛰기 목록은 동시 액세스 / 수정에 더 적합합니다. Herb Sutter는 동시 환경에서의 데이터 구조에 관한 기사 를 썼습니다 . 더 자세한 정보가 있습니다.

이진 검색 트리의 가장 자주 사용되는 구현은 레드-블랙 트리 입니다. 트리가 수정 될 때 동시 문제가 발생하며 종종 균형을 재조정해야합니다. 재조정 작업은 트리의 많은 부분에 영향을 줄 수 있으며, 이로 인해 많은 트리 노드에서 뮤텍스 잠금이 필요합니다. 건너 뛰기 목록에 노드를 삽입하는 것이 훨씬 더 현지화되므로 영향을받는 노드에 직접 연결된 노드 만 잠 가야합니다.


Jon Harrops 의견에서 업데이트

나는 Fraser and Harris의 최신 논문 Concurrent programming without locks을 읽었다 . 잠금없는 데이터 구조에 관심이 있다면 정말 좋은 것들입니다. 이 백서는 트랜잭션 메모리 및 이론적 연산 다중 워드 비교 및 ​​스왑 MCAS에 중점을 둡니다 . 하드웨어가 아직 지원하지 않기 때문에 둘 다 소프트웨어에서 시뮬레이션됩니다. MCAS를 소프트웨어로 구축 할 수 있다는 사실에 상당히 감동했습니다.

가비지 수집기가 필요하므로 트랜잭션 메모리 항목을 특히 강력하게 찾지 못했습니다. 또한 소프트웨어 트랜잭션 메모리 에는 성능 문제가 있습니다. 그러나 하드웨어 트랜잭션 메모리가 일반화되면 매우 기쁠 것입니다. 결국 그것은 여전히 ​​연구 중이며 약 10 년 동안 생산 코드에 사용되지 않을 것입니다.

섹션 8.2에서는 여러 동시 트리 구현의 성능을 비교합니다. 나는 그들의 결과를 요약 할 것이다. 50, 53 및 54 페이지에 매우 유익한 그래프가 있으므로 pdf를 다운로드하는 것이 좋습니다.

  • 잠금 건너 뛰기 목록 은 엄청나게 빠릅니다. 동시 액세스 수에 따라 매우 잘 확장됩니다. 이것이 스킵리스트를 특별하게 만드는 이유이며, 다른 잠금 기반 데이터 구조는 압력을 받고 삐걱 거리는 경향이 있습니다.
  • 잠금없는 건너 뛰기 목록건너 뛰기 목록 잠금보다 일관되게 빠르지 만 간신히 있습니다.
  • 트랜잭션 건너 뛰기 목록 은 잠금 및 비 잠금 버전보다 일관되게 2-3 배 느립니다.
  • 동시 액세스 상태에서 적갈색 나무 잠김 . 각 동시 사용자마다 성능이 선형 적으로 저하됩니다. 알려진 두 가지 잠금 레드-블랙 트리 구현 중 하나는 본질적으로 트리 재조정 동안 전역 잠금을 갖습니다. 다른 하나는 멋진 (그리고 복잡한) 잠금 에스컬레이션을 사용하지만 여전히 전역 잠금 버전을 크게 수행하지는 않습니다.
  • 자물쇠가없는 적갈색 나무 는 존재하지 않습니다 (더 이상 사실이 아닙니다, 업데이트 참조).
  • 트랜잭션 레드-블랙 트리는 트랜잭션 스킵리스트와 비교할 수 있습니다. 그것은 매우 놀랍고 유망한 것이었다. 트랜잭션 메모리 (쓰기가 훨씬 더 느리더라도) 비 동시 버전에서 빠른 검색 및 교체만큼 쉬울 수 있습니다.

업데이트
: 잠금 장치가없는 나무에 대한 논문입니다 여기에 잠금 - 무료 레드 - 블랙 트리 사용 CAS가 .
나는 그것을 깊이 들여다 보지 않았지만 표면에서는 단단 해 보인다.


3
비 회생 스킵 목록에서 노드의 약 50 %는 단일 링크 만 가져야하므로 삽입 및 삭제가 매우 효율적입니다.
Adisak

2
재조정에는 뮤텍스 잠금이 필요하지 않습니다. cl.cam.ac.uk/research/srg/netos/lock-free
Jon Harrop

3
@Jon, 예, 아니오 알려진 잠금없는 레드-블랙 트리 구현은 없습니다. 프레이저와 해리스는 트랜잭션 메모리 기반 레드-블랙 트리의 구현 방법과 성능을 보여줍니다. 트랜잭션 메모리는 여전히 연구 분야에서 많이 사용되므로 프로덕션 코드에서 빨강-검은 나무는 여전히 나무의 많은 부분을 잠 가야합니다.
deft_code

4
@deft_code : 인텔은 최근 Haswell에서 TSX 를 통해 트랜잭션 메모리 구현을 발표했습니다 . 이것은 당신이 언급 한 무료 데이터 구조를 잠그는 것이 흥미로울 수 있습니다.
Mike Bailey

2
생각 소다 '대답은 지금 아마 선호하는 해답이 될 수 있어야하므로 더 최신 (2015) 오히려이 답변 (2012) 이상을입니다.
fnl

81

첫째, 무작위 데이터 구조를 최악의 경우 보장하는 구조와 비교할 수는 없습니다.

건너 뛰기 목록은 Dean and Jones의 "건너 뛰기 목록과 이진 검색 트리 간의 이중성 탐색"에 자세히 설명되어있는 RBST (Random Balanced Binary Search Tree)와 동일 합니다.

다른 방법으로, 최악의 성능을 보장하는 결정적 스킵 목록을 가질 수도 있습니다. Munro et al.

위의 주장과 달리 동시 프로그래밍에서 잘 작동하는 이진 검색 트리 (BST)를 구현할 수 있습니다. 동시성에 중점을 둔 BST의 잠재적 인 문제는 RB (Red-Black) 트리에서와 마찬가지로 밸런싱에 대한 보장을 쉽게 얻을 수 없다는 것입니다. (그러나 "표준", 즉 무작위, 건너 뛰기 목록은 이러한 보장도 제공하지 않습니다.) 항상 균형을 유지하는 것과 좋은 (그리고 프로그래밍하기 쉬운) 동시 액세스간에 균형이 있으므로 완화 된 RB 트리가 일반적으로 사용됩니다 좋은 동시성이 필요할 때 휴식은 바로 나무를 재조정하지 않는 것입니다. 다소 오래된 (1998 년) 설문 조사에 대해서는 Hanke의``동시 레드-블랙 트리 알고리즘의 성능 '' [ps.gz]을 참조하십시오 .

이것에 대한 가장 최근의 개선 중 하나는 소위 색채 나무입니다 (기본적으로 검은 색이 1이고 빨간색이 0이되도록 가중치가 있지만 사이에 값을 허용합니다). 그리고 크로마 트리는 스킵 목록에 어떻게 대응합니까? Brown et al. "비 차단 트리에 대한 일반적인 기술" (2014)은 다음과 같이 말해야합니다.

128 개의 스레드를 사용하는 알고리즘은 잠금 기반 AVL 트리 인 Bronson et al. 63 ~ 224 %, 소프트웨어 트랜잭션 메모리 (STM)를 13 ~ 134 배 사용하는 RBT

추가 편집 : Pugh의 잠금 기반 건너 뛰기 목록은 프레이저와 해리스 (2007) "잠금없는 동시 프로그래밍" 에서 자체 잠금이없는 버전 (여기의 상단 답변에서 충분히 주장 된 지점)에 가깝게 벤치 마크되었습니다 . 또한 우수한 동시 작동을 위해 조정되었습니다. cf. Pugh의 "건너 뛰기 목록의 동시 유지 보수" 는 다소 경미한 방법입니다. 그럼에도 불구하고 한 가지 최신 / 2009 논문 "단순 낙관적 건너 뛰기 목록 알고리즘"Pugh의 잠금 기반 동시 건너 뛰기 목록 구현을 제안하는 Herlihy et al.은 Pugh에게 충분한 설득력을 제공하지 않는다고 비난했다. Herlihy et al. 건너 뛰기 목록의 간단한 잠금 기반 구현은 실제로 JDK의 잠금없는 구현뿐만 아니라 확장도 실패하지만 높은 경합 (50 % 삽입, 50 % 삭제 및 0 % 조회)에 대해서만 표시됩니다. 해리스는 전혀 테스트하지 않았습니다. 프레이저와 해리스는 75 % 조회, 12.5 % 삽입 및 12.5 % 삭제 (~ 500K 요소의 건너 뛰기 목록) 만 테스트했습니다. Herlihy et al.의 간단한 구현. 또한 테스트 한 경합이 낮은 경우 (70 % 조회, 20 % 삽입, 10 % 삭제) JDK의 잠금없는 솔루션에 가깝습니다. 스킵 목록을 충분히 크게 만들 때 (예 : 200K에서 2M 요소로)이 잠금에 대한 잠금없는 솔루션을 실제로 능가했습니다. Herlihy et al. Pugh의 증거에 대한 그들의 끊기를 극복하고 그의 구현도 테스트했지만 아아 그들은 그렇게하지 않았습니다.

EDIT2 : 모든 벤치 마크에서 (2015 년 출판) 머더로드를 발견했습니다 : Gramoli의 "동기화에 대해 알고 싶었던 것 이상. Synchrobench, 동시 알고리즘에 대한 동기화의 영향 측정" :이 질문과 관련된 발췌 이미지가 있습니다.

여기에 이미지 설명을 입력하십시오

"Algo.4"는 상기 언급 된 브라운 등의 전구체 (구형, 2011 버전)이다. (2014 버전이 얼마나 좋거나 나쁜지 모르겠습니다). "Algo.26"은 위에서 언급 한 Herlihy입니다. 보시다시피 업데이트시 휴지통에 버리고 여기에서 사용 된 Intel CPU는 원래 용지의 Sun CPU보다 훨씬 나쁩니다. "Algo.28"은 JDK의 ConcurrentSkipListMap입니다. 다른 CAS 기반 건너 뛰기 목록 구현과 비교했을 때 기대했던 것만 큼 효과적이지 않습니다. 높은 관심을 받고있는 승자는 Crain 등의 잠금 기반 알고리즘 (!!) 인 "Algo.2"입니다. 에서 "A 경합 친화적 이진 검색 나무" 와 "Algo.30"에서 "회전 skiplist"입니다 "멀티 코어에 대한 대수 데이터 구조" . ". Gramoli는이 세 가지 승자 알고리즘 논문의 공동 저자입니다. "Algo.27"은 Fraser의 건너 뛰기 목록의 C ++ 구현입니다.

Gramoli의 결론은 유사한 건너 뛰기 목록을 망치는 것보다 CAS 기반 동시 트리 구현을 망치는 것이 훨씬 쉽다는 것입니다. 그리고 수치에 따르면 동의하기가 어렵습니다. 이 사실에 대한 그의 설명은 다음과 같습니다.

잠금이없는 트리를 설계하는 데 어려움은 여러 참조를 원자 적으로 수정하는 데 어려움이 있습니다. 스킵 목록은 후속 포인터를 통해 서로 연결된 타워로 구성되며 각 노드는 바로 아래에있는 노드를 가리 킵니다. 각 노드는 후계자 타워와 그 아래에 후속 노드가 있기 때문에 종종 트리와 유사한 것으로 간주되지만, 하향 포인터는 일반적으로 불변이므로 노드의 원자 수정을 단순화한다는 것이 큰 차이점입니다. 이 차이는 아마도 스킵이 그림 [위]에서 볼 수 있듯이 경쟁이 심한 나무보다 성능이 뛰어난 나무를 나열하는 이유 일 것입니다.

이러한 어려움을 극복하는 것은 브라운 등의 최근 연구에서 주요 관심사였다. 그들은 다중 레코드 LL / SC 복합 "기본 체"를 구축 하는 것에 대해 완전히 별도의 (2013) 논문 "비 차단 데이터 구조를위한 기본적 프리미티브"를 보유하고 있으며,이를 LLX / SCX라고하며 자체적으로 (기계 수준) CAS를 사용하여 구현됩니다. 브라운 등. 이 LLX / SCX 빌딩 블록을 2014 년 (2011 년은 아님) 동시 트리 구현에 사용했습니다.

"핫 스폿 없음"/ CF (Contention-friendly) 건너 뛰기 목록 의 기본 아이디어를 여기에 요약 할 가치가 있다고 생각 합니다.. 편안한 RB 트리 (및 유사한 동시성 데이터 구조)에서 필수적인 아이디어를 추가합니다. 타워는 더 이상 삽입 즉시 구축되지 않지만 경합이 줄어들 때까지 지연됩니다. 반대로, 높은 타워를 삭제하면 많은 경합이 발생할 수 있습니다. 이것은 Pugh의 1990 년 동시 건너 뛰기 목록 논문만큼이나 거꾸로 관찰되었는데, 이는 Pugh가 삭제에 대한 포인터 반전을 도입 한 이유입니다 (스킵 목록에 대한 Wikipedia의 페이지가 여전히 오늘날까지 언급되지 않은 경건). CF 건너 뛰기 목록은이 단계를 한 단계 더 높이고 높은 타워의 상위 레벨 삭제를 지연시킵니다. CF 스킵리스트의 두 가지 지연된 조작은 (CAS 기반) 별도의 가비지 콜렉터와 유사한 스레드에 의해 수행되며, 작성자는이를 "적응 스레드"라고합니다.

Synchrobench 코드 (테스트 된 모든 알고리즘 포함)는 https://github.com/gramoli/synchrobench 에서 사용할 수 있습니다 . 최신 브라운 외. 구현 (위에 포함되지 않음)은 http://www.cs.toronto.edu/~tabrown/chromatic/ConcurrentChromaticTreeMap.java 에서 사용 가능합니다 . 누구나 32 개 이상의 코어 시스템을 사용할 수 있습니까? J / K 제 요점은 당신이 스스로를 실행할 수 있다는 것입니다.


12

또한 주어진 답변 외에도 균형 잡힌 트리와 비슷한 성능과 결합 된 구현 용이성. 스킵 목록이 효과적으로 구현 내부에 연결된 목록을 가지고 있기 때문에 순서 순회 (정방향 및 역방향)를 구현하는 것이 훨씬 간단하다는 것을 알았습니다.


1
빈 트리에 대한 순차 순회가 "def func (node) : func (left (node)); op (node); func (right (node))"처럼 간단하지 않습니까?
Claudiu

6
물론, 하나의 함수 호출에서 모두 순회하려면 true입니다. 그러나 std :: map과 같이 반복자 스타일의 트래버스를 원한다면 훨씬 더 성가 시게됩니다.
Evan Teran

@Evan : CPS로만 쓸 수있는 기능적 언어가 아닙니다.
Jon Harrop

@Evan : def iterate(node): for child in iterate(left(node)): yield child; yield node; for child in iterate(right(node)): yield child;? =). 로컬이 아닌 컨트롤 iz awesom .. @Jon : CPS로 작성하는 것은 고통스러운 일이지만, 계속해서 의미가 있습니까? 제너레이터는 기본적으로 파이썬의 특별한 연속 사례입니다.
Claudiu

1
@Evan : 예 수정하는 동안 노드 매개 변수가 트리에서 잘리는 한 작동합니다. C ++ 통과는 동일한 제약 조건을 갖습니다.
deft_code 2018

10

실제로 내 프로젝트의 B-tree 성능이 건너 뛰기 목록보다 나은 것으로 나타났습니다. 건너 뛰기 목록은 이해하기 쉽게 보인다하지만 B-트리를 구현하는 것은하지 않는 하드.

내가 아는 한 가지 장점은 일부 영리한 사람들이 원자 연산 만 사용하는 잠금없는 동시 건너 뛰기 목록을 구현하는 방법을 연구했다는 것입니다. 예를 들어, Java 6에는 ConcurrentSkipListMap 클래스가 포함되어 있으며 미친 경우 소스 코드를 읽을 수 있습니다.

그러나 동시 B-tree 변형을 작성하는 것은 그리 어렵지 않습니다. 다른 사람이 수행 한 것을 보았습니다. 트리를 밟을 때 노드를 "경우에 따라"선점 적으로 분할하고 병합하면 다음과 같이 할 필요가 없습니다. 교착 상태에 대해 걱정하고 한 번에 두 레벨의 나무를 잠그면됩니다. 동기화 오버 헤드는 약간 높지만 B- 트리가 더 빠를 것입니다.


4
나는 당신이 이진 트리 B-트리 호출하지한다고 생각합니다, 그 이름을 가진 완전히 다른 DS가
Shihab Shahriar 칸

8

인용 한 Wikipedia 기사에서 :

전체 목록을 인쇄하는 것과 같이 오름차순으로 모든 노드를 방문하도록하는 Θ (n) 연산은 건너 뛰기 목록의 레벨 구조를 최적의 방식으로 배후에서 무작위화할 수있는 기회를 제공합니다. 건너 뛰기 목록을 O (log n) 검색 시간으로 가져옵니다. [...] 최근에 [Θ]와 같은 Θ (n) 연산을 수행하지 않은 스킵 목록 은 항상 가능하기 때문에 전통적인 밸런스 트리 데이터 구조와 동일한 절대 최악의 성능 보장을 제공하지 않습니다. (매우 낮은 확률로) 스킵리스트를 만드는 데 사용 된 코인 플랩은 균형이 잘 잡히지 않은 구조를 만들 것입니다

편집 : 그래서 그것은 절충입니다 : 건너 뛰기 목록은 불균형 한 트리로 변질 될 위험이있는 메모리를 덜 사용합니다.


이는 건너 뛰기 목록을 사용하지 않는 이유입니다.
Claudiu

7
"100 레벨 1 요소에 대한 확률은 1,267,650,600,228,229,401,496,703,205,376에서 정확히 1입니다".
peterchen

8
왜 더 적은 메모리를 사용한다고 말 하시겠습니까?
조나단

1
@ peterchen : 감사합니다. 결정적 건너 뛰기 목록에서 발생하지 않습니까? @Mitch : "건너 뛰기 목록은 메모리를 덜 사용합니다". 건너 뛰기 목록은 균형 이진 트리보다 적은 메모리를 어떻게 사용합니까? 그들은 모든 노드와 중복 노드에 4 개의 포인터가있는 반면 나무에는 2 개의 포인터 만 있고 중복은 없습니다.
Jon Harrop

1
@Jon Harrop : 레벨 1의 노드는 노드 당 하나의 포인터 만 필요합니다. 더 높은 레벨의 노드는 노드 당 두 개의 포인터 만 필요합니다 (물론 한 노드에서 다음 노드로, 한 노드에서 그 아래 레벨로). 물론 레벨 3 노드는 해당 값에 대해 총 5 개의 포인터를 사용하고 있음을 의미합니다. 물론 이것은 여전히 ​​많은 메모리를 빨아 들일 것입니다 (쓸모없는 건너 뛰기 목록을 원하고 큰 데이터 세트가있는 경우 이진 검색 외에도) ...하지만 뭔가 빠진 것 같습니다 ...
Brian

2

건너 뛰기 목록은 목록을 사용하여 구현됩니다.

잠금이없는 솔루션은 단독 및 이중 연결 목록에 존재하지만 O (logn) 데이터 구조에 CAS 만 직접 사용하는 잠금이없는 솔루션은 없습니다.

그러나 CAS 기반 목록을 사용하여 건너 뛰기 목록을 작성할 수 있습니다.

(CAS를 사용하여 작성된 MCAS는 임의의 데이터 구조를 허용하며 MCAS를 사용하여 개념 증명 레드 블랙 트리가 작성되었습니다).

그래서, 이상하게도, 그들은 매우 유용한 것으로 판명되었습니다 :-)


5
"O (logn) 데이터 구조에 CAS 만 직접 사용하는 잠금없는 솔루션은 없습니다." 사실이 아니다. 카운터의 예를 참조 cl.cam.ac.uk/research/srg/netos/lock-free
존 Harrop에

-1

건너 뛰기 목록에는 잠금 제거 기능이 있습니다. 그러나 런트 타임은 새로운 노드의 레벨이 어떻게 결정되는지에 달려 있습니다. 일반적으로 이것은 Random ()을 사용하여 수행됩니다. 56000 단어의 사전에서 건너 뛰기 목록은 재생 트리보다 시간이 더 걸리고 트리는 해시 테이블보다 시간이 더 걸렸습니다. 처음 두 개는 해시 테이블의 런타임과 일치 할 수 없습니다. 또한 해시 테이블의 배열을 동시에 제거 할 수도 있습니다.

참조 위치가 필요한 경우 건너 뛰기 목록 및 유사한 순서의 목록이 사용됩니다. 예 : 응용 프로그램에서 날짜 다음과 이전의 항공편 찾기

메모리 이진 검색 재생 트리는 훌륭하고 더 자주 사용됩니다.

건너 뛰기 목록 대 재생 트리 대 해시 테이블 런타임 사전 찾기 op


잠깐 살펴본 결과 SplayTree보다 SkipList가 더 빠른 것으로 표시됩니다.
Chinasaur

스킵리스트의 일부로 무작위 화를 가정하는 것은 잘못된 것입니다. 요소를 건너 뛰는 방법이 중요합니다. 확률 적 구조에 대해 무작위 화가 추가됩니다.
user568109
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.