如何估算吞吐量与线程池大小

目录

验证代码

估算吞吐量

估算线程池大小


验证代码

用Thread.sleep() 模拟计算过程,因为计算的过程不会释放锁。

用object.wait() 模拟等待io的过程,因为等待io会释放锁。

@Test
	public void testThroughPutByMultiThread() throws InterruptedException {
		final Object lock = new Object();
		List<Thread> list = new ArrayList<>();
		StopWatch stopWatch = new StopWatch("testThroughPutByMultiThread");
		stopWatch.start("testConsumeTime");
		for(int i=0;i<5;i++) {
			int finalI = i;
			Thread thread = new Thread(new Runnable() {
				@Override
				public void run() {
					synchronized (lock) {
						// 计算部分
						try {
							System.out.println("线程" + finalI + "进入临界区");
							Thread.sleep(1000L);
							System.out.println("线程" + finalI + "计算完成");
							// 等待io
							lock.wait(2000L);
							System.out.println("线程" + finalI + "等待io完成");
						} catch (InterruptedException e) {
							e.printStackTrace();
						} finally {
							lock.notify();
						}
					}
				}
			});
			list.add(thread);
			thread.start();
		}

		for (Thread thread : list) {
			thread.join();
		}
		stopWatch.stop();
		System.out.println(stopWatch.prettyPrint());

	}

估算吞吐量

现在有一个task,它的执行时间分为2部分,第一部分做数学运算,第二部分等待IO。这两部分就是所谓的计算操作与等待操作。

假设这个task使用单CPU,开10个线程处理10个任务,计算部分花费1秒,等待部分花费9秒,在单核CPU的情况下可以得到下面的执行图(其实就是《计算机组成原理的流水线模型》):

图中的蓝色线条是等待调度,橙色线条是执行计算任务,绿色线条是CPU等待花费的时间。

这个图的吞吐量就显而易见了:

throughput = 10 tasks / (10 * computing time + wait time) 
           = 10 tasks / (10 * 1s + 9s) 
           = 10 / 19s = 0.526 tasks/s

如果我们现在有一个task使用双核CPU,那么会怎样呢?

可以看到因为有了2个CPU核心,计算任务可以重叠,进而花费的时间减半,吞吐量为:

throughput = 10 tasks / (10 * computing time / 2 + wait time) 
           = 10 tasks / (10 * 1s / 2 + 9s) 
           = 10 / 14s = 0.7142 tasks/s

那么总结一下吞吐量计算公式:

  • Throughput:吞吐量。
  • Tn:task数量。
  • C:CPU数量。可以有小数,比如0.5,代表只提供一半的算力。
  • Tc:task计算所花费的时间。
  • Tw:task等待花费的时间。
  • E:最后一个task完成所消耗的时间。

公式中C=1的意思是CPU 100%的全速工作,如果C=0.5那么意思就是CPU有50%的空闲时间,如果C=2则代表启动了两颗CPU全速运行。

可以看到想要提升吞吐量有:

  • 提高C,提高CPU核数
  • 降低Tc,降低计算时间,通过优化代码
  • 降低Tw,降低等待io时间,通过优化网络,优化响应端代码等。

总的来说就是使用更多的CPU核,让task运行时间更短。

也许你觉得还可以通过提升Tn来提高吞吐量,比如下面这个图:

可以看到吞吐量随着任务数量的上升而上升,那么是不是会一直长呢?答案是不会的,当Tn越来越大的时候,Tn * Tc / C 也会越来越大,那么可以忽略掉Tw,公式就变成了 C / Tc,这值就是理论上的吞吐量上界,增加Tn只会无限趋近于这个值。

估算线程池大小

那么问题来了,如何知道要开多少个线程能够让CPU达到目标利用率?

这个要看下面的公式:

  • N:CPU数量。
  • U:CPU利用率,0.1代表10%,1代表100%。
  • C:用到CPU的时间。
  • W:等待时间。

注:本公式里的 N * U = 吞吐量公式中的C。

如果U=1(利用率100%),决定线程数量的是W与C的比,当W越高时则需要越多的线程,当W=0时,只需要与N同样的线程即可。

这个公式也告诉我们盲目开启更多的线程不会带来额外的好处,还会造成反效果(增加的线程调度开销),所以在实践中都会使用具有上界的线程池。

而且在实际做性能调优的时候,会在计算得到的数字左右调整线程池大小,以达到最好效果。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值