7
votes

C - Comment créer un cache pour des lectures externes coûteuses?

Je travaille avec un microcontrôleur qui possède une EEPROM externe contenant des tableaux d'informations.

Il y a une grande quantité d'informations, mais il y a de bonnes chances que nous demandions au même cycle d'information du cycle de cycle si nous sommes équitablement «Stable» - c'est-à-dire si nous sommes à une température constante par exemple. P>

se lit de l'EEPROM Prenez environ 1 ms et nous faisons environ 30 par cycle. Notre cycle est actuellement à environ 100 ms, il existe donc des économies importantes. P>

Je cherche donc à mettre en œuvre un cache de RAM. Un coup devrait être significativement plus rapide que 1 mS puisque le noyau de microcontrôleur est en cours d'exécution à 8 MHz. P>

La recherche implique une adresse 16 bits renvoyant 16 bits de données. Le microcontrôleur est 32 bits. P>

Toute entrée de la mise en cache serait grandement appréciée, surtout si je manque totalement la marque et devrait utiliser quelque chose d'autre, comme une liste liée, ou même une bibliothèque préexistante. . P>

Voici ce que je pense que j'essaie d'atteindre: p>

-A cache constitué d'une gamme de structs. La structure contiendrait l'adresse, les données et une sorte de compteur indiquant la fréquence à laquelle cette pièce a été accédée (readcount). P>

-Le tableau serait trié par adresse normalement. J'aurais une fonction de recherche efficace () pour rechercher une adresse et obtenir les données (suggestions?) P>

-Si j'ai un cache mademoiselle, je trierais la matrice par lecture en lecture pour déterminer la valeur mise en cache la moins utilisée et jetez-le. Je remplirais ensuite sa position avec la nouvelle valeur que j'ai levée de l'EEPROM. Je voudrais ensuite réorganiser le tableau par adresse. Tout tri utiliserait un tri efficace (tri de la coque? - Vous ne savez pas comment gérer cela avec des tableaux) P>

-Je décrémentez-vous en quelque sorte toutes les variables ReadCount à ce qu'elle aurait tendance à zéro s'il n'était pas utilisé. Cela devrait conserver des variables constamment utilisées. P>

Voici mes pensées jusqu'à présent (pseudocode, excuses de mon style de codage): p>

#define CACHE_SIZE 50

//one piece of data in the cache
struct cacheItem
    {
    uint16_t address;
    uint16_t data;
    uint8_t readCount;
    };

//array of cached addresses 
struct cacheItem cache[CACHE_SIZE]; 

//function to get data from the cache
uint16_t getDataFromCache(uint16_t address)
    {
    uint8_t cacheResult;
    struct cacheItem * cacheHit; //Pointer to a successful cache hit



    //returns CACHE_HIT if in the cache, else returns CACHE_MISS    
    cacheResult = lookUpCache(address, cacheHit);

    if(cacheResult == CACHE_MISS)
        {
        //Think this is necessary to easily weed out the least accessed address
        sortCacheByReadCount();//shell sort?

        removeLastCacheEntry(); //delete the last item that hasn't been accessed for a while

        data = getDataFromEEPROM(address); //Expensive EEPROM read

        //Add on to the bottom of the cache
        appendToCache(address, data, 1); //1 = setting readCount to 1 for new addition

        //Think this is necessary to make a lookup function faster
        sortCacheByAddress(); //shell sort?     
        }
    else
        {
        data = cacheHit->data; //We had a hit, so pull the data
        cacheHit->readCount++; //Up the importance now
        }
    return data;
    }


//Main function
main(void)
    {
    testData = getDataFromCache(1234);
    }


4 commentaires

Quelle est la taille de l'EEPROM et combien de bélier avez-vous disponible pour la mise en cache?


EEPROM est 128k et j'ai 4k de RAM et n'en utilise pas beaucoup de choses pour le moment.


Fondamentalement, faites la même chose que tout autre cache: utilisez une sorte de fonction de hachage et une sorte de stratégie de taille / d'expulsion de godets basée sur la manière dont il est coûteux de déterminer ce qu'il faut expulser à quel point il est cher à relire.


