Вычисления карты: вычисления значения заранее

У меня есть вычислительная картамягкими значениями), что я использую для кэширования результатов дорогого вычисления.

Теперь у меня есть ситуация, где я знаю, что конкретный ключ, вероятно, будет искаться в течение следующих нескольких секунд. Тот ключ является также более дорогим для вычислений, чем большинство.

Я хотел бы вычислить значение заранее в потоке минимального приоритета, так, чтобы, когда значение в конечном счете требуют, оно уже кэшировалось, улучшая время отклика.

Что является хорошим способом сделать это таким образом что:

  1. Я управляю потоком (конкретно его приоритет), в котором выполняется вычисление.
  2. Дублирующейся работы избегают, т.е. вычисление только сделано однажды. Если задача вычисления уже работает затем, вызывающий поток ожидает той задачи вместо того, чтобы вычислить значение снова (FutureTask реализации это. С вычислительными картами Гуавы это верно, если Вы только звоните get но не, если Вы смешиваете его с вызовами к put.)
  3. "Вычисляют значение заранее" метод, является асинхронным и идемпотентным. Если вычисление уже происходит, оно должно сразу возвратиться, не ожидая того вычисления для окончания.
  4. Избегайте смены приоритетов, например, если первоочередной поток запрашивает значение, в то время как поток среднего приоритета делает что-то несвязанное, но задача вычисления ставится в очередь на низкоприоритетном потоке, первоочередной поток не должен оголодать. Возможно, это могло быть достигнуто путем временного повышения приоритета вычислительного потока (потоков) и/или выполнения вычисления на вызывающем потоке.

Как это могло быть скоординировано между всеми включенными потоками?


Дополнительная информация
Вычисления в моем приложении являются операциями фильтрации изображения, что означает, что они являются все зависящими от ЦП. Эти операции включают аффинные преобразования (в пределах от 50µs к 1 мс) и свертки (до 10 мс.), Конечно, эффективность переменных приоритетов потока зависит от способности ОС вытеснить большие задачи.

12
задан finnw 16 July 2010 в 19:11
поделиться

4 ответа

Вы можете организовать выполнение фоновых вычислений "только один раз", используя Future с ComputedMap. Future представляет задачу, которая вычисляет значение. Будущее создается ComputedMap и в то же время передается ExecutorService для фонового выполнения. Исполнитель может быть настроен на вашу собственную реализацию ThreadFactory, которая создает потоки с низким приоритетом, например

class LowPriorityThreadFactory implements ThreadFactory
{
   public Thread newThread(Runnable r) {
     Tread t = new Thread(r);
     t.setPriority(MIN_PRIORITY);
     return t;
   }
}

Когда значение необходимо, ваш высокоприоритетный поток извлекает будущее из карты и вызывает метод get() для получения результата, ожидая его вычисления, если это необходимо. Чтобы избежать инверсии приоритетов, вы добавляете в задачу дополнительный код:

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);
   }
}

Он заботится о повышении приоритета задачи до приоритета потока, вызывающего get(), если задача не завершена, и возвращает приоритет к исходному, когда задача завершается, нормально или иначе. (Для краткости код не проверяет, действительно ли приоритет больше, но это легко добавить).

Когда задача с высоким приоритетом вызывает get(), будущая задача может еще не начать выполняться. Может возникнуть соблазн избежать этого, установив большую верхнюю границу на количество потоков, используемых службой-исполнителем, но это может быть плохой идеей, поскольку каждый поток может работать с высоким приоритетом, потребляя столько процессора, сколько сможет, прежде чем ОС отключит его. Пул, вероятно, должен быть такого же размера, как количество аппаратных потоков, например, размер пула должен соответствовать Runtime.availableProcessors(). Если задача не начала выполняться, то вместо того, чтобы ждать, пока исполнитель составит ее расписание (что является формой инверсии приоритетов, поскольку ваш высокоприоритетный поток ждет завершения низкоприоритетных потоков), вы можете отменить ее из текущего исполнителя и повторно отправить на исполнитель, выполняющий только высокоприоритетные потоки.

8
ответ дан 2 December 2019 в 21:22
поделиться

Я подозреваю, что вы идете по неправильному пути, сосредотачиваясь на приоритетах потоков. Обычно данные, которые хранит кэш, дорого вычисляются из-за ввода-вывода (данные о выходе из памяти) по сравнению с привязкой к ЦП (логические вычисления). Если вы предварительно загружаете, чтобы угадать будущие действия пользователя, такие как просмотр непрочитанных электронных писем, то это указывает мне на то, что ваша работа, вероятно, связана с вводом-выводом. Это означает, что до тех пор, пока не происходит голодание потоков (что планировщики запрещёвывают),Игра в игры с приоритетом потоков не обеспечит значительного повышения производительности.

Если стоимостью является вызов ввода-вывода, то фоновый поток блокируется в ожидании поступления данных, и обработка этих данных должна быть довольно дешевой (например, десериализация). Поскольку изменение приоритета потоков не будет значительно ускорять работу, выполнение работы асинхронно в фоновом потоке потока должно быть достаточным. Если штраф за промахи в кэше слишком высок, то использование нескольких уровней кэширования, как правило, помогает еще больше уменьшить воспринимаемую пользователем задержку.

2
ответ дан 2 December 2019 в 21:22
поделиться

Один из распространенных способов согласования такого типа ситуации - иметь карту, значения которой являются объектами FutureTask. Итак, взяв в качестве примера некоторый код, который я написал с моего веб-сервера, основная идея заключается в том, что для данного параметра мы видим, существует ли уже FutureTask (что означает, что расчет с этим параметром уже запланирован), и если да, то ждем. В этом примере мы в противном случае планируем поиск, но это можно было бы сделать в другом месте с помощью отдельного вызова, если бы это было желательно:

  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
ответ дан 2 December 2019 в 21:22
поделиться

В качестве альтернативы приоритетам потоков вы можете выполнить задачу с низким приоритетом, только если не выполняются задачи с высоким приоритетом. Вот простой способ сделать это:

AtomicInteger highPriorityCount = new AtomicInteger();

void highPriorityTask() {
  highPriorityCount.incrementAndGet();
  try {
    highPriorityImpl();
  } finally {
    highPriorityCount.decrementAndGet();  
  }
}

void lowPriorityTask() {
  if (highPriorityCount.get() == 0) {
    lowPriorityImpl();
  }
}

В вашем случае оба метода Impl () будут вызывать get () на карте вычислений, highPriorityImpl () в том же потоке и lowPriorityImpl () в другом потоке.

Вы можете написать более сложную версию, которая откладывает задачи с низким приоритетом до завершения задач с высоким приоритетом и ограничивает количество одновременных задач с низким приоритетом.

1
ответ дан 2 December 2019 в 21:22
поделиться
Другие вопросы по тегам:

Похожие вопросы: