【问题标题】:Multi threaded object creation slower then in a single thread多线程对象的创建速度比单线程慢
【发布时间】:2013-09-13 12:47:08
【问题描述】:

我有一个可能是一个基本问题。当我在单核上创建 1 亿个哈希表时,我的机器上大约需要 6 秒(运行时间 = 每个核心 6 秒)。如果我在 12 个内核上执行此多线程(我的机器有 6 个允许超线程的内核),则大约需要 10 秒(运行时间 = 每个内核 112 秒)。

这是我使用的代码:

主要

public class Tests 
{
public static void main(String args[])
{
    double start = System.currentTimeMillis();
    int nThreads = 12;
    double[] runTime = new double[nThreads];

    TestsThread[] threads = new TestsThread[nThreads];
    int totalJob = 100000000;
    int jobsize = totalJob/nThreads;
    for(int i = 0; i < threads.length; i++)
    {
        threads[i] = new TestsThread(jobsize,runTime, i);
        threads[i].start();
    }
    waitThreads(threads);
    for(int i = 0; i < runTime.length; i++)
    {
        System.out.println("Runtime thread:" + i + " = " + (runTime[i]/1000000) + "ms");
    }
    double end = System.currentTimeMillis();
    System.out.println("Total runtime = " + (end-start) + " ms");
}

private static void waitThreads(TestsThread[] threads) 
{
    for(int i = 0; i < threads.length; i++)
    {
        while(threads[i].finished == false)//keep waiting untill the thread is done
        {
            //System.out.println("waiting on thread:" + i);
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }   
}
}

线程

import java.util.HashMap;
import java.util.Map;

public class TestsThread extends Thread
{
int jobSize = 0;
double[] runTime;
boolean finished;
int threadNumber;

TestsThread(int job, double[] runTime, int threadNumber)
{
    this.finished = false;
    this.jobSize = job;
    this.runTime = runTime;
    this.threadNumber = threadNumber;
}

public void run()
{
    double start = System.nanoTime();
    for(int l = 0; l < jobSize ; l++)
    {   
         double[] test = new double[65];
    }
    double end = System.nanoTime();
    double difference = end-start;
    runTime[threadNumber] += difference;
    this.finished = true;
}
}

我不明白为什么在多个线程中同时创建对象每个线程需要更长的时间,然后只在 1 个线程中串行执行。如果我删除创建哈希表的行,这个问题就会消失。如果有人能帮我解决这个问题,我将不胜感激。

【问题讨论】:

  • 我想说创建 100,000,000 个哈希表是个问题。
  • 应用程序实际上是在使用多核吗?如果它不跨越内核并且不得不关闭线程,我可以看到它需要更长的时间,因为那样你就会有这样的开销。
  • 当您删除哈希表创建时,您的线程什么也不做,所以没有什么可比较的;0
  • 你对runtime 变量的使用让我有点害怕。
  • @Bobby 它应该会吓到你,因为它涉及数据竞赛。这种代码的所有读数都是毫无价值的。

标签: java multithreading


【解决方案1】:

更新:此问题与bug report 相关联,并已通过Java 1.7u40 修复。这对 Java 1.8 来说从来都不是问题,因为 Java 8 具有完全不同的哈希表算法。


由于您没有使用创建的对象,因此操作将得到优化。所以你只是在测量创建线程的开销。这肯定是您启动的线程越多,开销就越大。

我必须更正我关于一个细节的答案,我还不知道:HashtableHashMap 类有一些特别之处。他们都在构造函数中调用sun.misc.Hashing.randomHashSeed(this)。换句话说,它们的实例在构造过程中逃逸,这对内存可见性有影响。这意味着与 ArrayList 不同,它们的构造无法优化,并且由于该方法内部发生的事情(即同步),多线程构造会变慢。

如前所述,这对于这些类来说是特别的,当然对于这个实现来说也是如此(我的设置:1.7.0_13)。对于普通类,此类代码的构建时间直接为零。

在这里我添加了一个更复杂的基准代码。注意DO_HASH_MAP = trueDO_HASH_MAP = false 之间的区别(当false 它将创建一个ArrayList 而不是没有这种特殊行为)。

import java.util.*;
import java.util.concurrent.*;

public class AllocBench {
  static final int NUM_THREADS = 1;
  static final int NUM_OBJECTS = 100000000 / NUM_THREADS;
  static final boolean DO_HASH_MAP = true;

