测试多线程对多核cpu的分支预测的影响

简介: 前言:现代的cpu都有流水线,分支预测功能,CPU的分支预测准确性可以达到98%以上,但是如果预测失败,则流水线失效,性能损失很严重。

前言:

现代的cpu都有流水线,分支预测功能,CPU的分支预测准确性可以达到98%以上,但是如果预测失败,则流水线失效,性能损失很严重。

CPU使用的分支预测技术可以参考:

处理器分支预测研究的历史和现状.pdf

同时多线程处理器上的动态分支预测器设计方案研究.pdf


正确地利用这些特性,可以写出高效的程序。

比如在写if,else语句时,应当把大概率事件放到if语句中,把小概率事件放到else语句中。

但是通常这种考虑都是基于单线程的,在多线程下有可能出现意外情况,比如多个线程同时执行同一处的代码。

测试:

下面基于Intel Core i5的一些多线程分支预测的测试。

测试思路(真实测试时发现不止以下三种情况,详细见下面的测试结果):

两个线程执行同一处代码,而if判断总为true。

两个线程执行同一处代码,一个的if判断总为true,另一个的if判断时为true,时为false。

两个线程执行不同的代码(逻辑功能一样,只是位置不同),一个的if判断总为true,另一个的if判断时为true,时为false。

代码如下:

其中test1测试的是同一处代码,当传入偶数参数时,则if判断总为true,当传入奇数参数时,if判断时为true时为false。

test2函数测试的是不同一处的代码,当传入偶数参数时,则if判断总为true,当传入奇数参数时,if判断时为true时为false。

import java.util.concurrent.CountDownLatch;
public class Test {
	public static int loop = 1000000000;
	public static int sum = 0;
	public static CountDownLatch startGate;
	public static CountDownLatch endGate;
	
	public static void test1(int x1, int x2) throws InterruptedException{
		startGate = new CountDownLatch(1);
		endGate = new CountDownLatch(2);
		new Thread(new T1(x1)).start();
		new Thread(new T1(x2)).start();
		Test.startGate.countDown();
		Test.endGate.await();
	}
	public static void test2(int x1, int x2) throws InterruptedException{
		startGate = new CountDownLatch(1);
		endGate = new CountDownLatch(2);
		new Thread(new T1(x1)).start();
		new Thread(new T2(x2)).start();
		Test.startGate.countDown();
		Test.endGate.await();
	}
}

class T1 implements Runnable{
	int xxx = 0;
	public T1(int xxx){
		this.xxx = xxx;
	}
	@Override
	public void run() {
		try {
			int sum = 0;
			int temp = 0;
			Test.startGate.await();
			long start = System.nanoTime();
			for(int i = 0; i < Test.loop; ++i){
				temp += xxx;
				if(temp % 2 == 0){
					sum += 100;
				}else{
					sum += 200;
				}
			}
			Test.sum += sum;
			long end = System.nanoTime();
			System.out.format("%s, T1(%d): %d\n", Thread.currentThread().getName(), xxx, end - start);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			Test.endGate.countDown();
		}
	}
}
class T2 implements Runnable{
	int xxx = 0;
	public T2(int xxx){
		this.xxx = xxx;
	}
	@Override
	public void run() {
		try {
			int sum = 0;
			int temp = 0;
			Test.startGate.await();
			long start = System.nanoTime();
			for(int i = 0; i < Test.loop; ++i){
				temp += xxx;
				if(temp % 2 == 0){
					sum += 100;
				}else{
					sum += 200;
				}
			}
			Test.sum += sum;
			long end = System.nanoTime();
			System.out.format("%s, T2(%d): %d\n", Thread.currentThread().getName(), xxx, end - start);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			Test.endGate.countDown();
		}
	}
}
AI 代码解读

因为测试的情况多种,简洁表述如下:

一个test1函数会有两个结果,如test1(2, 3)得到两个结果2.1s,2.2s,表示两个线程执行同一份代码,一个的if判断总是true(2是偶数),另一个的总是false(3是奇数),其中第一个线程平均执行一次计算的时间是2.1s,第二个线程平均执行一次计算的时间是2.2s。

测试的main函数中有两个for循环,每个10次。在表述测试结果时,用简略表示,如:

