代码之家  ›  专栏  ›  技术社区  ›  finnw

计算地图:提前计算价值

  •  12
  • finnw  · 技术社区  · 14 年前

    computing map soft values )我用来缓存昂贵计算的结果。

    现在我遇到了这样一种情况,我知道在接下来的几秒钟内可能会查找到某个特定的密钥。该密钥的计算成本也比大多数密钥高。

    这样做的好方法是什么:

    1. 我可以控制执行计算的线程(特别是它的优先级)。
    2. FutureTask 实现这个。使用Guava的计算地图,如果您只调用 get 但如果你把它和电话联系起来 put .)
    3. “预先计算值”方法是异步的、幂等的。如果计算已经在进行中,它应该立即返回,而不必等待计算完成。

    如何在所有相关线程之间进行协调?



    我的应用程序中的计算是图像过滤操作,这意味着它们都是CPU限制的。这些操作包括仿射变换(从50秒到1毫秒)和卷积(最多10毫秒)。当然,不同线程优先级的有效性取决于操作系统抢占较大任务的能力。

    4 回复  |  直到 14 年前
        1
  •  8
  •   mdma    14 年前

    ExecutorService ThreadFactory 创建低优先级线程的实现,例如。

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

    当需要该值时,高优先级线程从映射中获取未来,并调用get()方法来检索结果,等待必要时计算结果。避免 priority inversion 向任务中添加一些附加代码:

    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() 如果任务尚未完成,则在任务完成时(正常或其他情况下)将优先级返回到原始优先级(为了简洁起见,代码不会检查优先级是否确实更高,但这很容易添加。)

    Runtime.availableProcessors() . 如果任务还没有开始执行,而不是等待执行器调度它(这是优先级反转的一种形式,因为高优先级线程正在等待低优先级线程完成),那么您可以选择从当前执行器取消它,并在仅运行高优先级线程的执行器上重新提交。

        2
  •  2
  •   Neil Coffey    14 年前

    协调这种情况的一种常见方法是创建一个值为FutureTask对象的映射。因此,以我从web服务器上编写的一些代码为例,其基本思想是,对于给定的参数,我们看是否已经有了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;
      }
    

    在线程优先级方面:我想知道这是否会达到您认为的效果?我不太理解你关于将查找的优先级提高到等待线程之上的观点:如果线程正在等待,那么它正在等待,不管其他线程的相对优先级如何(你可能想看看我写的一些文章 thread priorities thread scheduling ,但长话短说,我不确定更改优先级是否一定会为您带来您所期望的结果。)

        3
  •  2
  •   Ben Manes    14 年前

    我怀疑你把重点放在线程优先级上是走错了路。通常,由于I/O(内存不足数据)与CPU受限(逻辑计算),高速缓存保存的数据计算起来很昂贵。如果您正在预取以猜测用户未来的操作,例如查看未读的电子邮件,那么它向我表明您的工作可能是I/O绑定的。这意味着,只要不发生线程饥饿(调度程序不允许),使用线程优先级玩游戏就不会带来太多性能改进。

        4
  •  1
  •   Jared Levy    14 年前

    作为线程优先级的替代方法,只有在没有高优先级任务进行时,才能执行低优先级任务。下面是一个简单的方法:

    AtomicInteger highPriorityCount = new AtomicInteger();
    
    void highPriorityTask() {
      highPriorityCount.incrementAndGet();
      try {
        highPriorityImpl();
      } finally {
        highPriorityCount.decrementAndGet();  
      }
    }
    
    void lowPriorityTask() {
      if (highPriorityCount.get() == 0) {
        lowPriorityImpl();
      }
    }
    

    在您的用例中,两个Impl()方法都会在计算映射上调用get(),在同一个线程中调用highPriorityImpl(),在不同的线程中调用lowPriorityImpl()。

    您可以编写更复杂的版本,将低优先级任务推迟到高优先级任务完成,并限制并发低优先级任务的数量。