  public static void main(String[] args) throws InterruptedException, ExecutionException {
    ExecutorService threadPool = Executors.newFixedThreadPool(NUM_THREADS);
    Callable<Long> task=new Callable<Long>() {
      public Long call() {
        return doAllocation(NUM_OBJECTS);
      }
    };

    long startTime=System.nanoTime(), cpuTime=0;
    for(Future<Long> f: threadPool.invokeAll(Collections.nCopies(NUM_THREADS, task))) {
      cpuTime+=f.get();
    }
    long time=System.nanoTime()-startTime;
    System.out.println("Number of threads: "+NUM_THREADS);
    System.out.printf("entire allocation required %.03f s%n", time*1e-9);
    System.out.printf("time x numThreads %.03f s%n", time*1e-9*NUM_THREADS);
    System.out.printf("real accumulated cpu time %.03f s%n", cpuTime*1e-9);

    threadPool.shutdown();
  }

  static long doAllocation(int numObjects) {
    long t0=System.nanoTime();
    for(int i=0; i<numObjects; i++)
      if(DO_HASH_MAP) new HashMap<Object, Object>(); else new ArrayList<Object>();
    return System.nanoTime()-t0;
  }
}

【讨论】:

  • 我几乎可以肯定创建和启动 12 个空线程不会花费 10 秒。
  • 不,它不会被优化掉。您在这里提出空洞的声明。
  • 在单线程需要 6 秒的系统上。我宁愿质疑整个测量过程。我不知道那个使用double的计时器是做什么的。
  • 更不用说对同一个数组条目的非同步访问了。
  • @Holger:这有点愚蠢。 OP 创建一个只有一个条目的双精度数组,因为数组是通过引用传递的,所以 OP 基本上“发明”了一个绝对不是线程安全的传递引用双精度。
【解决方案2】:

如果你在 6 核上做呢?超线程与双核并不完全相同,因此您可能也想尝试实际内核的数量。

此外,操作系统不一定会将您的每个线程调度到它们自己的内核。

【讨论】:

  • 如果我在 6 个内核上运行它,我也会遇到同样的问题。基本上问题与我使用的核心数量有关。
  • 在 2 个线程上,它总共会运行得更快,但每个线程花费的时间更长。
【解决方案3】:

由于您所做的只是测量时间和搅动内存,因此您的瓶颈很可能在您的 L3 缓存或主内存总线。在这种情况下,协调线程之间的工作可能会产生如此多的开销,从而变得更糟而不是更好。

这对于评论来说太长了,但你的内部循环可以只是

double start = System.nanoTime();
for(int l = 0; l < jobSize ; l++){
    Map<String,Integer> test = new HashMap<String,Integer>();
}
// runtime is an AtomicLong for thread safety
runtime.addAndGet(System.nanoTime() - start); // time in nano-seconds.

花时间创建 HashMap 可能会很慢,因此如果您过于频繁地调用计时器,您可能无法衡量您的想法。

顺便说一句,Hashtable 是同步的,您可能会发现使用 HashMap 更快,并且可能更具可扩展性。

【讨论】:

  • 如果哈希表没有被多个线程使用,那么它们是否同步应该不会有什么不同,对吧? (如果这是一个愚蠢的问题但没有信息学背景,我深表歉意)我将代码更改为创建 HashMap 对象(我编辑了我的初始帖子),但问题仍然存在。另外,我现在把时间花在循环之外,以确保测量时间不是问题。谢谢你的建议。
  • @Sipko 如果没有争用,同步的开销会更小,但它仍然存在。如果 JVM 可以证明一个对象从未被其他线程看到,它甚至可以优化掉这种开销。但是,如果有一个替代方案一开始就没有这种开销,为什么不使用它。
  • @Holger 感谢您的解释。但是,使用此建议并不能解决问题。我注意到如果我替换“Map test = new HashMap();”与“double[] test = new double[65]”一致,我遇到了同样的问题,但是当我使用“double[] test = new double[64]”时,它按预期运行。
猜你喜欢
  • 1970-01-01
  • 2011-06-25
  • 2016-04-07
  • 2012-09-05
  • 1970-01-01
  • 2020-08-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多