std: map 을 쓴다 -- 바이트 열에서 값으로
컴파일러는 이름을 끊임없이 찾는데 리스트 선형 탐색은 그 모양이 아니다. 키는 맵이 소유하는 한 버퍼에 복사되고 슬롯은 그 안의 어디인지만 적는다 -- R11 이 말하는 아레나와 핸들 모양이고, 그래서 맵을 놓는 것이 엔트리마다 하나가 아니라 두 번의 해제다. 개방 주소법에 선형 탐사. 표는 2의 거듭제곱이라 나눗셈이 아니라 마스크이고, 탐사가 길어지는 것이 표가 차는 것보다 먼저라 3/4 에서 자란다. 길에서 고친 것 넷: - 인스턴스를 만들 때 선언 유닛으로 전환하지 않아서, 필드 타입 Slot(V) 를 호출자 유닛에서 찾고 있었다. - mem.alloc_slice 가 원소 타입으로 단순한 이름만 받았다. Slot(V) 같은 인스턴스도 받는다. - 쓸 수 있는지를 바인딩이 아니라 소유된 것이 정한다. let p: ^[]mut T 는 p 를 고정하고 그것이 소유한 것은 쓸 수 있게 둔다. 슬라이스 인덱스도 마찬가지다. - 메서드 인자에 자유 함수와 같은 호출 한정 약화가 없었다. 알게 된 것: 대여는 루트 단위라 self 의 한 필드에 쓰는 동안 다른 필드를 읽을 수 없다. 지역으로 빼거나 메서드를 나누면 되지만, 필드 단위 대여가 있으면 훨씬 편할 자리다. count 5 / fn 2 let 3 missing 0 / grown 64 / after 40 / balanced 218/218, 28/28.
This commit is contained in:
+174
-2
@@ -1,4 +1,176 @@
|
||||
unit map;
|
||||
pub struct Map(K, V) {
|
||||
unit std.map;
|
||||
|
||||
// A table from a run of bytes to a value.
|
||||
//
|
||||
// A compiler looks names up constantly and a linear scan over a list is the
|
||||
// wrong shape for that. Keys are copied into one buffer the map owns and each
|
||||
// slot records where in it the key sits -- the arena-and-handle shape R11 asks
|
||||
// for, which also means letting go of the map is two frees and not one per
|
||||
// entry.
|
||||
//
|
||||
// Open addressing with linear probing. The table is a power of two so the
|
||||
// index is a mask rather than a division, and it grows at three quarters full
|
||||
// because probing gets long well before the table gets full.
|
||||
|
||||
pub struct Slot(V) {
|
||||
at: usize,
|
||||
len: usize,
|
||||
used: bool,
|
||||
value: V,
|
||||
}
|
||||
|
||||
pub struct Map(V) {
|
||||
slots: ^[]mut Slot(V),
|
||||
bytes: ^[]mut u8,
|
||||
used_bytes: usize,
|
||||
count: usize,
|
||||
|
||||
pub fn with_capacity(n: usize) -> !Self {
|
||||
var room: usize = 8;
|
||||
while room < n * 2 { room = room * 2; }
|
||||
let table: ^[]mut Slot(V) = try mem.alloc_slice(Slot(V), room);
|
||||
let text: ^[]mut u8 = try mem.alloc_slice(u8, 64);
|
||||
var i: usize = 0;
|
||||
while i < room {
|
||||
table.^[i].used = false;
|
||||
i = i + 1;
|
||||
}
|
||||
return Self{ slots: table, bytes: text, used_bytes: 0, count: 0 };
|
||||
}
|
||||
|
||||
pub fn count_of(self: &Self) -> usize { return self.count; }
|
||||
|
||||
pub fn room(self: &Self) -> usize { return self.slots.^.n; }
|
||||
|
||||
/// Where `key` sits in the table: the slot holding it, or the first free
|
||||
/// slot it could go in. Probing stops at a free slot, which is why a slot
|
||||
/// is never cleared -- only ever filled.
|
||||
fn find(self: &Self, key: []u8) -> usize {
|
||||
let mask: usize = self.slots.^.n - 1;
|
||||
var at: usize = hash(key) & mask;
|
||||
while true {
|
||||
if not self.slots.^[at].used { return at; }
|
||||
if self.same(at, key) { return at; }
|
||||
at = (at + 1) & mask;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn same(self: &Self, slot: usize, key: []u8) -> bool {
|
||||
if self.slots.^[slot].len != key.n { return false; }
|
||||
let from: usize = self.slots.^[slot].at;
|
||||
var i: usize = 0;
|
||||
while i < key.n {
|
||||
if self.bytes.^[from + i] != key[i] { return false; }
|
||||
i = i + 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn has(self: &Self, key: []u8) -> bool {
|
||||
return self.slots.^[self.find(key)].used;
|
||||
}
|
||||
|
||||
pub fn get(self: &Self, key: []u8, missing: V) -> V {
|
||||
let at: usize = self.find(key);
|
||||
if self.slots.^[at].used { return self.slots.^[at].value; }
|
||||
return missing;
|
||||
}
|
||||
|
||||
pub fn put(self: &mut Self, key: []u8, value: V) -> !void {
|
||||
if self.count * 4 >= self.slots.^.n * 3 { try self.regrow(); }
|
||||
let at: usize = self.find(key);
|
||||
if self.slots.^[at].used {
|
||||
self.slots.^[at].value = value;
|
||||
return;
|
||||
}
|
||||
try self.keep(key, at);
|
||||
self.slots.^[at].value = value;
|
||||
self.slots.^[at].used = true;
|
||||
self.count = self.count + 1;
|
||||
return;
|
||||
}
|
||||
|
||||
/// Make sure `need` bytes fit, moving to a bigger buffer if they do not.
|
||||
///
|
||||
/// Kept apart from `keep` because the borrow that swaps the buffer in is
|
||||
/// a borrow of the whole of `self` -- borrowing is tracked at the root --
|
||||
/// and it has to be over before any field is read again.
|
||||
fn ensure_room(self: &mut Self, need: usize) -> !void {
|
||||
let have: usize = self.bytes.^.n;
|
||||
if need <= have { return; }
|
||||
var room: usize = have;
|
||||
while room < need { room = room * 2; }
|
||||
let bigger: ^[]mut u8 = try mem.alloc_slice(u8, room);
|
||||
var k: usize = 0;
|
||||
let filled: usize = self.used_bytes;
|
||||
while k < filled {
|
||||
bigger.^[k] = self.bytes.^[k];
|
||||
k = k + 1;
|
||||
}
|
||||
let old: ^[]mut u8 = mem.replace(&mut self.bytes, bigger);
|
||||
mem.destroy(old);
|
||||
return;
|
||||
}
|
||||
|
||||
/// Copy `key` into the byte buffer and point the slot at it.
|
||||
fn keep(self: &mut Self, key: []u8, slot: usize) -> !void {
|
||||
let at: usize = self.used_bytes;
|
||||
let need: usize = at + key.n;
|
||||
try self.ensure_room(need);
|
||||
var i: usize = 0;
|
||||
while i < key.n {
|
||||
self.bytes.^[at + i] = key[i];
|
||||
i = i + 1;
|
||||
}
|
||||
self.slots.^[slot].at = at;
|
||||
self.slots.^[slot].len = key.n;
|
||||
self.used_bytes = at + key.n;
|
||||
return;
|
||||
}
|
||||
|
||||
/// Twice the slots, everything placed again. The keys do not move: they
|
||||
/// live in the byte buffer and the slots only point at them.
|
||||
fn regrow(self: &mut Self) -> !void {
|
||||
let bigger: ^[]mut Slot(V) = try mem.alloc_slice(Slot(V),
|
||||
self.slots.^.n * 2);
|
||||
let mask: usize = bigger.^.n - 1;
|
||||
var i: usize = 0;
|
||||
while i < bigger.^.n {
|
||||
bigger.^[i].used = false;
|
||||
i = i + 1;
|
||||
}
|
||||
i = 0;
|
||||
while i < self.slots.^.n {
|
||||
if self.slots.^[i].used {
|
||||
let from: usize = self.slots.^[i].at;
|
||||
let len: usize = self.slots.^[i].len;
|
||||
var at: usize = hash(self.bytes.^[from..from + len]) & mask;
|
||||
while bigger.^[at].used { at = (at + 1) & mask; }
|
||||
bigger.^[at] = self.slots.^[i];
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
let old: ^[]mut Slot(V) = mem.replace(&mut self.slots, bigger);
|
||||
mem.destroy(old);
|
||||
return;
|
||||
}
|
||||
|
||||
pub fn drop(self: &mut Self) -> void {
|
||||
mem.destroy(self.slots);
|
||||
mem.destroy(self.bytes);
|
||||
}
|
||||
}
|
||||
|
||||
/// FNV-1a. Small, fast, and good enough for identifiers; nothing here has to
|
||||
/// resist an adversary choosing the keys.
|
||||
pub fn hash(key: []u8) -> usize {
|
||||
var h: u32 = 2166136261;
|
||||
var i: usize = 0;
|
||||
while i < key.n {
|
||||
h = h ^ (key[i] as u32);
|
||||
h = h * 16777619;
|
||||
i = i + 1;
|
||||
}
|
||||
return h as usize;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user