2009-09-11 6 views
2

여러 가지 이유로 B + 트리를 작성 중이며 노드 구현에 대해 질문하고 있습니다. 내 현재의 구현 * 사용하고 볼 수있는 것처럼 내가 * * 또는 * 사용할지 여부를 궁금 어디 장소에서 *B + 트리 구현, * * vs *

struct BPlusNode 
{ 
public: 
    //holds the list of keys 
    keyType **keys; 
    //stores the number of slots used 
    size_t size; 
    //holds the array of pointers to lower nodes NULL if this is a leaf node 
    BPlusNode **children; 
    //holds the pointer to the next load to the 'left' 
    BPlusNode *next; 
    //Data page pointers NULL if this is a branch node 
    Bucket **pages; 
}; 

: 내 노드는 현재처럼 보인다.

* *는 두 개의 역 참조 연산을 필요로하므로 *를 사용하는 것보다 느리다는 것을 잘 알고 있지만이 클래스는 많은 재귀를 사용하며 하위 호출에 대한 포인터를 전달하는 것이 훨씬 편리합니다. 재귀 함수. 이것을하기 위해서 포인터 연산을하고 결과 포인터를 전달해야합니다.

와 **

someFunction(BPlusNode* currNode) 
{ 
    ...... 
    someFunction(currNode->children[ChildIndex]); 
} 



함께

*

someFunction(BPlusNode* currNode) 
{ 
    ...... 
    someFunction((currNode->children) + ChildIndex); 
} 

I에서, * 버전 원하는 포인터를 생성하기 위해 메모리의 추가 판독이 있다는 것을 알 수 있지만 * * 버전은 나를 생각하기에 더 쉽습니다 ("The Art of Computer Programming"과 위키 피 디아에서 그려진 다이어그램을 보는 방법과 더 비슷합니다).

누구에게 어떤 생각이 있습니까? 세 번째 옵션에 대한 제안? 왜 다른 사람보다 우월한 증거? 기타?

편집 :
아래 답변을 게시 할 수 있지만 * * 스키마를 사용하면 각 하위 노드 또는 버킷의 전체 내용을 복사 할 필요가 없다는 것을 깨달았습니다. 배열 (즉, 배열의 크기를 변경) 배열을 다시 할당 할 때 * 스키마에 20 개의 하위 노드가있는 경우 20 * sizeof (BPlusNode) 바이트를 복사해야하며 * * scheme의 경우 20 * sizeof (BPlusNode *) 바이트가 필요합니다.

반면에 나는 모든 인서트와 페이지 분할을 수행했기 때문에 이러한 성능 향상은 불필요하며 검색에서 * * ​​*의 이점이 더 중요 할 수도 있습니다.

+2

태그가 C++이므로 포인터 연산 대신 참조로 포인터를 전달할 수없는 이유가 있습니까? – greyfade

+0

someFunction (BPlusNode * & currNode) ....와 같이하고 someFunction (currNode-> children [ChildIndex])에 의해 호출하면 * *보다 더 나쁠 수 있습니다. []는 기본적으로 * (currNode-> children + ChildIndex)와 같으므로 * * 스키마와 동일하게 포인터 산술, 참조 해제가 있습니다. * * 스킴과는 달리 객체에 대한이 포인터는 검색되어 통과되어야합니다. 그래서 효율면에서 적어도 * * 계획과 같습니다. 아마 더 나빠질 수 있습니다. –

+0

@James : greyfade가'someFunction (BPlusNode & currNode)'의 서명을 제안하고 있다고 확신합니다. 이것은 'someFunction (BPlusNode * currNode)'와 기능적으로 (그리고 성능 측면에서) 보이지만 깨끗하게 보이고 실수로 pointee 오브젝트 대신 포인터를 변경하여 발생할 수있는 오류를 피할 수 있습니다. –

답변

2

키와 포인터 데이터에 대해 다른 구조체를 정의합니다. 디스크 구조와 일치해야하는 고정 크기 노드를 사용하겠다고 약속합니다. 이렇게하면 트리를 훨씬 쉽게 매핑 할 수 있습니다.

BPlusNode 구조체는 이러한 매핑 된 데이터 노드를 가리키는 핸들 클래스가되어 트리를 강등하면서 형제를 읽음으로써 prev 및 next 포인터와 같은 것들을 합성합니다.

enum BPlusNodeType { 
    LEAF, BRANCH 
}; 

struct BPlusNodeData { 
    static const size_t max_size = 511; // Try to fit into 4K? 8K? 
    uint16_t size; 
    uint16_t type; 
    keyType key[max_size]; 
    union { 
     Bucket* data[max_size]; 
     BPlusNodeData* children[max_size]; 
    }; 
}; 
+0

+1. 노드 포인터의 고정 된 크기의 내부 배열은'*'또는'**'기반 솔루션보다 빠르고 더 깨끗합니다 (잊어 버릴 동적 메모리 (de/re) 할당 없음). –

+0

