ch4 pugixml

27
4빛의 속도록 XML 파싱하기 김경렬

Upload: allting-kim

Post on 19-Jun-2015

269 views

Category:

Software


1 download

TRANSCRIPT

Page 1: Ch4 pugixml

제 4장

빛의 속도록 XML 파싱하기

김경렬

Page 2: Ch4 pugixml

XML 파싱 모형.

❖ SAX(Simple API for XML) - 스트림과 ‘태그 시작, 끝, 문자자료’등의 콜백으로 데이터 처리.!

❖ Pull Parsing - SAX 유사, iterator 객체를 통해 제어.!

❖ DOM(Document Object Model) - 입력을 문서 객체로 변환후 처리.

Page 3: Ch4 pugixml

pugixml DOM 파서

❖ 메모리 안에 들어갈 정도로 작은 문서.!

❖ 방문할 노드들이 서로 참조하는 복잡한 구조를 가진 문서!

❖ 복잡한 방식으로 변환해야 하는 문서.

Page 4: Ch4 pugixml

pugixml 설계상의 선택

❖ 아주 빠르고 가벼운 DOM 기반 XML 조작 라이브러리를 목표로 개발.!

❖ 성능과 XML 검증의 절충점.!

❖ well-formed 검증.!

❖ DTD(Document Type Declaration)은 검증 않함.!

❖ 종종 well-formed 가 아닌 경우도 성공으로 처리.

Page 5: Ch4 pugixml

파싱

❖ 토큰 스트림 대신 문자 스트림에 대해 파싱을 수행.!

❖ UTF-8 문자만 지원.!

❖ 제자리 파싱(In-place parsing) - 스트림에 있는 자료를 직접 처리하는 방식.문자열 복사를 최소화하기 위해.

Page 6: Ch4 pugixml

In-place parsing

❖ 문자열을 만나면 그 문자열의 포인터와 길이를 저장.!

❖ 성능 좋아지지만, 메모리 사용량은 증가.- 원본 스트림 유지.

< n > T h e n o d e t e x t < / n >포인터0xabc3, 길이 130xabc0

Page 7: Ch4 pugixml

In-place parsing - 널문자 처리

❖ 문자열 접근을 보다 빠르게 하기 위하여 널문자를 삽입.!

❖ XML은 문자열 끝 다음 문자는 < 기호로 구분됨.

< n > T h e n o d e t e x t \0 / n >포인터0xabc3, 길이 130xabc0

Page 8: Ch4 pugixml

In-place parsing - 문자 표현 처리❖ 문자열이 표현과 다른 경우 파싱중 처리.!

