12
votes

Carte informatique: valeur informatique à l'avance

J'ai un Carte informatique (avec Valeurs douces ) que j'utilise pour mettre en cache les résultats d'un calcul coûteux.

Maintenant, j'ai une situation où je sais qu'une clé particulière sera probablement levée dans les prochaines secondes. Cette clé est également plus chère à calculer que la plupart.

J'aimerais calculer la valeur à l'avance, dans un fil de priorité minimale, de sorte que lorsque la valeur est finalement demandée, elle sera déjà mise en cache, améliorant le temps de réponse.

Qu'est-ce qu'un bon moyen de le faire tel que:

  1. J'ai le contrôle sur le fil (spécifiquement sa priorité) dans laquelle le calcul est effectué.
  2. Les travaux en double sont évités, c'est-à-dire que le calcul n'est effectué qu'une seule fois. Si la tâche de calcul est déjà exécutée, le thread d'appel attend cette tâche au lieu de calculer à nouveau la valeur ( futuretask implémente cela. Avec les cartes informatiques de GUAVA, cela est vrai si vous n'appelez que mais pas si vous le mélangez avec des appels vers mettre .)
  3. La méthode "Compute Valeur à l'avance" est asynchrone et idempotente. Si un calcul est déjà en cours, il devrait revenir immédiatement sans attendre que le calcul finisse.
  4. éviter l'inversion prioritaire, par ex. Si un thread à haute priorité demande la valeur tandis qu'un thread de priorité moyen fait quelque chose de sans rapport, mais la tâche de calcul est mise en file d'attente sur un thread à faible priorité, le fil haut de priorité ne doit pas être affamé. Peut-être que cela pourrait être atteint en renforçant temporairement la priorité du ou des threads de calcul et / ou en exécutant le calcul sur le thread d'appel.

    Comment cela pourrait-il être coordonné entre tous les threads impliqués?


    Info supplémentaire
    Les calculs de mon application sont des opérations de filtrage d'images, ce qui signifie qu'ils sont tous liés à la CPU. Ces opérations comprennent des transformations affines (allant de 50μs à 1 ms) et des convolutions (jusqu'à 10 ms.) Bien sûr, l'efficacité des priorités de filetage variables dépend de la capacité du système d'exploitation à préempter les tâches plus importantes.


2 commentaires

Vous souhaitez précomputer et mettre en cache une clé du cache de précompception? Pouvez-vous, euh ... stockez-le dans le cache de précompception?


@Blueraja, qui répond aux exigences n ° 1 mais pas n ° 2, # 3 ou n ° 4.


4 Réponses :


2
votes

Un moyen courant de coordination Ce type de situation est d'avoir une carte dont les valeurs sont des objets futuretk. Ainsi, le vol comme un exemple du code que j'ai écrit à partir d'un serveur Web de la mine, l'idée essentielle est que pour un paramètre donné, nous voyons s'il y a déjà un FutureTask (ce qui signifie que le calcul avec ce paramètre a déjà été prévu), et Si oui, nous attendons cela. Dans cet exemple, nous planifions sinon la recherche, mais cela pourrait être fait ailleurs avec un appel séparé si cela était souhaitable:

  private final ConcurrentMap<WordLookupJob, Future<CharSequence>> cache = ...

  private Future<CharSequence> getOrScheduleLookup(final WordLookupJob word) {
    Future<CharSequence> f = cache.get(word);
    if (f == null) {
      Callable<CharSequence> ex = new Callable<CharSequence>() {
        public CharSequence call() throws Exception {
          return doCalculation(word);
        }
      };
      Future<CharSequence> ft = executor.submit(ex);
      f = cache.putIfAbsent(word, ft);
      if (f != null) {
        // somebody slipped in with the same word -- cancel the
        // lookup we've just started and return the previous one
        ft.cancel(true);
      } else {
        f = ft;
      }
    }
    return f;
  }


2 commentaires

Voir la réponse de la MDMA (et l'article lié à l'inversion prioritaire) pour voir pourquoi je suis préoccupé par les priorités de thread.


Je remarque que vous soumettez la tâche alors Vérifiez que l'on utilise un autre futur est déjà sur la carte et l'interrompt si oui. Pourquoi ne pas créer le futur , tenter de l'ajouter à la carte, puis soumettez-la à l'exécutant uniquement si la clé n'était pas déjà présente sur la carte? De cette façon, vous ne gaspillez pas les cycles de la CPU si la tâche n'est pas interruptible.



2
votes

Je soupçonne que vous dirigez le mauvais chemin en vous concentrant sur les priorités de fil. Habituellement, les données présentes sur le cache sont coûteuses pour calculer les E / S due (données hors mémoire) contre la CPU liée (calcul de la logique). Si vous préférez deviner l'action future d'un utilisateur, par exemple en regardant des courriels non lus, cela m'indique que votre travail est probablement I / O lié. Cela signifie que tant que la famine de fil de fil ne se produit pas (quels planificateurs interdit), jouer à des jeux avec priorité de thread n'offrira pas une amélioration de la performance.

Si le coût est un appel d'E / S, le thread d'arrière-plan est bloqué attendre que les données arrivent et le traitement de ce que les données soient assez bon marché (par exemple la désériorialisation). Comme la modification de la priorité de thread n'offre pas une grande partie de l'accélération, effectuer le travail de manière asynchrone sur l'antécédents de fond devrait être suffisant. Si la cache de cache manque de pénalité est trop élevée, l'utilisation de plusieurs couches de mise en cache tend à aider à réduire davantage la latence perçue par l'utilisateur.


1 commentaires

Le calcul est CPU-lié (traitement d'image)



8
votes

Vous pouvez organiser une exécution "une fois" "de l'exécution de l'arrière-plan en utilisant un avenir avec la carte calculée. L'avenir représente la tâche qui calcule la valeur. L'avenir est créé par le calculateur et en même temps, transmis à un ExecuTelservice pour l'exécution des antécédents. L'exécuteur peut être configuré avec votre propre Filfactory Mise en œuvre qui crée des filets à faible priorité, par exemple

class HandlePriorityInversionTask extends FutureTask<ResultType>
{
   Integer priority;  // non null if set
   Integer originalPriority;
   Thread thread;
   public ResultType get() {
      if (!isDone()) 
         setPriority(Thread.currentThread().getPriority());
      return super.get();
   }
   public void run() {
      synchronized (this) {
         thread = Thread.currentThread();
         originalPriority = thread.getPriority();
         if (priority!=null) setPriority(priority);
      } 
      super.run();
   }
   protected synchronized void done() {
         if (originalPriority!=null) setPriority(originalPriority);
         thread = null;
   }

   void synchronized setPriority(int priority) {
       this.priority = Integer.valueOf(priority);
       if (thread!=null)
          thread.setPriority(priority);
   }
}


6 commentaires

Mon projet utilise déjà la dernière version de GUAVA afin que je puisse utiliser un filefactorybuilder - plus simple que l'usine de fil personnalisée. Merci pour le lien d'inversion prioritaire. Je vais uppoter cela plus tard quand je récupère mes votes.


Je n'étais pas vu le filfactoirebuilder à Guava, c'est bien! Le reste de la poste devrait toujours être pertinent, en particulier la tâche qui gère l'inversion prioritaire pour les tâches démarrées et la stratégie de reprogrammer les tâches non démarrées sur un exécuteur haute priorité. Cela garantira qu'une fois que votre thread de priorité est le résultat, il est calculé comme une priorité élevée, que le calcul a déjà commencé ou non.


L'autre chose que j'ai pensé à appeler exécuter sur le fil consommant. La documentation n'est pas claire mais dans la mise en œuvre de RunnableFuture Les deuxièmes appels ultérieurs vers Exécuter (chevauchement ou non) sont les non-ops. Y a-t-il une autre raison pour laquelle vous évitez cela?


J'éviterais que l'appel de la course, car ce n'est pas le contrat spécifié. (L'exécution ne renvoie pas un résultat, par exemple. Et si l'exécution précédente est terminée, mais avec une exception, vous n'obtiendrez pas l'exception.) Enfin, comme je l'ai mis en place ci-dessus, vous n'obtiendrez pas l'inversion prioritaire. correction, qui est dans la méthode get ().


L'appel à exécuter serait suivi immédiatement par un appel à obtenir qui retiendra l'exception.


J'ai écrit une mise en oeuvre runnablefuture similaire à la vôtre sauf avec une gamme de priorités de tous les fils d'attente. Je posterai une version simplifiée comme réponse si je peux comprendre un moyen de raccourcir.



1
votes

Alternative aux priorités de thread, vous ne pouvez effectuer une tâche à faible priorité que si aucune tâche à haute priorité n'est en cours. Voici un moyen simple de faire cela: xxx

dans votre cas d'utilisation, les méthodes IPL () appelleraient get () sur la carte informatique, HighPriorityImpl () dans le même thread et LowpriorityImpl () Dans un fil différent.

Vous pouvez écrire une version plus sophistiquée qui donne des tâches à faible priorité jusqu'à ce que les tâches prioritaires à haute priorité complètent et limitent le nombre de tâches prioritaires simultanées.


1 commentaires

Ma tâche à faible priorité prend beaucoup de temps à courir et est toujours toujours en cours d'exécution lorsque la prochaine demande de haute priorité arrive. J'aime cette méthode, mais pour en tirer pleinement parti de cela, je devrais diviser mes tâches en sous-éléments plus petits (et en utilisant les priorités de thread, j'espère obtenir le système d'exploitation pour le faire pour moi.)