나는 우아함을 좋아하지만 문법에 약간은 어색하다. 나는 두 번째 문맥에서 노동 조합이 어떻게 작동하는지 알지만, 첫 번째 노동 조합 선언은 무엇과 잘 들어 맞습니까? –

+0

또한 keyType이 큰 추악한 일이라는 것을 말하면 어떻게 할 것인가? 불행하게도 keyType은 실제로 numericArray라고하는 무언가입니다. 이것은 본질적으로 1에서 n 차원 좌표를 저장하고 그 좌표를 힐버트 공간 채우기 곡선 (1 비트 마술 사용)으로 1D 색인으로 변환하는 동적 배열입니다. 어떻게 영향을 미칠까요? –

1

**을 사용하면 각 BPlusNode* 자식 포인터를 보유하기위한 추가 할당 단계가 필요합니다. 또는 블록을 할당하고 children에있는 각 포인터가이 블록 안에있는 BPlusNode* 요소를 순차적으로 가리 키도록 할 수 있습니다. 그러나 노드 생성 당 하나의 동적 메모리 할당 (및 이에 상응하는 추가 할당 해제 단계)은 여전히 ​​있습니다. 그래서 나는 단 하나의 *을 사용할 것을 절대적으로 권할 것이다.

someFunction((currNode->children) + ChildIndex); 

작성하는 당신을 아픈 경우, 당신은 내가 명확하게 찾을 수있는

someFunction(&currNode->children[ChildIndex]); 

로 다시 작성할 수 있습니다.

+0

그러나 포인터 연산, 산술 결과 dereferncing, 그리고 노드의 this 포인터를 검색하는 것과 같은 방식으로 그렇게하지 않습니까? 또한, 한 번에 트리에 삽입 만하면됩니다. 사실 현재 데이터 세트로 약 25 억 5 천 7 백만 건의 삽입 호출이있을 것입니다 (페이지 구조가 약 255,700 개의 버킷으로 인해 발생 함). 트리는 단지 256의 순서이기 때문에 초기 러쉬에서 많은 분할이 생길 것이므로 어레이의 크기 조정의 용이성이 향상된다는 이점이 있습니까? –

+0

내가 정확히 무엇을 의미하는지 확신 할 수 없다 - 내가 게시 한 두 개의 코드 스 니펫은 모든 점에서 기능적으로 동일 *하며 정확히 동일한 코드가 생성됩니다. 두 번째 스 니펫의 '&'는 포인터가 실제로 참조 해제되는 것을 방지합니다. 포인터 계산 만 발생하여 주소를 최종 결과로 생성합니다. (그리고 당신은'**'버전도 같은 포인터 산술 연산을한다는 것을 알고 있습니다. 그리고 추가 포인터 역 참조를합니다.) 또한 어떤 단계에서도'this' 포인터가 여기에 관련되어 있습니다. 의미합니다. –

+0

글쎄 & (뭔가 [인덱스]) 내가 그때 구조에 대한 포인터를 찾기 위해 돌아 가야했다 dereference를 수행 생각 & 무슨 일이 있었는지 오해. 그래도 삽입하는 동안의 효과는 어떻습니까? 크기를 조정해야하는 배열의 모든 노드를 다시 할당하고 복사하지 않아도됩니까? –

0

당신이 'vector<BPlusNode *> children'와 STL 'vector<keyType *> keys'를 사용하는 것이 더 등겠습니까 : 그것은 다음과 같은 것을 볼 수 있었다

?

너무 단순 할 수도 있지만 더블 인다 우트 ​​(double-indirection)가 C++에서 자주 필요하지는 않습니다.

+0

STL 추가 기능을 사용하면 더 느려지 게됩니다. 이 프로그램은 인덱스에서 많은 수의 읽기 (그리고 디스크에서 훨씬 더 큰 수)를 처리해야합니다. 이 트리에 대한 쿼리가 가능한 한 빨리되기를 바랍니다. –

+0

마지막으로 std :: vector 등을 삭제하면 노드가 커집니다. Vector에는 이와 관련된 공간 오버 헤드가 있고, 몇 십억 개의 이벤트가 있고 따라서 수십만 개의 페이지와 1,000 개의 노드 만있을 때 문제가되지는 않지만 keyType을 사용하더라도 그 자체만으로도 실질적인 공간을 확보 할 수 있습니다. 그러나 수 천억 또는 수개조의 이벤트 (때로는 필요한 금액)가있는 상황을 보면 가능한 한 적은 메모리 오버 헤드가 가장 좋은 것으로 나타납니다. –

+0

@James :'vector '읽기/쓰기는 동적으로 할당 된'keyType * '배열을 읽거나 쓰는 것보다 느려지지 않습니다. 괜찮은 컴파일러는 양쪽 모두에 대해 동일한 코드를 생성합니다. OTOH의'vector' 공간 오버 헤드는 실제적입니다. 일반적으로 고정 크기 또는 동적으로 할당 된 배열을 '벡터'로 대체 할 것을 권하고 싶지만,이 경우에는 Zan Lynx의 고정 크기 배열 접근 방식이 더 작고 빠를 것입니다. –