一行数据表示执行一次main函数的结果,后面的时间是粗略平均计算得到的

test1(2,3) 2.1s 2.1s test1(2,4) 2.1s 2.1s


main函数:

	public static void main(String[] args) throws InterruptedException {
		for(int i = 0; i < 10; ++i){
			test1(2, 3);
		}
		System.out.println("!!!!!!!!!!!!!!!!!!!!");
		for(int i = 0; i < 10; ++i){
			test1(2, 4);
		}		
	}
AI 代码解读

测试结果如下:

1 test1(2,3) 2.0s 2.0s test1(2,4) 1.8s 2.0s
2 test1(2,4) 1.3s 1.3s test1(2,3) 1.3s 1.8s
3            
4 test2(2,3) 1.3s 1.7s test2(2,4) 1.3s 1.9s
5 test2(2,4) 1.3s 1.3s test2(2,3) 1.3s 1.8s


先来分析第1行数据,test1(2,3)的结果是最坏的,显然这是因为两个线程执行同一份代码,且两者的分支预测结果互相干扰,导致总是不准。

但是为什么后面的test1(2,4)的结果也是比较差?尽管两个线程执行的是同一份代码,但是两个线程中if的判断都总是true,为什么会出现耗时比较多的情况?

简单推测1:可能是之前的test1(2,3)影响了test1(2,4)的分支预测的结果,分支预测器中有历史表,前面执行的分支预测历史会影响后面的选择。

再来分析第2行,显然test1(2,4)是最好的结果,两个线程执行同一份代码,且if总是为true。再看第2行的test(2,3)结果,比第1行的要好,为什么这个和第1行的数据差这么多?

简单推测2:应该是不同的核有自己的分支预测器。

再来看test2函数的测试结果,从第4行来看,test2(2,3)的结果符合预期,但是显然test2(2,4)的结果不理想,为什么两个线程执行不同地方的代码,if判断总是true,结果会不同?

简单推测3:受test(2,3)的结果的影响,结合简单推测1和2,就可以理解为什么test(2,4)的前一个结果是1.3s,后一个是1.9s了。

再来分析第5行数据,这行数据完全符合预期。


总结:

推测:i5的分支预测器是一个混合的分支预测器。每个核都有历史表,每一个核都有自己的分支预测器。

多线程下的分支预测并不是很乐观,如果可以避开多个线程执行同一份代码,且分支预测条件的结果总是变化,则尽量避开。

目录
打赏
0
0
0
0
1128
分享
相关文章
|
10月前
|
Linux 查看进程PID和线程CPU和内存占用情况
Linux 查看进程PID和线程CPU和内存占用情况
955 0
|
5月前
线程CPU异常定位分析
【10月更文挑战第3天】 开发过程中会出现一些CPU异常升高的问题,想要定位到具体的位置就需要一系列的分析,记录一些分析手段。
128 0
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
4月前
|
.如何根据 CPU 核心数设计线程池线程数量
IO 密集型:核心数*2 计算密集型: 核心数+1 为什么加 1?即使当计算密集型的线程偶尔由于缺失故障或者其他原因而暂停时,这个额外的线程也能确保 CPU 的时钟周期不会被浪费。
190 4
单元测试策略问题之行覆盖率和分支覆盖率之间的问题如何解决
单元测试策略问题之行覆盖率和分支覆盖率之间的问题如何解决
270 7
Redis性能优化问题之检查 Redis 实例是否启用了透明大页机制,如何解决
Redis性能优化问题之检查 Redis 实例是否启用了透明大页机制,如何解决
|
8月前
|
Jstack 查看线程状态及定位占用 cpu 较高的 java 线程
Jstack 查看线程状态及定位占用 cpu 较高的 java 线程
803 2
Redis性能优化问题之优化 Redis fork 耗时严重的问题,如何解决
Redis性能优化问题之优化 Redis fork 耗时严重的问题,如何解决
SpringBoot单元测试快速写法问题之计算测试用例的分支覆盖率如何解决
SpringBoot单元测试快速写法问题之计算测试用例的分支覆盖率如何解决
项目环境测试问题之线程同步器会造成执行完任务的worker等待的情况如何解决
项目环境测试问题之线程同步器会造成执行完任务的worker等待的情况如何解决

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等