Un cache entièrement associatif a une meilleure performance que la version associative N-Way.


5 Réponses :


8
votes

Le tri répété me semble cher. Je voudrais implémenter le cache comme une table de hachage sur l'adresse. Pour garder les choses simples, je ne commencerais même pas même de compter des hits, mais plutôt d'expulser de vieilles entrées immédiatement sur une collision de hachage: xxx

si cela ne s'avère pas suffisamment efficace, je commencerais par tripoter la fonction de hachage.


1 commentaires

Merci pour votre contribution ici - cela ressemble à ce qu'il serait vraiment rapide à mettre en œuvre et à essayer.



1
votes

N'ayez pas peur de faire plus de calculs, dans la plupart des cas, les E / S sont plus lentes. C'est la mise en œuvre la plus simple que je puisse penser:

#define CACHE_SIZE 50

something   cached_vals[CACHE_SIZE];
short int   cached_item_num[CACHE_SIZE];    
char        cache_hits[CACHE_SIZE]; // 0 means free.


void inc_hits(char index){
    if (cache_hits[index] > 127){
        for (int i = 0; i < CACHE_SIZE; i++)
            cache_hits[i] <<= 1;
            cache_hits[i]++;    // 0 is reserved as "free" marker
    };
    cache_hits[index]++;
}:

int get_new_space(short int item){
    for (int i = 0; i < CACHE_SIZE; i++)
        if (!cache_hits[i]) {
            inc_hits(i);
            return i;   
        };
    // no free values, dropping the one with lowest count
    int min_val = 0;
    for (int i = 1; i < CACHE_SIZE; i++)
        min_val = min(cache_hits[min_val], cache_hits[i]);
    cache_hits[min_val] = 2; // just to give new values more chanches to "survive"
    cached_item_num[min_val] = item;
    return min_val;
};


something* get_item(short int item){
    for (int i = 0; i < CACHE_SIZE; i++){
        if (cached_item_num[i] == item){    
            inc_hits(i);
            return cached_vals + i;
        };
    };
    int new_item = get_new_space(item);
    read_from_eeprom(item, cached_vals + new_item);
    return chached_vals + new_item; 
};


2 commentaires

Merci pour votre réponse. Il est facile de suivre l'appel de l'appel min () - devrions-nous stocker 'i' dans la variable min_val plutôt que le résultat du comparateur? Aussi, y a-t-il une raison pour laquelle vous n'avez pas utilisé de structure contenant du cached_vals, de cached_item_num et de cache_hits - peut-être à cause des optimisations du compilateur pour la boucle à travers celles-ci? Merci encore.


@Brian: Vous avez raison sur max. Qu'en est-il de la structure: il était plus simple d'écrire de cette façon et la logique est la même.



0
votes

Vous avez dit que quelle entrée dont vous avez besoin de la table est liée à la température et que la température a tendance à rester stable. Tant que la température ne change pas trop rapidement, il est peu probable que vous ayez besoin d'une entrée de la table à plus d'une entrée de l'entrée précédemment nécessaire.

Vous devriez être capable d'accomplir votre objectif en gardant seulement 3 entrées dans la RAM. La première entrée est celle que vous venez d'utiliser. La prochaine entrée est celle correspondant à la température juste en dessous de la dernière mesure de la température, et l'autre est la température juste au-dessus de la dernière mesure de la température. Lorsque la température change, l'une de ces entrées devient probablement le nouveau courant actuel. Vous pouvez ensuite préformer quelle que soit la tâche dont vous avez besoin d'utiliser ces données, puis d'aller de l'avant et de lire l'entrée dont vous avez besoin (plus haut ou inférieur à la température actuelle) après avoir terminé d'autres travaux (avant de lire la mesure de température suivante).

Comme il n'y a que 3 entrées dans la RAM à la fois, vous n'avez pas besoin d'être intelligente sur la structure de données que vous devez stocker pour les accéder efficacement, ou même les garder triés car ce ne sera jamais aussi long.

