1 简介
现在市面上可用的CPU的性能测试有很多,比如CPUZ, GeekBench, 鲁大师等。这些工具从不同的角度对CPU的性能进行测试评价,我们可以使用这些工具对CPU的性能有一定的了解。但是,对于一名开发人员来说,CPU的性能对程序的性能影响到底是怎样的,事实上并没有哪款软件以为这个目的服务的。因此,本文就通过一个求质数的Java代码对CPU的性能进行测试。通过这个程序,可以对 CPU执行Java程序的性能影响有初步的认识。
本代码主要包括单核、多核两个部分,计算内容是求大数的质数。程序计算的数据范围是在10亿以上一定范围内求所有的质数的数量。本的算法使用最简单直白的素数判断方法,几乎没有经过特别的优化,因为我们计算的目的并不是为了让CPU尽可能高效地获得结果,相反,我们希望 CPU 尽可能做更多的计算,从而通过处理相同的计算量所使用的时间来判断CPU的计算性能,即通过求素数的总计算数量与CPU使用的时间的比值作为评分,以对CPU的性能表现进行打分,从而通过便于我们通过这些评分对CPU的性能在这个程序当中的表现能够以量化的结果进行对比。
2 测试原理
2.1 质数判断函数
我们编写了Java程序,从10亿开始,对指定范围的自然数进行质数判断,使用的判断函数如下:
boolean isPrimeNumber(long n) {
if (n < 2 || n % 2 == 0)
return false;
long m = (long) Math.sqrt(n) + 1;
for (long i = 3; i < m; i += 2) {
if (n % i == 0)
return false;
return true;
}
判断的原理很简单,包括以下几步:
- 如果是小于2或是偶数,则不是质数,直接返回 false;
- 用区间 [ 3 , ⌊ n ⌋ + 1 ] [3, \lfloor \sqrt{n} \rfloor + 1] [3,⌊n⌋+1]上的所有自然数对待测试的自然数 n 进行求余判断,如果余数为0,表示能整除,则不是质数,返回 false;
- 如果第2步循环结束仍然没有退出,则表示是质数,返回 true。
第2步实际上对范围做了简单的优化,因为如果 n 是不是质量,则必可以表示成 p*q 的形式,那么p 和 q 必有1个小于 ⌊ n ⌋ + 1 \lfloor \sqrt{n} \rfloor + 1 ⌊n⌋+1,所以可以对判断的范围进行缩小而减少计算量。
2.2 多线程处理
对于给定的区间,使用多线程技术可以并行计算。本算法默认的输入自然的起始为10亿,即long startNumber = 1_000_000_000L;
; 处理范围 totalRange = 10_000_000
,并使用系数来指定范围,默认值为1即 1千万个数。另一个控制参数是线程数 threadCount
,对指定的判断数量,会根据线程数量进行平均拆分,每个线程负责其中的一段。
为了减少数据存储所占用的时间,本算法中只统计质数的数量,而不记录具体值。即在每次调用isPrimeNumber(n)
后,如果返回true,计数变量加1。
3 测试结果与分析
3.1 说明
测试使用了以下参数,含义如下:
- 1,1 = 使用1线程求1千万个数
- 1,10 = 使用1线程求1亿个数
- 32,1 = 使用32线程求1千万个数
- 32,10 = 使用1线程求1亿个数
3.1 测试结果
测试使用了多种CPU,结果如下表所示:
CPU | 1,1 | 1, 10 | 32, 1 | 32, 10 |
---|---|---|---|---|
9750H | 140 | 141 | 1,105 | 1,153 |
3700X | 214 | 209 | 1, 609 | 1,589 |
5800U | 664 | 645 | 4,421 | 4,152 |
5950X | 743 | 728 | 10,707 | 10,110 |
M1PRO | 948 | 956 | 7,008 | 7,458 |
3.2 结果分析
测试发现,不同的CPU的性能差别,比专业软件大得多,比较明显的变化就是随着时间的变化性能有明显的提升。比如最新的5800U、5950X和M1Pro性能都比较好,而且早期的3700X和9750H的表现就差了很多。所以,可以理解为,新的CPU在架构上对程序指令的处理有了明显的优化,性能表现有显著提升。
作为对比,笔者还选择了C#在.Net6.0环境下进行测试,发现性能居然比Java还要强5%~10%。另外,在测试时还发表,通过将long都替换成int,发现所有的性能几乎都有20%左右的提升,所以可以得出这样的结论:虽然CPU、Windows和Java都是64位的,但是在使用32位的int代替64位的long类型时,性能会更好,且使用的类型量比较大时,性能有20%左右的提升。所以,在能够使用int的场合,即int可以满足数据数据范围的要求,仍然推荐使用int而非long。
4 运行代码
4.1 运行方法
核心代码,只有1个单文件 FindPrimeNumbers.java
,在本测试中,使用以下命令在控制台运行:
# 编译
javac FindPrimeNumbers.java
# 4种情况的运行代码
java FindPrimeNumbers 1 1
java FindPrimeNumbers 1 10
java FindPrimeNumbers 32 1
java FindPrimeNumbers 32 10
4.2 源代码 (Java)
import java.util.Random;
public class FindPrimeNumbers {
public static Random rand = new Random(0);
/**
* Arguments [thread_count] [coefficient] thread_count: number of thread
* coefficient: be ratio to the amount of task.
*/
public static void main(String[] args) throws Exception {
// show help
if (args.length > 0 && args[0].toLowerCase().equals("help")) {
System.out.println("SUM, COUNT (B), TIME(s), SPEED(B/s)");
return;
}
// get arguments
int threadCount = args.length > 0 ? Integer.parseInt(args[0]) : 20;
int coefficient = args.length > 1 ? Integer.parseInt(args[1]) : 1;
// check numbers between 1b and 2b.
long startNumber = 1_000_000_000L;
long totalRange = 10_000_000 * coefficient;
long interval = totalRange / threadCount;
// define variables
double[] results = new double[threadCount];
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
threads[i] = new Thread("Subthread-" + i) {
@Override
public void run() {
int tid = Integer.parseInt(getName().split("-")[1]);
long start = startNumber + interval * tid;
long end = tid == threadCount - 1 ? startNumber + totalRange : startNumber + interval * tid + interval;
int primeNo = 0;
// System.out.printf("id: %d, start=%d, end=%d.\n", tid, start, end);
for (long i = start; i < end; i++)
if (isPrimeNumber(i))
primeNo++;
results[tid] = primeNo;
}
boolean isPrimeNumber(long n) {
if (n < 2 || n % 2 == 0)
return false;
long m = (long) Math.sqrt(n) + 1;
for (long i = 3; i < m; i += 2)
if (n % i == 0)
return false;
return true;
}
};
}
// start all threads
long t1 = System.currentTimeMillis();
for (int i = 0; i < threads.length; i++)
threads[i].start();
// wait threads to finish
for (int i = 0; i < threads.length; i++)
threads[i].join();
long t2 = System.currentTimeMillis();
// sum all results
long total_sum = 0;
for (int i = 0; i < threads.length; i++) {
total_sum += results[i];
}
// format and output results
double time = (t2 - t1) / 1000.0;
double speed = totalRange / time /1000;
System.out.printf("# of Primes=%d, total=%d, time=%6.2fs, score=%5.0f.\n", total_sum, totalRange, time, speed);
}
}
4.3 源代码 (C#)
public class Program
{
public static void Main(string[] args)
{
// show help
if (args.Length > 0 && args[0].ToLower().Equals("help"))
{
Console.WriteLine("SUM, COUNT (B), TIME(s), SPEED(B/s)");
return;
}
// get arguments
int threadCount = args.Length > 0 ? int.Parse(args[0]) : 1;
int coefficient = args.Length > 1 ? int.Parse(args[1]) : 1;
// check numbers between 1b and 2b.
long startNumber = 1_000_000_000L;
long totalRange = 10_000_000 * coefficient;
long interval = totalRange / threadCount;
// define variables
long[] results = new long[threadCount];
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++)
{
threads[i] = new Thread(
() =>
{
int tid = int.Parse(Thread.CurrentThread.Name.Split("-")[1]);
long start = startNumber + interval * tid;
long end = tid == threadCount - 1 ? startNumber + totalRange : startNumber + interval * tid + interval;
int primeNo = 0;
// System.out.printf("id: %d, start=%d, end=%d.\n", tid, start, end);
for (long i = start; i < end; i++)
if (isPrimeNumber(i))
primeNo++;
results[tid] = primeNo;
// Console.WriteLine();
}
);
threads[i].Name = "Thread-" + i;
}
// start all threads
var t1 = DateTime.Now;
for (int i = 0; i < threadCount; i++)
threads[i].Start();
// wait threads to finish
for (int i = 0; i < threadCount; i++)
threads[i].Join();
var t2 = DateTime.Now;
// sum all results
long total_sum = 0;
for (int i = 0; i < threadCount; i++)
{
total_sum += results[i];
}
// format and output results
double time = (t2 - t1).TotalSeconds;
var speed = (int)(totalRange / time / 1000);
Console.WriteLine($"PrimeCount={total_sum}, Range={totalRange}, Time={time:0.00}s, Score={speed}.\n");
}
public static bool isPrimeNumber(long n)
{
if (n < 2 || n % 2 == 0)
return false;
long m = (long)Math.Sqrt(n);
for (long i = 3; i < m; i += 2)
if (n % i == 0)
return false;
return true;
}
}
5 测试中的一些数据(仅供参考)
以下是测试中用于测试编写的求和代码
代码3.0
import java.util.Random;
public class SpeedTest {
public static Random rand = new Random(0);
/**
* Arguments [thread_count] [coefficient]
* thread_count: number of thread
* coefficient: be ratio to the amount of task.
*/
public static void main(String[] args) throws Exception {
// show help
if(args.length > 0 && args[0].toLowerCase().equals("help"))
{
System.out.println("SUM, COUNT (B), TIME(s), SPEED(B/s)");
return;
}
// get arguments
int threadCount = args.length > 0 ? Integer.parseInt(args[0]) : 1;
int coefficient = args.length > 1 ? Integer.parseInt(args[1]) : 1;
// define variables
double[] results = new double[threadCount];
long[] counts = new long[threadCount];
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
threads[i] = new Thread("Subthread-" + i) {
@Override
public void run() {
int tid = Integer.parseInt(getName().split("-")[1]);
counts[tid] = 1048576L * (1024 + rand.nextInt(2)) * coefficient;
results[tid] = get_sum(0, counts[tid]);
}
double get_sum(long start, long end) {
double result = 0;
for(long i = start; i < end; i ++)
result += i;
return result;
}
};
}
// start all threads
var t1 = System.currentTimeMillis();
for (int i = 0; i < threads.length; i++)
threads[i].start();
// wait threads to finish
for (int i = 0; i < threads.length; i++)
threads[i].join();
var t2 = System.currentTimeMillis();
// sum all results
double total_sum = 0;
double total_count = 0;
for (int i = 0; i < threads.length; i++){
total_sum += results[i];
total_count += counts[i];
}
// format and output results
total_count /= 1E9; // in Billions
double time = (t2 - t1)/1000.0;
var speed = total_count / time;
System.out.printf("%8.3E, %8.3f, %8.3f, %8.3f.\n", total_sum, total_count, time, speed);
}
}
测试代码 2.0
import java.util.ArrayList;
import java.util.Random;
public class SpeedTest {
public static double res = 0;
public static long count = 0;
public static Random rand;
public static double[] results;
public static long[] counts;
public static int[] elasptime;
public static int threadCount = 100;
public static void main(String[] args) throws Exception {
threadCount = 100;
Random rand = new Random(0);
results = new double[threadCount];
counts = new long[threadCount];
elasptime = new int[threadCount];
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
threads[i] = new Thread() {
@Override
public void run() {
var name = Thread.currentThread().getName();
if(!name.contains("-"))
return;
int tid = Integer.parseInt(name.split("-")[1]);
long len = 1024 * 1024 * (1024 + rand.nextInt(2));
double sum = 0;
for (long i = 0; i < len; i++)
sum += i;
counts[tid] = len;
results[tid] = sum;
}
};
}
// System.out.println("---- Start ----");
var t1 = System.currentTimeMillis();
for (int i = 0; i < threads.length; i++)
threads[i].start();
// System.out.println("---- Threads Join ---");
for (int i = 0; i < threads.length; i++)
threads[i].join();
var t2 = System.currentTimeMillis();
double total_sum = 0;
double total_count = 0;
for (int i = 0; i < threads.length; i++){
total_sum += results[i];
total_count += counts[i];
}
total_count /= 1E9; // in Billions
double time = (t2 - t1)/1000.0;
var speed = total_count / time;
System.out.printf("%5.2e, %5.2f, %5.2f, %5.2f.\n", total_sum, total_count, time, speed);
// System.out.println("--- FINISH ---");
}
}
测试代码 1.0
import java.util.ArrayList;
import java.util.Random;
public class SpeedTest {
public static double res = 0;
public static long count = 0;
public static Random rand = new Random(0);
public static void main(String[] args) throws Exception {
// heavy_task();
double[] results = new double[100];
Thread[] threads = new Thread[30];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread() {
@Override
public void run() {
HwTimer timer = new HwTimer();
timer.start();
timer.result = 0;
timer.count = 1024 * 1024 * (1024 + rand.nextInt(2));
for (long i = 0; i < timer.count; i++)
timer.result += i;
timer.stop();
timer.show();
RandomDemo.count += timer.count;
// var res1 = heavy_task();
var name = Thread.currentThread().getName();
if(name.contains("-")) {
results[Integer.parseInt(name.split("-")[1])] = timer.result;
// System.out.println("timer.result: " + timer.result);
}
}
};
}
System.out.println("---- Start ----");
HwTimer timer = new HwTimer();
timer.start();
for (int i = 0; i < threads.length; i++)
threads[i].start();
// System.out.println("---- Threads Join ---");
for (int i = 0; i < threads.length; i++)
threads[i].join();
timer.stop();
timer.count = RandomDemo.count;
timer.result = 0;
for (int i = 0; i < threads.length; i++)
timer.result += results[i];
timer.show();
}
public static double heavy_task() {
HwTimer timer = new HwTimer();
timer.start();
timer.result = 0;
timer.count = 1024 * 1024 * (1021 + rand.nextInt(2));
for (long i = 0; i < timer.count; i++)
timer.result += i;
timer.stop();
timer.show();
return timer.result;
}
public static void randDemo() {
Random rand = new Random(11);
ArrayList<Integer> origIDs = new ArrayList<Integer>();
ArrayList<Integer> randIDs = new ArrayList<Integer>();
int count = 10;
for (int i = 0; i < count; i++)
origIDs.add(i);
while (origIDs.size() > 0) {
int pos = rand.nextInt(origIDs.size());
randIDs.add(origIDs.get(pos));
origIDs.remove(pos);
}
for (Integer i : randIDs)
System.out.print(i + ", ");
// output: 6, 8, 1, 3, 0, 5, 9, 4, 7, 2,
}
public static void speedDemo() {
var t1 = System.currentTimeMillis();
long result = 0;
long len = 1024 * 1024 * 1024;
len *= 3;
for (long i = 0; i < len; i++)
result += i;
var t2 = System.currentTimeMillis();
System.out.println("\nresult: " + result + ", time: " + (t2 - t1) + "ms");
}
}
public class HwTimer {
public static void main(String[] args) {
// TODO Auto-generated method stub
}
public long startTime = 0;
public long endTime = 0;
public String name = "no name";
public double result = 1;
public long count = 1;
public void start() {
startTime = System.currentTimeMillis();
}
public void stop() {
endTime = System.currentTimeMillis();
}
public void show() {
var name = Thread.currentThread().getName();
var time = (endTime - startTime) / 1000.0;
var speed = count / time / 1E9;
var count1 = count * 1.0 / 1E9;
System.out.printf("%s: res=%5.2e, count=%5.2f B, time=%5.2fs, speed = %5.2f B/s\n", name, result, count1, time, speed);
}
}
不同机器的表现
3700X
c:\java_code>javac SpeedTest.java && java SpeedTest
5.776E+17, 1.075, 2.747, 0.391.
c:\java_code>javac SpeedTest.java && java SpeedTest
5.776E+17, 1.075, 2.742, 0.392.
c:\java_code>java SpeedTest 16
9.235E+18, 17.190, 2.930, 5.867.
c:\java_code>java SpeedTest 100
5.771E+19, 107.431, 9.990, 10.754.
c:\java_code>java SpeedTest 1 10
5.776E+19, 10.748, 27.741, 0.387.
c:\java_code>java SpeedTest 10
5.772E+18, 10.745, 2.846, 3.775.
c:\java_code>java SpeedTest 20
1.154E+19, 21.487, 3.711, 5.790.
c:\java_code>java SpeedTest 30
1.731E+19, 32.227, 4.559, 7.069.
c:\java_code>java SpeedTest 40
2.308E+19, 42.970, 5.500, 7.813.
c:\java_code>java SpeedTest 5
2.887E+18, 5.373, 2.828, 1.900.
c:\java_code>java SpeedTest 100
5.771E+19, 107.431, 8.992, 11.947.
c:\java_code>java SpeedTest 200
1.154E+20, 214.858, 16.149, 13.305.
c:\java_code>java SpeedTest 200
1.154E+20, 214.858, 15.068, 14.259.
c:\java_code>java SpeedTest 200 10
1.154E+22, 2148.585, 156.073, 13.767.
MacBook Pro 2021
haowei@Haos-MacBook-Pro-2021-M1-Pro java_code % java SpeedTest
5.776E+17, 1.075, 4.482, 0.240.
haowei@Haos-MacBook-Pro-2021-M1-Pro java_code % java SpeedTest
5.776E+17, 1.075, 4.509, 0.238.
haowei@Haos-MacBook-Pro-2021-M1-Pro java_code % java SpeedTest 2
1.155E+18, 2.150, 4.583, 0.469.
haowei@Haos-MacBook-Pro-2021-M1-Pro java_code % java SpeedTest 14
8.079E+18, 15.041, 7.040, 2.136.
haowei@Haos-MacBook-Pro-2021-M1-Pro java_code % java SpeedTest 200
1.154E+20, 214.858, 83.269, 2.580.
haowei@Haos-MacBook-Pro-2021-M1-Pro java_code % java SpeedTest 1 10
5.776E+19, 10.748, 44.697, 0.240.
MacBook Pro 2018
多核跑分:
5.77e+21, 1074.31, 166.83, 6.44
单核跑分
5.78e+19, 10.75, 25.21, 0.47.
5950X 跑分
执行结果:约为 15.50 左右
除了个别几次由于某些原因导致的降速度为10左右,大部分结果在15-16,故取15.50为性能结果。
for /L %i in (1,1,10) do @echo off && java SpeedTest
# SUM, COUNT (B), TIME(s), SPEED(B/s)
5.77e+19, 107.43, 10.68, 10.06.
5.77e+19, 107.43, 10.33, 10.40.
5.77e+19, 107.43, 6.87, 15.64.
5.77e+19, 107.43, 11.94, 9.00.
5.77e+19, 107.43, 6.97, 15.41.
5.77e+19, 107.43, 6.46, 16.62.
5.77e+19, 107.43, 6.47, 16.61.
5.77e+19, 107.43, 7.05, 15.23.
5.77e+19, 107.43, 6.92, 15.52.
5.77e+19, 107.43, 6.77, 15.87.
CPU 占用情况