❖ `line1\xD\xAline2\xDline3\xA\xA` 을line1\xAline2\xAline3\xA\xA` 로 변환.!

❖ 문자 참조 확장 - &#97; 을 a 로 변환.!

❖ 개체 참조 확장 - &lt; (<), &gt;(>), &quot;(“), &apos;(‘); &amp;(&)!

❖ 특성 값 정규화(Attribute-value normalization) - 모든 공백문자를 빈칸으로 변환.

Page 9: Ch4 pugixml

In-place parsing - 문자 표현 처리

❖ 변환 때문에 물자열이 더 길어져서는 안된다.!

❖ 변환 결과가 더 길면 문서 자료를 덮어 쓸 수 있기 때문.

< n > A & # 3 2 ; & l t ; B . < / n >0xabc0

< n > A < B . \0 l t ; B . < / n >0xabc0

Page 10: Ch4 pugixml

In-place parsing - Copy-on-Write❖ memory-mapped file I/O 을 사용.!

❖ 널종료와 텍스트 변환을 지원하기 위해 Copy-on-Write 방식을 적용하여 원본 파일이 변경되는 것을 막음.!

❖ 프로세스 주소 공간에 직접 대응 시키므로 메모리 복사를 피할 수 있음.!

❖ 파일이 캐시되지 않은 경우 커널이 로딩하므로 입출력과 파싱이 병렬적으로 진행.!

❖ 수정된 페이지만 물리적 메모리에 할당되므로 메모리 소비를 줄일 수 있음.

Page 11: Ch4 pugixml

문자별 연산의 최적화Optimizing character-wise operations

❖ 문자 하나에 소비된 평균 프로세서 주기(cycle) 수이다.!

❖ 문자 집합 소속 여부 검출한 문자가 어떤 문자 집합에 속하는지 판정하는 것.

Page 12: Ch4 pugixml

! enum chartype_t!! {!! ! ct_parse_pcdata = 1,! // \0, &, \r, <!! ! ct_parse_attr = 2,!! // \0, &, \r, ', "!! ! ct_parse_attr_ws = 4,! // \0, &, \r, ', ", \n, tab!! ! ct_space = 8,! ! ! // \r, \n, space, tab!! ! ct_parse_cdata = 16,! // \0, ], >, \r!! ! ct_parse_comment = 32,!// \0, -, >, \r!! ! ct_symbol = 64,! ! ! // Any symbol > 127, a-z, A-Z, 0-9, _, :, -, .!! ! ct_start_symbol = 128! // Any symbol > 127, a-z, A-Z, _, :!! };!!! static const unsigned char chartype_table[256] =!! {!! ! 55, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 63, 0, 0, // 0-15!! ! 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31!! ! 8, 0, 6, 0, 0, 0, 7, 6, 0, 0, 0, 0, 0, 96, 64, 0, // 32-47!! ! 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 192, 0, 1, 0, 48, 0, // 48-63!! ! 0, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 64-79!! ! 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 0, 0, 16, 0, 192, // 80-95!! ! 0, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 96-111!! ! 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 0, 0, 0, 0, 0, // 112-127!!! ! 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 128+!! ! … … …!! ! 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192!! };!!! bool ischartype_utf8(char c, chartype_t ct){!! ! return ct & chartype_table[(unsigned char)c];!! }!

Page 13: Ch4 pugixml

특정 구간의 모든 문자

❖ 주어진 문자가 숫자인지 판정하는 함수!

❖ bool isdigit(char ch) { return (ch >= '0' && ch <= '9'); }!

❖ bool isdigit(char ch) { return (unsigned)(ch - '0') < 10; }

Page 14: Ch4 pugixml

UTF-8 바이트열

❖ 연속된 4 바이트가 ASCII 기호를 나타내는 UTF-8 바이트열인지 판별하는 코드.!

❖ (*(const uint32_t*)data & 0x80808080) == 0

Page 15: Ch4 pugixml

표준 라이브러리 is*() 함수

❖ 성능이 중요한 코드에서는 isalpha()등을 피해야 함.!

❖ locale 이 “C” 인지 점검하는 과정때문.

Page 16: Ch4 pugixml

문자열 변환의 최적화 Optimizing string transformations

❖ 문자열 값을 읽고 변환하는 과정에서 시간 소비가 크다.!

❖ A&#32;&lt; B.!

❖ A < B.

Page 17: Ch4 pugixml

PCDATA 파싱 함수

❖ bool 플래그 2개 -> 4개 변형.!

❖ 문자들은 문자 집합 판정 이용.

template <bool opt_eol, bool opt_escape> struct!! strconv_pcdata_impl {!! static char_t* parse(char_t* s) {!! gap g;!! while (true) {!! while (!PUGI__IS_CHARTYPE(*s, ct_parse_pcdata)) ++s;!! if (*s == '<') { // PCDATA ends here!! *g.flush(s) = 0;!! return s + 1;!! } else if (opt_eol && *s == '\r') { // 0x0d or 0x0d 0x0a pair!! *s++ = '\n'; // replace first one with 0x0a!! if (*s == '\n') g.push(s, 1);!! } else if (opt_escape && *s == '&') {!! s = strconv_escape(/s, g);!! } else if (*s == 0) {!! return s;!! } else {!! ++s;!! }!! }!! }!! };!

Page 18: Ch4 pugixml

PCDATA 틈(GAP) 관리❖ &quot; 를 “ 로 대체하면 문자 다섯개의 틈이 생김.!

❖ 두 틈을 병합 - 기존 틈과 새 틈 사이 자료를 앞으로 옮김.!

❖ 읽기/쓰기 포인터보다 좀 더 빠르게 병합(memmove)

Page 19: Ch4 pugixml

제어 흐름의 최적화 Optimizing control flow

❖ 재귀적 하강 파서(recursive-descent parser) 형태에서 성능 향상을 위해 재귀를 반복 루프로 변경.!

❖ 노드 커서는 스택방식으로 동작.!

❖ 스택 공간 소비량이 입력 문서와 무관하게 일정.!

❖ 안정성을 증가.!

❖ 잠재적인 비싼 함수 호출을 피함. ???

Page 20: Ch4 pugixml

분기 순서와 코드 국소성

❖ 자주 실행되는 부분(태그이름,속성)과 거의 실행되지 않는 부분(DOCTYPE)!

❖ 처리 확률 - ‘<‘ 문자 다음 ‘태그 이름’, ‘/‘, ‘!’, ‘?’ 순의 확률로 나타남.!

❖ 코드 조각들의 확률에 따라 재배치!

❖ 인라인 코드량을 제한.

Page 21: Ch4 pugixml

❖ 조건 분기들을 확률이 높은 거에서 낮은 것 순서로 재배치.!

❖ 평균적인 조건 판정 및 분기 수행 횟수가 최소화.

! if (data[0] == '<')!! {!! if (data[1] == '!') { ... }!! else if (data[1] == '/') { ... }!! else if (data[1] == '?') { ... }!! else { /* start-tag or unrecognized tag */ }!! }!!! if (data[0] == '<')!! {!! if (PUGI__IS_CHARTYPE(data[1], ct_start_symbol)) { /* start-tag */ }!! else if (data[1] == '/') { ... }!! else if (data[1] == '!') { ... }!! else if (data[1] == '?') { ... }!! else { /* unrecognized tag */ }!! }!

Page 22: Ch4 pugixml

메모리 안정성 보장❖ 버퍼 오버플로우를 막기 위해.!

❖ 현재 읽기와 버퍼 끝을 비교.!

❖ 하나의 레지스터가 더 필요.!

❖ 함수 호출시 현재 위치와 끝위치를 전달할 포인터 필요!

❖ 널문자 처리.!

❖ 입력 버퍼와 버퍼 크기를 전달하는 경우 사용의 불편함 발생.

Page 23: Ch4 pugixml

DOM 자료 구조

❖ 연결 목록 기반 접근 방식을 사용하는 노드 수정은 O(1).!

❖ 연결 목록 접근 방식- 고정 크기 할당을 위한 빠른 메모리 할당자를 설계하는 것이 임의 크기 할당자보다 쉽다.!

❖ 메모리 절약 - last_child 제거, prev_sibling_cyclic 으로 대체O(1).

struct Node {! Node* first_child;! Node* last_child;! Node* prev_sibling;! Node* next_sibling;!};!struct Node {! Node* first_child;! Node* prev_sibling_cyclic;! Node* next_sibling;!};!

Page 24: Ch4 pugixml

Node* last_child(Node* node) {! return (node->first_child) ?! node->first_child->prev_sibling_cyclic : NULL;!}!!Node* prev_sibling(Node* node) {! return (/node->prev_sibling_cyclic->next_sibling) ?! node->prev_sibling_cyclic : NULL;!}!

Page 25: Ch4 pugixml

스택 기반 메모리 할당

❖ 가변 크기 문자열 할당.!

❖ 할당 국소성을 유지.!

❖ DOM 파괴 속도을 위한 메모리 해제.

Page 26: Ch4 pugixml

스택 할당자

const size_t allocator_page_size = 32768;!struct allocator_page {! allocator_page* next_page;! size_t offset;! char data[allocator_page_size];!};!struct allocator_state {! allocator_page* current;!};!!void* allocate_new_page_data(size_t size) {! size_t extra_size = (size > allocator_page_size) ?! size - allocator_page_size : 0;! return malloc(sizeof(allocator_page) + extra_size);!}

void* allocate_oob(allocator_state* state, size_t size) {! allocator_page* page = (allocator_page*)allocate_new_page_data(size);! // add page to page list! page->next_page = state->current;! state->current = page;! // user data is located at the beginning of the page! page->offset = size;! return page->data;!}!!void* allocate(allocator_state* state, size_t size) {! if (state->current->offset + size <= allocator_page_size) {! void* result = state->current->data + state->current->offset;! state->current->offset += size;! return result;! }! return allocate_oob(state, size);!}!

Page 27: Ch4 pugixml

스택 기반 할당자의 메모리 해제 지원❖ 메모리 해제와 재사용을 위해 참조 카운트 방식 도입.!

❖ 모든 페이지는 32바이트 경계로 정렬되고 모든 페이지 포인터의 하위 다섯 비트는 항상 0이다. ???!

❖ 5비트에 XML 메타 데이터를 저장.!

❖ 할당된 요소의 위치를 페이지 시작 위치를 기준으로 오프셋으로 저장.!

❖ 페이지 포인터의 주소 => (allocator_page*)((char*)(object) -object->offset - offsetof(allocator_page, data))