/*
* Copyright 1999-2002 Carnegie Mellon University.
* Portions Copyright 2002 Sun Microsystems, Inc.
* Portions Copyright 2002 Mitsubishi Electric Research Laboratories.
* All Rights Reserved. Use is subject to license terms.
*
* See the file "license.terms" for information on usage and
* redistribution of this file, and for a DISCLAIMER OF ALL
* WARRANTIES.
*
*/
package edu.cmu.sphinx.decoder.scorer;
import edu.cmu.sphinx.frontend.Data;
import edu.cmu.sphinx.frontend.BaseDataProcessor;
import edu.cmu.sphinx.frontend.DataProcessingException;
import edu.cmu.sphinx.util.CustomThreadFactory;
import edu.cmu.sphinx.util.props.PropertyException;
import edu.cmu.sphinx.util.props.PropertySheet;
import edu.cmu.sphinx.util.props.S4Boolean;
import edu.cmu.sphinx.util.props.S4Integer;
import java.util.*;
import java.util.concurrent.*;
/**
* An acoustic scorer that breaks the scoring up into a configurable number of separate threads.
* <p>
* All scores are maintained in LogMath log base
*/
public class ThreadedAcousticScorer extends SimpleAcousticScorer {
/**
* The property that controls the thread priority of scoring threads.
* Must be a value between {@link Thread#MIN_PRIORITY} and {@link Thread#MAX_PRIORITY}, inclusive.
* The default is {@link Thread#NORM_PRIORITY}.
*/
@S4Integer(defaultValue = Thread.NORM_PRIORITY)
public final static String PROP_THREAD_PRIORITY = "threadPriority";
/**
* The property that controls the number of threads that are used to score HMM states. If the isCpuRelative
* property is false, then is is the exact number of threads that are used to score HMM states. If the isCpuRelative
* property is true, then this value is combined with the number of available processors on the system. If you want
* to have one thread per CPU available to score states, set the NUM_THREADS property to 0 and the isCpuRelative to
* true. If you want exactly one thread to process scores set NUM_THREADS to 1 and isCpuRelative to false.
* <p>
* If the value is 1 isCpuRelative is false no additional thread will be instantiated, and all computation will be
* done in the calling thread itself. The default value is 0.
*/
@S4Integer(defaultValue = 0)
public final static String PROP_NUM_THREADS = "numThreads";
/**
* The property that controls whether the number of available CPUs on the system is used when determining
* the number of threads to use for scoring. If true, the NUM_THREADS property is combined with the available number
* of CPUS to determine the number of threads. Note that the number of threads is contained to be never lower than
* zero. Also, if the number of threads is 0, the states are scored on the calling thread, no separate threads are
* started. The default value is false.
*/
@S4Boolean(defaultValue = true)
public final static String PROP_IS_CPU_RELATIVE = "isCpuRelative";
/**
* The property that controls the minimum number of scoreables sent to a thread. This is used to prevent
* over threading of the scoring that could happen if the number of threads is high compared to the size of the
* active list. The default is 50
*/
@S4Integer(defaultValue = 10)
public final static String PROP_MIN_SCOREABLES_PER_THREAD = "minScoreablesPerThread";
private final static String className = ThreadedAcousticScorer.class.getSimpleName();
private int numThreads; // number of threads in use
private int threadPriority;
private int minScoreablesPerThread; // min scoreables sent to a thread
private ExecutorService executorService;
/**
* @param frontEnd
* the frontend to retrieve features from for scoring
* @param scoreNormalizer
* optional post-processor for computed scores that will
* normalize scores. If not set, no normalization will applied
* and the token scores will be returned unchanged.
* @param minScoreablesPerThread
* the number of threads that are used to score HMM states. If
* the isCpuRelative property is false, then is is the exact
* number of threads that are used to score HMM states. If the
* isCpuRelative property is true, then this value is combined
* with the number of available processors on the system. If you
* want to have one thread per CPU available to score states, set
* the NUM_THREADS property to 0 and the isCpuRelative to true.
* If you want exactly one thread to process scores set
* NUM_THREADS to 1 and isCpuRelative to false.
* <p>
* If the value is 1 isCpuRelative is false no additional thread
* will be instantiated, and all computation will be done in the
* calling thread itself. The default value is 0.
* @param cpuRelative
* controls whether the number of available CPUs on the system is
* used when determining the number of threads to use for
* scoring. If true, the NUM_THREADS property is combined with
* the available number of CPUS to determine the number of
* threads. Note that the number of threads is constrained to be
* never lower than zero. Also, if the number of threads is 0,
* the states are scored on the calling thread, no separate
* threads are started. The default value is false.
* @param numThreads
* the minimum number of scoreables sent to a thread. This is
* used to prevent over threading of the scoring that could
* happen if the number of threads is high compared to the size
* of the active list. The default is 50
* @param threadPriority
* the thread priority of scoring threads. Must be a value between
* {@link Thread#MIN_PRIORITY} and {@link Thread#MAX_PRIORITY}, inclusive.
* The default is {@link Thread#NORM_PRIORITY}.
*/
public ThreadedAcousticScorer(BaseDataProcessor frontEnd, ScoreNormalizer scoreNormalizer,
int minScoreablesPerThread, boolean cpuRelative, int numThreads, int threadPriority) {
super(frontEnd, scoreNormalizer);
init(minScoreablesPerThread, cpuRelative, numThreads, threadPriority);
}
public ThreadedAcousticScorer() {
}
@Override
public void newProperties(PropertySheet ps) throws PropertyException {
super.newProperties(ps);
init(ps.getInt(PROP_MIN_SCOREABLES_PER_THREAD), ps.getBoolean(PROP_IS_CPU_RELATIVE),
ps.getInt(PROP_NUM_THREADS), ps.getInt(PROP_THREAD_PRIORITY));
}
private void init(int minScoreablesPerThread, boolean cpuRelative, int numThreads, int threadPriority) {
this.minScoreablesPerThread = minScoreablesPerThread;
if (cpuRelative) {
numThreads += Runtime.getRuntime().availableProcessors();
}
this.numThreads = numThreads;
this.threadPriority = threadPriority;
}
@Override
public void allocate() {
super.allocate();
if (executorService == null) {
if (numThreads > 1) {
logger.fine("# of scoring threads: " + numThreads);
executorService = Executors.newFixedThreadPool(numThreads,
new CustomThreadFactory(className, true, threadPriority));
} else {
logger.fine("no scoring threads");
}
}
}
@Override
public void deallocate() {
super.deallocate();
if (executorService != null) {
executorService.shutdown();
executorService = null;
}
}
@Override
protected <T extends Scoreable> T doScoring(List<T> scoreableList, final Data data) {
if (numThreads > 1) {
int totalSize = scoreableList.size();
int jobSize = Math.max((totalSize + numThreads - 1) / numThreads, minScoreablesPerThread);
if (jobSize < totalSize) {
List<Callable<T>> tasks = new ArrayList<Callable<T>>();
for (int from = 0, to = jobSize; from < totalSize; from = to, to += jobSize) {
final List<T> scoringJob = scoreableList.subList(from, Math.min(to, totalSize));
tasks.add(new Callable<T>() {
public T call() throws Exception {
return ThreadedAcousticScorer.super.doScoring(scoringJob, data);
}
});
}
List<T> finalists = new ArrayList<T>(tasks.size());
try {
for (Future<T> result : executorService.invokeAll(tasks))
finalists.add(result.get());
} catch (Exception e) {
throw new DataProcessingException("No scoring jobs ended", e);
}
return Collections.min(finalists, Scoreable.COMPARATOR);
}
}
// if no additional threads are necessary, do the scoring in the calling thread
return super.doScoring(scoreableList, data);
}
}