使用newFixedThreadPool和newSingleThreadExecutor的性能问题

6
我正在尝试对我们的客户端代码进行基准测试。因此,我决定编写一个多线程程序来对我的客户端代码进行基准测试。我试图测量以下方法将需要多少时间(95百分位)- attributes = deClient.getDEAttributes(columnsList); 下面是我编写的用于在上述方法上进行基准测试的多线程代码。我在两种情况下看到了很多变化 -
1)首先,使用20个线程和运行15分钟的多线程代码。我得到95百分位为37ms。我正在使用 -
ExecutorService service = Executors.newFixedThreadPool(20);

2) 但是,如果我使用以下代码运行同样的程序 15分钟:

ExecutorService service = Executors.newSingleThreadExecutor();

而不是

ExecutorService service = Executors.newFixedThreadPool(20);

当使用newSingleThreadExecutor运行代码时,95%分位数仅为7ms,这远低于使用newFixedThreadPool(20)时的数字。

有人能告诉我使用以下两种方法出现高性能问题的原因是什么-

newSingleThreadExecutor vs newFixedThreadPool(20)

无论哪种方式,我都要运行我的程序15分钟

以下是我的代码-

public static void main(String[] args) {

    try {

        // create thread pool with given size
        //ExecutorService service = Executors.newFixedThreadPool(20);
        ExecutorService service = Executors.newSingleThreadExecutor();

        long startTime = System.currentTimeMillis();
        long endTime = startTime + (15 * 60 * 1000);//Running for 15 minutes

        for (int i = 0; i < threads; i++) {
            service.submit(new ServiceTask(endTime, serviceList));
        }

        // wait for termination        
        service.shutdown();
        service.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
    } catch (InterruptedException e) {

    } catch (Exception e) {

    }
}

以下是实现Runnable接口的类 -
class ServiceTask implements Runnable {

    private static final Logger LOG = Logger.getLogger(ServiceTask.class.getName());
    private static Random random = new SecureRandom();

    public static volatile AtomicInteger countSize = new AtomicInteger();

    private final long endTime;
    private final LinkedHashMap<String, ServiceInfo> tableLists;

    public static ConcurrentHashMap<Long, Long> selectHistogram = new ConcurrentHashMap<Long, Long>();


    public ServiceTask(long endTime, LinkedHashMap<String, ServiceInfo> tableList) {
        this.endTime = endTime;
        this.tableLists = tableList;
    }

    @Override
    public void run() {

        try {

            while (System.currentTimeMillis() <= endTime) {

                double randomNumber = random.nextDouble() * 100.0;

                ServiceInfo service = selectRandomService(randomNumber);

                final String id = generateRandomId(random);
                final List<String> columnsList = getColumns(service.getColumns());

                List<DEAttribute<?>> attributes = null;

                DEKey bk = new DEKey(service.getKeys(), id);
                List<DEKey> list = new ArrayList<DEKey>();
                list.add(bk);

                Client deClient = new Client(list);

                final long start = System.nanoTime();

                attributes = deClient.getDEAttributes(columnsList);

                final long end = System.nanoTime() - start;
                final long key = end / 1000000L;
                boolean done = false;
                while(!done) {
                    Long oldValue = selectHistogram.putIfAbsent(key, 1L);
                    if(oldValue != null) {
                        done = selectHistogram.replace(key, oldValue, oldValue + 1);
                    } else {
                        done = true;
                    }
                }
                countSize.getAndAdd(attributes.size());

                handleDEAttribute(attributes);

                if (BEServiceLnP.sleepTime > 0L) {
                    Thread.sleep(BEServiceLnP.sleepTime);
                }
            }
        } catch (Exception e) {

        }
    }
}

更新:-

这是我的处理器规格- 我正在Linux机器上运行程序,定义为2个处理器:

vendor_id       : GenuineIntel
cpu family      : 6
model           : 45
model name      : Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz
stepping        : 7
cpu MHz         : 2599.999
cache size      : 20480 KB
fpu             : yes
fpu_exception   : yes
cpuid level     : 13
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology tsc_reliable nonstop_tsc aperfmperf pni pclmulqdq ssse3 cx16 sse4_1 sse4_2 popcnt aes hypervisor lahf_lm arat pln pts
bogomips        : 5199.99
clflush size    : 64
cache_alignment : 64
address sizes   : 40 bits physical, 48 bits virtual
power management:
2个回答

14
有人可以告诉我为什么使用newSingleThreadExecutornewFixedThreadPool(20)相比表现如此低效吗?如果你并行运行了许多任务(在这种情况下是20个),而处理器数量少于任务数,那么每个单独的任务完成所需时间会更长。计算机执行一个任务比同时执行多个线程更容易。即使将池中的线程数限制为您拥有的CPU数量,每个任务也可能会稍微慢一些。然而,如果比较不同大小的线程池的吞吐量(完成一定数量任务所需的时间),你应该会发现20个线程的吞吐量更高。如果使用20个线程执行1000个任务,则总体上完成速度要快得多。虽然每个任务可能需要更长时间,但它们将并行执行。由于线程开销等原因,它可能不会比单线程快20倍,但可能会快15倍左右。不应该担心单个任务的速度,而应该尝试通过调整线程池中的线程数来最大化任务吞吐量。要使用多少线程取决于IO的数量、每个任务使用的CPU周期、锁、同步块、操作系统上运行的其他应用程序和其他因素。通常,人们建议将线程池中的线程数设置为CPU数量的1-2倍,以最大化吞吐量。如果有更多IO请求或线程阻塞操作,则需要添加更多线程。如果有更多的CPU限制,则减少线程数,使其接近可用的CPU数量。如果您的应用程序与服务器上的其他重要应用程序竞争使用操作系统周期,则可能需要更少的线程。

非常感谢您的建议,Gray。再提供一些信息——我正在对类似于我们这里的生产盒子的lnp盒子进行负载和性能测试。是的,20个线程的吞吐量比一个线程要大得多。所以您的建议是,我应该将线程数从20降低到较低的数字,然后再尝试一下?是这样吗? - arsenal
好的。尝试调整数字,直到你最大化吞吐量。请注意,如果您切换架构,您将不得不进行另一次速度测试@TechGeeky。 - Gray
当然。我还有一个疑惑- 你说要最大化吞吐量?但是如果我降低线程数,那吞吐量会继续减少,对吗? - arsenal
可能不是这样的。例如,如果你使用10个线程,每个线程需要15毫秒,那么你会更快,因为你只使用了一半的线程,但时间却少于一半。 - Gray
那么CPU核心呢? - Xaqron
显示剩余4条评论

-1

简而言之

  1. 如果你的任务是CPU密集型的(即没有读/写或阻塞任务使线程处于空闲状态),那么线程数可以设置得接近您的核心数量。这样可以有效地利用所有资源并避免过多的上下文切换。

  2. 如果它们是内存密集型的任务,例如进行API或DB调用,则最好使用更多的线程,这样在一个线程等待响应时,另一个线程就可以用当前的空闲线程进行切换。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接