标签:
在进行中文分词的时候,我们如何利用多核提升分词速度呢?
计算机很早就进入多核心时代了,不充分利用多核CPU是对计算资源的一种极大的浪费。
在对一段文本进行分词的时候,word分词器的处理步骤如下:
1、把要分词的文本根据标点符号分割成句子;
2、以分割后的句子为基本单位进行分词;
3、把各个句子的分词结果按原来的句子顺序组合起来;
word分词器充分考虑到了利用多核提升分词速度这个问题,在第1步完成后,如果分割出了多个句子,那么这多个句子就可以同时(并行)进行分词,这样就能充分利用多核CPU来提升分词速度。
word分词器提出了两种解决方案,我们分别来介绍:
1、多线程
在Java中,多线程可以帮助我们充分利用CPU,我们可以根据自己的机器情况及其应用特点在配置文件word.conf中指定合适的线程池大小:
#配置分词使用的固定线程池大小,根据机器的情况设置合适的值 thread.pool.size=4
代码实现核心片段如下:
private static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(WordConfTools.getInt("thread.pool.size", 4)); @Override public List<Word> seg(String text) { List<String> sentences = Punctuation.seg(text, KEEP_PUNCTUATION); if(sentences.size() == 1){ return segSentence(sentences.get(0)); } //如果是多个句子,可以利用多线程提升分词速度 List<Future<List<Word>>> futures = new ArrayList<>(sentences.size()); for(String sentence : sentences){ futures.add(submit(sentence)); } sentences.clear(); List<Word> result = new ArrayList<>(); for(Future<List<Word>> future : futures){ List<Word> words; try { words = future.get(); if(words != null){ result.addAll(words); } } catch (InterruptedException | ExecutionException ex) { LOGGER.error("获取分词结果失败", ex); } } futures.clear(); return result; } /** * 将切分句子的任务提交给线程池来运行 * @param sentence 句子 * @return 切分结果 */ private Future<List<Word>> submit(final String sentence){ return EXECUTOR_SERVICE.submit(new Callable<List<Word>>(){ @Override public List<Word> call() { return segSentence(sentence); } }); }
2、Parallel Stream
Java8 support functional-style operations on streams of elements,if we use "parallelStream()" instead of "stream()" , the serial process can automatic change to parallel process.
Java8的内置的并行处理功能,通过上面的简短的介绍我们应该有所了解,使用这种方案的好处是,随着JDK的改进,程序的性能会间接受益,而且我们的代码可以更简单,虽然我们不再能够控制并行度,比如上面我们能指定线程数,但是我们可以放心JRE会做出合理的调度与优化,而且人为指定线程数很多时候都不是最合理的。
我们可以在配置文件word.conf中指定是否启用并行分词:
#是否利用多核提升分词速度 parallel.seg=true
上面多线程的代码可以简化为:
private static final boolean PARALLEL_SEG = WordConfTools.getBoolean("parallel.seg", true); @Override public List<Word> seg(String text) { List<String> sentences = Punctuation.seg(text, KEEP_PUNCTUATION); if(sentences.size() == 1){ return segSentence(sentences.get(0)); } if(!PARALLEL_SEG){ //串行顺序处理,不能利用多核优势 return sentences.stream().flatMap(sentence->segSentence(sentence).stream()).collect(Collectors.toList()); } //如果是多个句子,可以利用多核提升分词速度 Map<Integer, String> sentenceMap = new HashMap<>(); int len = sentences.size(); for(int i=0; i<len; i++){ //记住句子的先后顺序,因为后面的parallelStream方法不保证顺序 sentenceMap.put(i, sentences.get(i)); } //用数组收集句子分词结果 List<Word>[] results = new List[sentences.size()]; //使用Java8中内置的并行处理机制 sentenceMap.entrySet().parallelStream().forEach(entry -> { int index = entry.getKey(); String sentence = entry.getValue(); results[index] = segSentence(sentence); }); sentences.clear(); sentences = null; sentenceMap.clear(); sentenceMap = null; List<Word> resultList = new ArrayList<>(); for(List<Word> result : results){ resultList.addAll(result); } return resultList; }
如果对更多的细节感兴趣,请点击这里查看代码的源文件。
标签:
原文地址:http://my.oschina.net/apdplat/blog/414076