Commit fdfa319c authored by inge's avatar inge
Browse files

Initial revision

parent 7253c54c
Local_to_global.doc
Inledning
=========
Den här filen beskriver en datastruktur som är lämplig för att lagra
en mappning från lokala textnummer till globala textnummer i servern.
Den används i varje Conference för att tala om vilka texter som finns
kvar med mötet som mottagare.
Den tidigare implementationen var följande (typ):
int first_text_no; /* Första numret i listan */
int num_texts; /* Antal Text_no i array */
Text_no * texts; /* Pekare till array */
Varje gång en ny text skapas i ett möte så läggs det globala
textnumret till i arrayen ovan, varvid hela arrayen omallokeras med
realloc(). Detta är naturligtvis fruktansvärt inneffektivt.
Ibland så tas texter bort, t ex när en person utplånar en text eller
när en garbning sker i servern. Då kan två saker hända:
- Den första texten som är kvar tas bort. I så fall ökas
first_text_no, som innehåller starten på arrayen.
- Något text inne i listan tas bort. Det globala textnumret ersätts
då med 0.
I det första fallet så omallokeras arrayen med realloc() igen och
krymper lite. I det andra fallet så händer ingenting alls med
storleken. Detta är det troligaste fallet eftersom det första numret
nästan alltid är en markerad text. Den här arrayen har alltså den
otrevliga egenskapen att den växer oavbrutet.
Analys
======
Det sätt som textnummer läggs till har ett antal egenskaper:
- Texter läggs hela tiden på bakifrån, aldrig i mitten eller i
början.
- Numren på de lokala textnumren är konsekutiva, dvs inga hål
finns. Sådana hål kan dock uppstå (och uppstår!) när texter tas
bort.
Lösning
=======
Det första man ser när man analyserar innehållet i en mapp, är att det
finns långa avsnitt av idel nollor, och långa avsnitt där det inte
finns några nollor alls, eller åtminstone väldigt få. Detta antyder
alltså att man bör ha en adaptiv datastruktur som anpassar sig till
det lokala förhållanden. Vi föreslår alltså följande.
Mappen lagras i block (små arrayer). Det finns två sorters block:
1. Glesa block. Glesa block består egentligen av två block. I det
ena blocket ligger nycklar (Local_text_no) och i det andra blocket
ligger data (Text_no). Inom ett block använder man binärsökning i det
ena blocket för att hitta just den Local_text_no man är ute efter.
2. Täta block. Täta block består av ett enda block som innehåller
data (Text_no). Man vet vilket lokalt textnummer det första entryt
svarar mot. Det kan finnas enstaka lokala nummer i ett tätt block som
inte finns -- då innehåller data 0.
Blockstorleken är fixerad till t ex 100 entries. (Det verkar som om
man tjänar nästan exakt samma antal bytes oavsett om man väljer
blockstorlek 50 eller 1000). Ett fullt tätt block innehåller alltid
exakt 100 lokala textnummer. Ett fullt glest block innehåller alltid
100 existerande globala textnummer. (Ett glest block tar dubbelt så
mycket plats som ett tätt block, eftersom ett glest block ju
egentligen består av både ett nyckelblock och ett värdeblock).
För att hålla reda på sina block har man en array av block_info:
typedef struct block_info {
int first_free;
int zeroes;
Local_text_no start;
Local_text_no * key_block;
Text_no * value_block;
} L2g_block_info;
Om key_block == NULL så är det ett tätt block.
first_free
Fältet first_free visar var i blocket som man kan fylla på med
fler värden. Det är 100 för fulla block. För block som inte är
fulla pekar det ut det entry i value_block som nästa värde ska
hamna i. Det gäller t ex det sista blocket, som fylls på allt
eftersom nya inlägg skickas till mötet eller block där texter har
tagits bort.
zeroes
Fältet zeroes används bara för täta block, och räknar antalet
nollor i blocket. Om zeroes blir större än 50% av blockstorleken
gör man om blocket till ett glest block. Fältet zeroes är en
optimering som troligtvis underlättar ihopslagning av block. Det
är möjligt att den inte behövs.
start
Fältet start innehåller numret på det första lokala textnumret i
blocket.
key_block
Fältet key_block är en pekare till blocket med Local_text_no, dvs
nycklarna i blocket. Detta fält är NULL om detta är ett tätt
block.
value_block
Fältet value_block är en pekare till blocket med Text_no, dvs
värdena i blocket.
Förutom detta behövs en struct per möte som håller reda på arrayen med
block:
typedef struct local_to_global {
int num_blocks;
int block_size;
L2g_block_info * blocks;
} Local_to_global;
/*
* File: local_to_global.c
*
* Copyright 1996 Inge Wallin, Per Cederqvist
*/
/* FIXME: Use smalloc() et al instead of malloc() */
#include <malloc.h>
#include "kom-types.h"
#include "local_to_global.h"
/* ================================================================ */
/* ==== Static functions ==== */
/* ================================================================ */
/*
* Add a new block to the Local_to_global L2G.
* Since we always add consecutively, we always start out by making
* a new block dense.
*
* Return a pointer to the newly created block info.
*/
static L2g_block_info *
add_block(Local_to_global *l2g)
{
L2g_block_info * binfo;
/* Realloc the block pointer array. */
l2g->num_blocks++;
l2g->blocks = (L2g_block_info *)
realloc(l2g->blocks,
l2g->num_blocks * sizeof(L2g_block_info));
/* Create a new block info and fill it in. */
binfo = &(l2g->blocks[l2g->num_blocks - 1]);
binfo->first_free = 0;
binfo->zeroes = L2G_BLOCKSIZE;
binfo->start = 0;
binfo->key_block = NULL;
binfo->value_block = (Text_no *) malloc(L2G_BLOCKSIZE * sizeof(Text_no));
return binfo;
}
/*
* Make a sparse block from a dense one.
*/
static void
make_sparse(L2g_block_info *binfo)
{
int next;
int i;
/* Allocate the room for the key block. */
binfo->key_block = (Local_text_no *)
malloc(L2G_BLOCKSIZE * sizeof(Local_text_no));
/* Compact the block. */
next = 0;
for (i = 0; i < binfo->first_free; ++i) {
if (binfo->value_block[i] != 0) {
binfo->key_block[next] = binfo->start + i;
binfo->value_block[next] = binfo->value_block[i];
next++;
}
}
/* Set the rest of the fields. */
binfo->first_free = next;
binfo->zeroes = L2G_BLOCKSIZE - next;
binfo->start = binfo->key_block[0];
/* FIXME: What happens if next == 0? Can this happen? */
}
/*
* Find the block where the local text no LNO is and return a pointer
* to it. Returning a pointer does not mean that the text still exists,
* so a search within the block must also be done later.
*
* Return NULL if LNO is bigger than the highest number in the structure.
*/
static L2g_block_info *
find_block(Local_to_global *l2g, Local_text_no lno)
{
L2g_block_info * binfo;
/* Let binfo point to the last block. */
binfo = &(l2g->blocks[l2g->num_blocks - 1]);
/* If lno is greater than the biggest local number, return NULL. */
if (binfo->key_block == NULL) {
/* A dense block */
if (lno >= binfo->start + binfo->first_free)
return NULL;
} else {
/* A sparse block */
if (lno > binfo->value_block[binfo->first_free - 1])
return NULL;
}
/* Find the block where lno *could* be. */
/* FIXME: Binary search? */
while (binfo > l2g->blocks) {
if (lno >= binfo->start)
return binfo;
binfo--;
}
return l2g->blocks;
}
/* ================================================================ */
/* ==== Outside accessible functions ==== */
/* ================================================================ */
/*
* Remove all memory associated with the structure.
*/
void
l2g_destruct(Local_to_global *l2g)
{
l2g_clear(l2g);
/* Free the only data block in existance. */
free(l2g->blocks->value_block);
/* Free the block pointers */
free(l2g->blocks);
}
/*
* Initialize an empty piece of memory to be a Local to global structure.
*/
void
l2g_init(Local_to_global *l2g)
{
/* Initialize the main structure */
l2g->num_blocks = 0;
l2g->blocks = NULL;
/* Initialize the first and, so far only, block info. */
add_block(l2g);
}
/*
* Make L2G into an empty structure, ready to put the first data into it.
*/
void
l2g_clear(Local_to_global *l2g)
{
L2g_block_info * binfo;
int i;
/* Free the block info structures. */
binfo = l2g->blocks + 1;
for (i = 1; i < l2g->num_blocks; ++i) {
if (binfo->key_block != NULL)
free(binfo->key_block);
free(binfo->value_block);
binfo++;
}
/* Free the block pointers except the first one. */
l2g->blocks = realloc(l2g->blocks, sizeof(L2g_block_info));
l2g->num_blocks = 1;
/* Make the first block into a dense block and reset it. */
binfo = l2g->blocks;
binfo->first_free = 0;
binfo->zeroes = L2G_BLOCKSIZE;
binfo->start = 0;
if (binfo->key_block != NULL) {
free(binfo->key_block);
binfo->key_block = NULL;
}
}
/*
* Copy everything from one l2g structure (FROM) into another (TO).
* TO has to be an initialized structure.
*/
void
l2g_copy(Local_to_global *from, Local_to_global *to)
{
L2g_block_info * binfo;
int i, j;
/* FIXME: More efficient code */
l2g_clear(to);
binfo = from->blocks;
for (i = 0; i < from->num_blocks; ++i) {
if (binfo->key_block == NULL) {
/* Dense block */
for (j = 0; j < binfo->first_free; ++j) {
if (binfo->value_block[j] != 0)
l2g_append(to, binfo->start + j, binfo->value_block[j]);
}
} else {
/* Sparse block */
for (j = 0; j < binfo->first_free; ++j) {
if (binfo->value_block[j] != 0)
l2g_append(to, binfo->key_block[j], binfo->value_block[j]);
}
}
binfo++;
}
}
/*
* Append the pair LNO-TNO. LNO has to be bigger than the last local
* number in the structure (this is not tested FIXME?).
*/
void
l2g_append(Local_to_global *l2g,
Local_text_no lno,
Text_no tno)
{
L2g_block_info * binfo;
int i;
if (tno == 0)
return;
/* Let binfo point to the last block. */
binfo = &(l2g->blocks[l2g->num_blocks - 1]);
/* Correct fix? */
if (binfo->first_free == 0) {
binfo->start = lno;
} else
if (binfo->first_free == L2G_BLOCKSIZE) {
/* If the last block is full, create a new dense block. */
binfo = add_block(l2g);
} else if (binfo->key_block == NULL
&& lno - binfo->start >= L2G_BLOCKSIZE){
/*
* If the last block is dense, and LNO is outside the block,
* see if we should convert it to a sparse block or if we should
* allocate a new one.
*/
if ((L2G_BLOCKSIZE - binfo->first_free)
+ binfo->zeroes > L2G_BLOCKSIZE / 2) {
make_sparse(binfo);
} else {
binfo = add_block(l2g);
}
}
/* Enter the new value in the block. */
if (binfo->first_free == 0) {
binfo->start = lno;
}
if (binfo->key_block == NULL) {
/* A dense block. */
for (i = binfo->first_free; i < lno - binfo->start; ++i)
binfo->value_block[i] = 0;
binfo->value_block[lno - binfo->start] = tno;
binfo->first_free = lno - binfo->start + 1;
} else {
/* A sparse block. */
binfo->key_block [binfo->first_free] = lno;
binfo->value_block[binfo->first_free] = tno;
binfo->first_free++;
}
binfo->zeroes--;
}
/*
* Delete the local text LNO from the structure L2G.
*/
void
l2g_delete(Local_to_global *l2g, Local_text_no lno)
{
L2g_block_info * binfo;
int i;
/* Find block where LNO might be and return if not there. */
binfo = find_block(l2g, lno);
if (binfo == NULL)
return;
/* Go through the block where it might be and delete LNO */
if (binfo->key_block == NULL) {
/* A dense block */
if (binfo->start <= lno && lno < binfo->start + L2G_BLOCKSIZE)
binfo->value_block[lno - binfo->start] = 0;
binfo->zeroes++;
} else {
/* FIXME: Binary search */
for (i = 0; i < binfo->first_free && binfo->key_block[i] <= lno; ++i) {
if (binfo->key_block[i] == lno) {
/* FIXME: Compacting? */
binfo->value_block[i] = 0;
binfo->zeroes++;
}
}
}
}
/*
* Lookup the global text number which corresponds to the local text
* number LNO.
*/
Text_no
l2g_lookup(Local_to_global *l2g, Local_text_no lno)
{
L2g_block_info * binfo;
int i;
binfo = find_block(l2g, lno);
if (binfo == NULL)
return 0;
if (binfo->key_block == NULL) {
/* A dense block */
if (binfo->start <= lno && lno < binfo->start + L2G_BLOCKSIZE)
return binfo->value_block[lno - binfo->start];
return 0;
} else {
/* FIXME: Binary search */
for (i = 0; i < binfo->first_free && binfo->key_block[i] <= lno; ++i) {
if (binfo->key_block[i] == lno)
return binfo->value_block[i];
}
return 0;
}
return 0;
}
/*
*
*/
Local_text_no
l2g_next_key(Local_to_global *l2g, Local_text_no lno)
{
/* FIXME: NYI */
return 0;
}
/*
* Compact the structure as much as possible.
*/
void
l2g_compact(Local_to_global *l2g)
{
/* We don't have to do any compacting. It works anyhow. */
/* However, for memory's sake, we would like to do it. */
/* FIXME: More efficient code. */
Local_to_global dummy;
l2g_init(&dummy);
l2g_copy(l2g, &dummy);
l2g_copy(&dummy, l2g);
l2g_destruct(&dummy);
}
/*
* Dump the internal structure of L2G onto the file FILE.
*/
void
l2g_dump(Local_to_global *l2g, FILE *file)
{
L2g_block_info * binfo;
int i, j;
fprintf(file, "Number of blocks: %d\n", l2g->num_blocks);
binfo = l2g->blocks;
for (i = 0; i < l2g->num_blocks; ++i) {
fprintf(file, "%d: %d %d %d (%s) [", i,
binfo->first_free,
binfo->zeroes,
(int) binfo->start,
((binfo->key_block == NULL) ? "dense" : "sparse"));
if (binfo->key_block == NULL) {
/* A dense block */
for (j = 0; j < binfo->first_free; ++j) {
fprintf(file, "%d ", (int) binfo->value_block[j]);
}
} else {
/* A sparse block */
for (j = 0; j < binfo->first_free; ++j) {
fprintf(file, "%d:%d ",
(int) binfo->key_block[j],
(int) binfo->value_block[j]);
}
}
fprintf(file, "]\n");
binfo++;
}
}
/*
*
*/
void
l2g_read(Local_to_global *l2g, FILE *file)
{
}
/*
*
*/
void
l2g_print(Local_to_global *l2g, FILE *file)
{
}
/*
* File: local_to_global.h
*
* Copyright 1996 Inge Wallin, Per Cederqvist
*/
#ifndef LOCAL2GLOBAL__H
#define LOCAL2GLOBAL__H
#include "kom-types.h"
#define L2G_BLOCKSIZE 10 /* Doesn't seem to matter much. */
typedef struct block_info {
int first_free; /* Insert new texts here. */
int zeroes; /* Used as optimization for compacting. */
Local_text_no start; /* First local text no in this block. */
Local_text_no * key_block;
Text_no * value_block;
} L2g_block_info;
typedef struct local_to_global {
int num_blocks;
L2g_block_info * blocks;
} Local_to_global;
void l2g_destruct(Local_to_global *l2g);
void l2g_init (Local_to_global *l2g);
void l2g_clear (Local_to_global *l2g);
void l2g_copy (Local_to_global *from, Local_to_global *to);
void l2g_append (Local_to_global *l2g, Local_text_no lno,
Text_no tno);
void l2g_delete (Local_to_global *l2g, Local_text_no lno);
Text_no l2g_lookup (Local_to_global *l2g, Local_text_no lno);
Local_text_no l2g_next_key(Local_to_global *l2g, Local_text_no lno);
void l2g_compact (Local_to_global *l2g);
/* Text file representation, e.g. for the database. */
void l2g_dump (Local_to_global *l2g, FILE *file);
void l2g_read (Local_to_global *l2g, FILE *file);
void l2g_print (Local_to_global *l2g, FILE *file);