Si les températures peuvent déplacer plus rapidement que 1 unité par période d'examen, vous pouvez simplement augmenter la taille de votre cache et avoir quelques entrées d'anticipation supplémentaires (dans la direction que la température semble être en direct) que vous ne ferez que les entrées de fuite. Ensuite, vous voudrez peut-être stocker les entrées dans une structure efficace. Je ne m'inquiéterais pas de la façon dont vous avez accédé à une entrée récemment, car les prévisions de la distribution de probabilité de température suivantes basées sur la température actuelle seront généralement très bonnes. Vous devrez vous assurer que vous gérez le cas où vous êtes de côté et que vous avez besoin de lire dans l'entrée pour une température de lecture juste immédiatement, cependant.


2 commentaires

Merci pour votre contribution. Il n'est pas simplement aussi simple que la lecture d'une valeur malheureusement - il existe de multiples valeurs, y compris la température qui apparaît de nombreux chemins de recherche différents. Il aura donc besoin d'être une cache plus grande.


Dans ce cas, je suppose que vous avez probablement divers éléments de données de tailles différentes que vous souhaitez cacher, et qu'ils auront des périodes différentes entre lesquelles ils sont nécessaires. Est-ce exact?



1
votes

Les données de tri et de déménagement semble être une mauvaise idée, et il n'est pas clair que vous gagnez quelque chose d'utile.

Je suggérerais une approche beaucoup plus simple. Allouer 4 * N (pour certains n ) octets de données, en tant que tableau de structures de 4 octets contenant chacune une adresse et des données. Pour rechercher une valeur à l'adresse a , vous examinez la structure à l'index un mod n ; Si son adresse stockée est celle que vous souhaitez, utilisez les données associées, sinon recherchez les données de l'EEPROM et stockez-la ainsi avec l'adresse A . Simple, facile à mettre en œuvre, facile à tester et facile à comprendre et à déboguer plus tard.

Si l'emplacement de votre recherche actuelle a tendance à être proche de l'emplacement des recherches précédentes, cela devrait fonctionner assez bien - à tout moment que vous expulsez des données, il va être d'au moins N emplacements loin dans la table, qui signifie que vous n'êtes probablement pas susceptible de le vouloir bientôt à tout moment - je suppose que c'est au moins une bonne heuristique comme "combien de fois ai-je récemment utilisé cela". (Si votre EEPROM stocke plusieurs tables de données différentes, vous pouvez probablement simplement faire du cache pour chacun le plus simple d'éviter les collisions.)


0 commentaires

0
votes

Il y a mes suggestions:

  1. remplace la plus ancienne ou remplacer la stratégie la moins récente serait mieux, car le réolaçage au moins accessible serait de remplir rapidement le cache, puis il suffit de remplacer à plusieurs reprises le dernier élément. P> li>

  2. Ne traversez pas tous les matrices, mais prenez une position pseudo-aléatoire (ensemencement par adresse) à remplacer. (Un cas particulier de l'emplacement unique est déjà présenté par @Ruslik). P> Li> ol>

    Mon idée serait la suivante: p>

    #define CACHE_SIZE 50
    
    //one piece of data in the cache
    struct cacheItem
        {
        uint16_t address;
        uint16_t data;
        uint8_t whenWritten;
        };
    
    //array of cached addresses 
    struct cacheItem cache[CACHE_SIZE]; 
    // curcular cache write counter
    unit8_t writecount = 0;
    
    // this suggest cache location either contains actual data or to be rewritten;
    struct cacheItem *cacheLocation(uint16_t address) {
        struct cacheLocation *bestc, *c;
        int bestage = -1, age, i;
        srand(address); // i'll use standard PRNG to acquire locations; as it initialized
                        // it will always give same sequence for same location
        for(i = 0; i<4; i++) { // any number of iterations you find best
            c = &(cache[rand()%CACHE_SIZE]);
            if(c->address == address) return c; // FOUND!
            age = (writecount - whenWritten) & 0xFF; // after age 255 comes age 0 :(
            if(age > bestage) {
                bestage = age;
                bestc = c;
            }
        }
        return c;
    }
    
    ....
    struct cacheItem *c = cacheLocation(addr);
    if(c->address != addr) {
        c->address = addr;
        c->data = external_read(addr);
        c->whenWritten = ++writecount;
    }
    


0 commentaires