Hlavní navigace

GLib: Cache (2)

20. 2. 2001
Doba čtení: 5 minut

Sdílet

Minule jsme načali téma o mechanizmu pro úsporu paměti - "keších" knihovny GLib. Dnes nás čeká ještě pár detailů o tomto nástroji a příklad ze života, na kterém si použití GCache demonstrujeme.

Procházení všemi prvky keše

Funkce typu „foreach“ jsou všem pravidelným čtenářům jistě dobře známy. Stejný nástroj je implementován i do keší.

void g_cache_key_foreach(GCache *cache, GHFunc func,
                         gpointer user_data);

void (*GHFunc) (gpointer key, gpointer value,
                gpointer user_data);

Tato funkce zavolá na každý klíč keše cache uživatelskou funkci func a předá jí kromě klíče a sdílených dat i pointer user_data. Jak jste si určitě všimli, funkce func je typu GHFunc, který už známe z dřívějška, konkrétně z kapitoly o hash-tabulkách.

Pro procházení keší je zde ale i jiná funkce:

void g_cache_value_foreach(GCache *cache, GHFunc func,
                           gpointer user_data);

Její použití bych si ale dovolil nedoporučovat. Pokud jsem správně pochopil zdrojový kód knihovny, funkce g_cache_value_foreach() prochází všemi prvky keše cache a volá na ně uživatelskou funkci func (která je rovněž typu GHFunc). Této funkci ale jako klíč ( key) předává pointer na sdílená data a jako hodnotu ( value) ukazatel na speciální vnitřní datový typ, ke kterému by uživatel knihovny správně neměl mít vůbec přístup! Uživateli je tato datová položka naprosto k ničemu a proto se mi zdá tento přístup poněkud matoucí a nebezpečný. Patrně se tvůrcům GLib tato funkce nechtěně zatoulala do veřejné části rozhraní. Uděláte jenom dobře, budete-li ji ignorovat. (Jestli se pletu, tak mě opravte!!! :-)

A to je z rozhraní mechanizmu GCache vše. Pojďme rychle na nějaký konkrétní příklad…

Příklad:

#include <glib.h>

/* nase struktura definujici jednu texturu */
typedef struct {
  char *name;         /* nazev textury */
  int transparency;   /* pruhlednost */
  int array[200*200]; /* samotna textura */
} Texture;


/* funkce pro duplikaci klice */
static gpointer dup_key(gpointer key) {
  return (gpointer) g_strdup((gchar *) key);
}


/* vytvoreni textury podle klice (nahrani z disku) */
static gpointer load_texture(gpointer key) {
  Texture *t;
  
  printf("Nacitam texturu %s ", key);
  
  /* inicializace */
  t = g_new0(Texture, 1);
  t->name = (char *) dup_key(key);
  
  /* skutecne nacteni je pro jednoduchost vypusteno */
}


/* HLAVNI PROGRAM */
gint main(void) {
  GCache *textures; /* cache */
  Texture *obj[10]; /* pole objektu */
  Texture *mramor;  /* jedna textura */
  int x;            /* pomocna promenna */

  /* vytvoreni nove cache */
  textures = g_cache_new(load_texture, g_free, dup_key,
      g_free, g_str_hash, g_direct_hash, g_str_equal);

  /* inicializace pole objektu */
  obj[0] = g_cache_insert(textures, "Mramor");
  obj[1] = g_cache_insert(textures, "Plast");
  obj[2] = g_cache_insert(textures, "Mramor");
  obj[3] = g_cache_insert(textures, "Drevo");
  obj[4] = g_cache_insert(textures, "Drevo");
  obj[5] = g_cache_insert(textures, "Mramor");
  obj[6] = g_cache_insert(textures, "Kov");
  obj[7] = g_cache_insert(textures, "Plast");
  obj[8] = g_cache_insert(textures, "Drevo");
  obj[9] = g_cache_insert(textures, "Mramor");

  /* kontrolni vypis */
  puts("");
  for (x = 0; x < 10; x++) {
    printf("obj[%d]: %s, %d ", x, 
        obj[x]->name, obj[x]->transparency);
  }

  /* editace jedne z textur */
  mramor = g_cache_insert(textures,
        "Mramor");                  /* nacteni */
  mramor->transparency = 255;       /* editace */
  g_cache_remove(textures, mramor); /* uvolneni */

  /* kontrolni vypis */
  puts("");
  for (x = 0; x < 10; x++) {
    printf("obj[%d]: %s, %d ", x, 
        obj[x]->name, obj[x]->transparency);
  }

  /* dealokace objektu (uvolneni textur) */
  for (x = 0; x < 10; x++) {
    g_cache_remove(textures, obj[x]);
  }

  /* dealokace cache */
  g_cache_destroy(textures);
}

Výše uvedený příklad prosím chápejte jako nástin části kódu jakéhosi grafického programu, který na různé geometrické objekty aplikuje textury. V ukázce se definuje struktura Texture, která slouží k uložení informací o texturách a právě ona bude předmětem kešování. Jejími prvky je jméno textury ( name), průhlednost ( transparency) a pole bajtů uchovávající samotnou texturu ( array).

Klíčem textur bude řetězec – název.

Kromě hlavní funkce jsou v programu ještě dvě rutiny. Obě slouží pro účely mechanizmu kešování:

Funkce dup_key() slouží pro duplikaci klíče a bude se předávat funkci g_cache_new() jako parametr  key_dup_func.

Stejně tak funkce load_texture(). I ona bude volána výhradně z vnitřku mechanizmu kešování. Funkci g_cache_new() se bude předávat jako argument value_new_func a jejím účelem je vytvořit nový záznam o textuře; načíst texturu z disku do datové struktury Texture. Pro jednoduchost je však kód obstarávající načtení ze souboru vypuštěn.

Hlavní program obsahuje proměnnou textures, což je samotná keš, pole obj, které klidně chápejte jako jakési pole geometrických objektů (pro jednoduchost jsou v poli uloženy jen záznamy o texturách objektů), a pomocné proměnné mramorx.

Po inicializaci keše textures se inicializuje i pole objektů obj. Z kontrolního výstupu můžete sledovat, že každý druh textury se opravdu inicializuje (načítá z disku) jen jednou.

Následuje editace atributů textury „Mramor“. Nejprve se do proměnné mramor uloží voláním g_cache_insert() pointer na záznam v keši, poté se položka modifikuje a nakonec se pointer keši zase vrátí ( g_cache_remove()). Je důležité nezapomínat každý „získaný“ pointer zase „vracet“!

Po kontrolním výpisu se už jen uvolní z paměti všechny objekty a nakonec i samotná keš.

Výstupem programu by mělo být:

Nacitam texturu Mramor
Nacitam texturu Plast
Nacitam texturu Drevo
Nacitam texturu Kov

obj[0]: Mramor,  0
obj[1]: Plast,   0
obj[2]: Mramor,  0
obj[3]: Drevo,   0
obj[4]: Drevo,   0
obj[5]: Mramor,  0
obj[6]: Kov,     0
obj[7]: Plast,   0
obj[8]: Drevo,   0
obj[9]: Mramor,  0

obj[0]: Mramor,  255
obj[1]: Plast,   0
obj[2]: Mramor,  255
obj[3]: Drevo,   0
obj[4]: Drevo,   0
obj[5]: Mramor,  255
obj[6]: Kov,     0
obj[7]: Plast,   0
obj[8]: Drevo,   0
obj[9]: Mramor,  255

root_podpora

Co si z příkladu odnést? Možná to, že na funkce g_cache_insert() a g_cache_remove() je dobré pohlížet jako na dvojici funkcí g_malloc() a g_free(): každý ukazatel, který funkcí g_cache_insert() získáme, je velice vhodné (!) po použití „rádoby-dealokovat“ voláním g_cache_remove(). Jen tak si mechanizmus GCache udrží pravdivý údaj o počtu ukazatelů na sdílená data a jen tak dosáhneme správné funkce celého systému kešování a úspory paměti.

Toť pro dnešek z mé strany vše. U dalšího pokračování se těší

Byl pro vás článek přínosný?

Autor článku

Michal Burda vystudoval informatiku a aplikovanou matematiku a nyní pracuje na Ostravské univerzitě jako odborný asistent. Zajímá se o data mining, Javu a Linux.