神仙打架?Rust大战JDK,卷死
前言
背景
JYM好,我是退居互联网十八线攻城狮贤哥。
今天我们讲讲Java和Rust的性能问题。
\
话说最近Rust和Go如今纷纷正式入场,在互联网圈卷起来了。
疫情压力下,每天都会有很多Java er在纠结,要不要转语言换个赛道啊?
\
如果转语言的话,Go好还是Rust好?
Rust这种媲美C++的性能核武器登场,Java和Go是不是药丸?
\
我无法回答这些问题,但是接下来有时间我会出专题来测评各语言,JYM都来讨论一下,能为你提供一点片面的参考。
至于今天,我们主要是将一下Java和Rust的性能,没耐心的JY可以直接看 三个名为“结果” 的小节。
亿点点说明
-
发这篇文不是为了喷Rust或者Java,抛砖引玉,请大家正确食用。
-
代码尽量采用一样的结构实现,但我水平有限,实在难免存在
不公平,哪位兄弟有更公平的测试代码的,希望可以贴出来与诸君共赏
- 文中有部分代码是用Kotlin写的,Kotlin与Groovy,Scala,Java一样是JDK语言,编译之后是Java字节码文件,同样是以JVM运行。
首轮测试,搞起
测试环境
-
Windows 10
-
锐龙 3700x
-
rust1.60
-
openjdk 11
废话少说,上代码
以下是Kotlin实现。
需要注意JDK的数组是对象,并非纯栈值,严谨的说,数据大部分是保存在堆上的。
另外,我的CPU是8核的,所以代码里我都用的16线程,将CPU跑到100%,避免单线程调度产生的误差。
自己测试的话,建议修改线程数量。
import java.util.stream.Collectors
fun get_time(): Long {
println(System.currentTimeMillis());
return System.currentTimeMillis();
}
fun calc(iter:Int): Array<Int> {
var arr=Array(5001){0}
var arr2=Array(5001){0}
for(item in 1..5000) {
arr[item-1]=item +iter
}
for(item in 1..5000){
var sum=0;
for(sub_item in 1..5000) {
sum+=arr[sub_item]
}
arr2[item-1]=sum
}
return arr2
}
fun main() {
val t=get_time()
val a=ArrayList<Thread>();
for (item in 1..16)
{
val t=Thread{->
run {
var sum=0
var i=0
var o=0
while (true) {
val a = calc(t.toInt())
i += 1;
i = i % 500;
if (i == 1) {
o = o + 1;
}
if (o == 10) {
break
}
sum+=a[0]
}
}
}
t.start()
a.add(t)
}
a.forEach{
it.join()
}
println((get_time()-t)/1000)
return
}
以下是Rust实现
fn get_time() -> u64 {
let start = SystemTime::now();
let since_the_epoch = start
.duration_since(UNIX_EPOCH)
.expect("Time went backwards");
return since_the_epoch.as_secs();
}
fn calc(iter: i32) -> [i32; 2] {
let mut arr = [0; 5000];
let mut arr2 = [0; 5000];
for item in 1..5000usize {
arr[item] = iter;
}
for mut item in 1..5000 {
let mut sum = 0;
for sub_item in 1..5000 {
sum += arr[sub_item] % 5000
}
arr2[item - 1] = sum
}
return [arr2[0],arr2[1]]
}
fn main() {
let t = get_time() as i64;
let ti32=t.clone() as i32;
let mut b: [Option<JoinHandle<()>>; 16] = Default::default();
let mut a = 0;
for i in 0..16 {
let h = thread::spawn(move || {
let mut sum = 0i32;
let mut i = 0;
let mut o = 0;
while true {
let a = calc(ti32);
i += 1;
i = i % 500;
if i == 1 {
o = o + 1;
}
if o == 10 {
break;
}
sum += a[0];
}
println!("log{}",sum)
});
b[i] = Option::Some(h);
}
for i in b {
i.unwrap().join().expect("a")
}
println!("{}",get_time() as i64-t);
return;
}
结果
|Java | Rust |
| ---- | ----- |
|77s | 101s |
卧槽,JDK不讲武德,来骗,来偷袭Rust老同志?
JDK结果比rust还快25% ??
第二/三波测试
我怀疑是不是AMD对rust优化不好
或者AMD对Java优化太好。
或者是我的算法刚好命中Java的优势
所以我把JDK版本,处理器,系统的环境,换一下,又测试了一波
测试环境
-
Windows 11
-
酷睿i3 10100
-
graalvm jdk 1.8
-
rust 1.6
废话少说,上代码
\
\
use std::thread;
\
use std::time::{SystemTime, UNIX_EPOCH};
\
fn timestamp1() -> i64 {
let start = SystemTime::now();
let since_the_epoch = start
.duration_since(UNIX_EPOCH)
.expect("Time went backwards");
let ms = since_the_epoch.as_secs() as i64 * 1000i64 + (since_the_epoch.subsec_nanos() as f64 / 1_000_000.0) as i64;
ms
}
\
fn calc(f: &i32) -> i32
{
const len:usize=500;
let mut sum =0;
let mut arr=[[0i32;len];len];
let mut loop_no= 500;
while true {
loop_no-=1;
if(loop_no==0){
break;
}
for i in 0..len {
for j in 0..len {
if (i > len / 2) {
arr[i / 2][j/2] = (i * 8 % 1000 + 2) as i32+f;
} else {
arr[i/2][j/2] = (j * 8 % 1000 + 2) as i32+f;
}
}
}
\
for i in 0..len {
for j in 0..len {
sum+=arr[i][j];
sum=sum%500000000;
}
}
}
\
return sum;
\
}
\
fn task()
{
let mut sum=0;
let mut i=0;
while true {
i+=1;
let i2= &i;
sum+=calc(i2);
if(sum>500){
sum=0;
}
if i>50 {
break
}
}
println!("{}",sum);
}
fn main() {
let time=timestamp1();
let mut arr=[thread::spawn(||{ task(); }),thread::spawn(||{ task(); }),thread::spawn(||{ task
(); }),thread::spawn(||{ task(); }),thread::spawn(||{ task(); }),thread::spawn(||{ task()
; }),thread::spawn(||{ task(); }),thread::spawn(||{ task(); })];
\
for item in arr {
item.join().unwrap()
}
println!("{}",timestamp1()-time)
}
\
\
以下是Java实现:
\
\
import java.util.List;
import java.util.stream.Collectors;
\
public class BenchMark {
public static int calc(int f) {
final int len = 500;
int sum = 0;
int[][] arr = new int[len][len];
\
var loop_no=500;
\
while (true)
{
loop_no-=1;
if(loop_no==0){
break;
}
for (int i = 0; i < len; i++) {
for (int j = 0; j < len; j++) {
if (i > len / 2) {
arr[i / 2][j / 2] = (i * 8 % 1000 + 2) + f;
} else {
arr[i / 2][j / 2] = (j * 8 % 1000 + 2) + f;
}
}
}
\
for (int i = 0; i < len; i++) {
for (int j = 0; j < len; j++) {
sum += arr[i][j];
sum = sum % 500000000;
}
}
\
}
\
return sum;
}
\
public static void task() {
var sum = 0;
var i = 0;
while (true) {
i += 1;
var i2 = i;
sum += calc(i2);
if (sum > 500) {
sum = 0;
}
if (i > 50) {
break;
}
}
System.out.println(sum);
}
\
public static void main(String... args) {
var time=System.currentTimeMillis();
List.of(0,1,2,3,4,5,6,7).stream().map(
it->new Thread(()->task())
).peek(it->it.start()).collect(Collectors.toList()).forEach(
it->{
try {
it.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
);
System.out.println(System.currentTimeMillis()-time);
}
}
\
结果
\
| Rust | Java |
| ---- | ----- |
|96s | 112s |
\
这一波,rust耗时96秒,Java耗时112秒,Rust比Java快了15%。
\
感觉差不多打平手
\
有朋友说,Runtime语言在内存申请方面比较有优势,这有点矛盾
\
更多的说法是Runtime语言在内存方面存在GC的劣势
\
所以后面我减少了内存申请,代码就被我后来改成第三轮。第二轮就没有另外保存。
\
改了一下算法
\
| Rust | Java |
| ---- | ----- |
|79s | 99s |
\
这一改,rust耗时79秒,Java耗时99秒,Rust有了20%的优势。
\
综合下来其实rust与Java打的有来有回,拉不开差距。
\
大家都不是盖的,连编程语言互相卷,性能拉不开太大差距。
\
第四波测试
\
续上第四波测试:
\
有人说上面都不是真实场景。
\
这次的测试是针对学生数据的处理:
\
对学生信息进行解析和对比
\
测试环境
\
- Windows 10
\
- 锐龙 3700x
\
- rust1.60
\
- openjdk 11
上代码
\
测试数据样例CSV文件如下:
\
都是假数据,就没必要全发了,格式对上就没问题
\
实际数据12929行,有兴趣的朋友不妨试试
\
00003648732,昂高,2019,1,440000000003648704,在校,36007@mock.com,,15000000036,本科,财经学院,国际经济与贸易,20国贸专科1班,2019,0,男
00004651006,冬凡,2019,2,440000000004651008,在校,35522@mock.com,,15000000047,本科,财经学院,会计学,19立信注会创新2班,2019,0,女
00016981406,杰昂,2020,3,440000000016981376,在校,45352@mock.com,,15000000170,本科,财经学院,财务管理,19财务专科1班,2020,0,男
00021199306,震震,2020,4,440000000021199296,在校,41234@mock.com,,15000000212,本科,财经学院,财务管理,19财务本科1班,2020,0,女
00028630182,月元,2020,5,440000000028630208,在校,45572@mock.com,,15000000286,本科,财经学院,会计学,20会计本科8班,2020,0,男
00036510342,昂治,2020,6,440000000036510336,在校,40650@mock.com,,15000000365,专科,财经学院,会计学,19会计本科4班,2020,0,男
00036977932,菱翠,2019,7,440000000036977920,在校,33818@mock.com,,15000000370,本科,财经学院,证券与期货,19证券专科1班,2019,0,女
00038356442,彰昆,2020,8,440000000038356416,在校,41798@mock.com,,15000000384,本科,财经学院,会计学,19会计本科3班,2020,0,男
00045988090,曼又,2019,9,440000000045988096,在校,36595@mock.com,,15000000460,本科,财经学院,金融工程,19金融本科4班,2019,0,男
00055533180,东旭,2020,10,440000000055533184,在校,42107@mock.com,,15000000555,本科,财经学院,会计,19会计专科4班,2020,0,男
\
Kotlin 实现
\
\
import java.io.File
\
class Student(
val id: ULong,
val name: String,
val enrollment_year: UInt,
val user_id: UInt,
val id_card_no: String,
val student_status: UInt,
val email: String,
val tel: String,
val edu_level: String,
val department: String,
val major: String,
val class_in_school: String,
val grade: UInt,
val sex: UInt,
)
\
class StudentCompareResult(
val id: Pair<ULong, ULong>,
val name: Pair<String, String>,
val enrollment_year: Pair<UInt, UInt>,
val user_id: Pair<UInt, UInt>,
val id_card_no: Pair<String, String>,
val student_status: Pair<UInt, UInt>,
val email: Pair<String, String>,
val tel: Pair<String, String>,
val edu_level: Pair<String, String>,
val department: Pair<String, String>,
val major: Pair<String, String>,
val class_in_school: Pair<String, String>,
val grade: Pair<UInt, UInt>,
val sex: Pair<UInt, UInt>
)
\
\
fun parseStudent(row: String): Student {
\
val fields = row.split(",");
\
val student = Student(
id = fields[0].toULong(),
name = fields[1],
enrollment_year = fields[2].toUInt(),
user_id = fields[3].toUInt(),
id_card_no = fields[4],
student_status = if (fields[5] == "在校") 1u else 0u,
email = fields[6],
tel = fields[8],
edu_level = fields[9],
department = fields[10],
major = fields[11],
class_in_school = fields[12],
grade = fields[13].toUInt(),
sex = fields[14].toUInt()
)
return student
}
\
fun getStudentMap(content: String): HashMap<ULong, Student> {
val rows = content.split("\n");
\
val students = rows.map({
parseStudent(it)
}).toMutableList()
\
students.removeAt(0);
\
val idToStudentMap = HashMap<ULong, Student>();
for (s in students) {
idToStudentMap.put(s.id, s);
}
return idToStudentMap;
}
\
\
fun <T> compare(a: T, b: T): Pair<T, T>? {
return (if (a!!.equals(b)) null else Pair(a, b))
}
\
\
fun compareStudent(src: Student, target: Student): StudentCompareResult {
val compareResult = StudentCompareResult(
id = compare(src.id, target.id)!!,
name = compare(src.name, target.name)!!,
enrollment_year = compare(src.enrollment_year, target.enrollment_year)!!,
user_id = compare(src.user_id, target.user_id)!!,
id_card_no = compare(src.id_card_no, target.id_card_no)!!,
student_status = compare(src.student_status, target.student_status)!!,
email = compare(src.email, target.email)!!,
tel = compare(src.tel, target.tel)!!,
edu_level = compare(src.edu_level, target.edu_level)!!,
department = compare(src.department, target.department)!!,
major = compare(src.major, target.major)!!,
class_in_school = compare(src.class_in_school, target.class_in_school)!!,
grade = compare(src.grade, target.grade)!!,
sex = compare(src.sex, target.sex)!!,
)
return compareResult;
}
\
fun readText(path: String): String {
return File(path).readText()
}
\
fun calc(srcText: String, targetText: String) {
\
var i = 0;
\
while (true) {
\
val srcMap = getStudentMap(srcText)
val targetMap = getStudentMap(targetText)
\
val keys = HashSet<ULong>();
\
val resultMap = HashMap<ULong, StudentCompareResult>();
\
for (key in keys) {
val src = srcMap.get(key);
val target = targetMap.get(key);
if (null != src && null != target) {
resultMap.put(key, compareStudent(src, target));
}
}
\
// for x in resultMap {
// prUIntln!("OK{:?},{:?}",x.0,x.1);
// }
\
if (i == 500) {
break;
}
i += 1;
}
\
\
}
\
fun main(args: Array<String>) {
val srcText = readText("C:\\Users\\CDX\\Desktop\\实验室\\导出\\mock_student.csv");
val targetText = readText("C:\\Users\\CDX\\Desktop\\实验室\\导出\\mock_student2.csv");
\
val now = System.currentTimeMillis();
\
println("开始计时:" + now);
(0..15).toList().map {
Thread { calc(srcText, targetText) }
}.map {
it.start()
it
}.forEach {
it.join()
}
println("计时结束:{}" + (System.currentTimeMillis() - now));
}
\
Rust:
(与Kotlin代码大体对齐,但代码写的很糙,有兴趣的兄弟不妨自己实现一个)
\
\
use std::{fmt, thread};
use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::Read
use std::io;
use std::thread::{JoinHandle, Thread};
use std::time::SystemTime;
\
#[derive(Debug ,Clone)]
struct Student{
id: u64,
name: String,
enrollment_year: u32,
user_id: u32,
id_card_no: String,
student_status: u32,
email: String,
tel: String,
edu_level: String,
department: String,
major: String,
class_in_school: String,
grade: u32,
sex: u32,
}
\
impl fmt::Display for Student{
fn fmt(&self,f: &mut fmt::Formatter) -> fmt::Result{
write!(f,"x:{}, y:{}",self.id,self.name)
}
}
\
#[derive(Debug ,Clone)]
struct StudentCompareResult {
id: Option<[u64; 2]>,
name: Option<[String; 2]>,
enrollment_year: Option<[u32; 2]>,
user_id: Option<[u32; 2]>,
id_card_no: Option<[String; 2]>,
student_status: Option<[u32; 2]>,
email: Option<[String; 2]>,
tel: Option<[String; 2]>,
edu_level: Option<[u64; 2]>,
department: Option<[String; 2]>,
major: Option<[String; 2]>,
class_in_school: Option<[String; 2]>,
grade: Option<[u32; 2]>,
sex: Option<[u32; 2]>,
}
\
impl fmt::Display for StudentCompareResult{
fn fmt(&self,f: &mut fmt::Formatter) -> fmt::Result{
write!(f,"x:{:?}, y:{:?}",self.id,self.name)
}
}
\
#[inline]
fn parseStudent(row: & str) -> Student {
let field_split = row.split(",");
\
let fields: Vec<&str> = field_split.collect();
\
let student = Student {
id: fields[0].trim_start_matches("0").parse::<u64>().unwrap(),
name: fields[1].parse().unwrap(),
enrollment_year: fields[2].parse::<u32>().unwrap(),
user_id: fields[3].parse::<u32>().unwrap(),
id_card_no: fields[4].parse().unwrap(),
student_status: if fields[5].eq("在校") { 1 } else { 0 },
email: fields[6].parse().unwrap(),
tel: fields[8].parse().unwrap(),
edu_level: fields[9].parse().unwrap(),
department: fields[10].parse().unwrap(),
major: fields[11].parse().unwrap(),
class_in_school: fields[12].parse().unwrap(),
grade: fields[13].parse::<u32>().unwrap(),
sex: fields[14].parse::<u32>().unwrap(),
};
student
}
\
#[inline]
fn getStudentMap<'a>(content: &str) -> HashMap<u64, Student>
{
let rows = content.split("\n");
\
let mut students = rows.into_iter().map(|e| {
return parseStudent(&e);
}).collect::<Vec<Student>>();
\
students.remove(0);
\
let mut idToStudentMap = HashMap::new();
for s in students {
idToStudentMap.insert(s.id, s);
}
return idToStudentMap;
}
\
#[inline]
fn compareField< T: PartialEq>(src: Box<T>, target: Box<T>) -> Option<[T; 2]>
{
return if src.eq(&target) {
Option::from([*src,*target])}
else { Option::None }
}
\
macro_rules! compare {
($src:expr,$target:expr) => {
compareField(Box::new($src),Box::new($target))
};
}
\
#[inline]
fn compareStudent(src: Student, target: Student) -> Box<StudentCompareResult>
{
\
let compareResult = StudentCompareResult {
id: compare!(src.id, target.id),
name: compare!(src.name,target.name),
enrollment_year: compare!(src.enrollment_year,target.enrollment_year),
user_id: compare!(src.user_id,target.user_id),
id_card_no:compare!(src.id_card_no,target.id_card_no),
student_status: compare!(src.student_status,target.student_status),
email: compare!(src.email,target.email),
tel: compare!(src.tel,target.tel),
edu_level: compare!(src.id,target.id),
department: compare!(src.department,target.department),
major: compare!(src.major,target.major),
class_in_school: compare!(src.class_in_school,target.class_in_school),
grade: compare!(src.grade,target.grade),
sex: compare!(src.sex,target.sex),
};
return Box::new(compareResult );
}
\
#[inline]
fn readText(path:&str) -> String
{
let mut studentCsvFile = File::open(path).unwrap();
let mut content = String::new();
studentCsvFile.read_to_string(&mut content).unwrap();
return content;
}
\
fn calc()
{
\
let srcText=readText("C:\\Users\\CDX\\Desktop\\实验室\\导出\\mock_student.csv");
let targetText=readText("C:\\Users\\CDX\\Desktop\\实验室\\导出\\mock_student2.csv");
\
let now = SystemTime::now();
println!("开始计时:{}",now.elapsed().unwrap().as_millis());
\
let mut i=0;
loop{
\
let srcMap = getStudentMap(srcText.as_str());
let targetMap = getStudentMap(targetText.as_str());
\
let mut keys=HashSet::new();
{
let ref srcMap = srcMap;
let ref targetMap = targetMap;
let srcKeys = &srcMap.into_iter().map(|e| { *e.0 }).collect::<HashSet<u64>>();
let targetKeys = &targetMap.into_iter().map(|e| { *e.0 }).collect::<HashSet<u64>>();
keys.extend(srcKeys);
keys.extend(targetKeys);
}
\
let mut resultMap=HashMap::new();
\
for key in keys {
let src=&srcMap.get(&key);
let target=&targetMap.get(&key);
if src.is_some()&&target.is_some() {
&resultMap.insert(key,(src.unwrap(),target.unwrap()));
}
}
\
// for x in resultMap {
// println!("OK{:?},{:?}",x.0,x.1);
// }
\
if(i==500){
break;
}
i+=1;
}
\
let mut input = String::new();
println!("计时结束:{}",now.elapsed().unwrap().as_secs());
io::stdin().read_line(&mut input);
}
\
fn main() {
let mut b: [Option<JoinHandle<()>>; 16] = Default::default();
\
for i in 0..16 {
let h = thread::spawn(|| {
calc()
});
b[i]=Option::Some(h)
}
for i in b {
i.unwrap().join().expect("a")
}
}
\
结果
这一波就有点遗憾,在稍微复杂的场景下,JDK创建了很多对象,GC运行也频繁,所以....
.
.
.
.
.
.
.
| Rust | Java |
| ---- | ----- |
|70s | 42s |
个鬼啊,看走眼了,42s的才是Java
JDK这次真的不当人了,竟然比Rust快将近 40%。
总结
这样的测试结果真的有点骇人听闻
\
我在知乎发的前面测试结果的时候,反响热烈就很热烈
有一些人都在发友好的互动(往死里黑)
所以我后面这一波不敢发测试结果了,就JYM独家,You 明白我的意思吧。在知乎发了必然被
喷出翔。
\
这些只是我个人的测试结果,跟算法乃至于硬件和操作系统平台都有关系,毕竟
我Rust也就初学水平,但我还是觉得掘金社区氛围比较好,JYM都是人才,说话也好听,应该
不会那么狭隘,所以还是要给你们看看结果。
一点点心得
关于实践和测试
姜承尧老师曾经说过:网上的技术言论,很多都是荒谬的,学技术一定要自己动手实践才能得到
真实的结果。这句话我一直记得,也亲生验证过很多次,所以我写本文其实动机源于此。
\
网上很多人会告诉你:“生存环境下千万不要用反射,Java反射性能慢几十倍”
“XFS数据库性能比EXT格式快多了,数据库不要用EXT格式”
“Java执行效率差,比Go要慢好几倍”
\
这些问题我先不解谜,希望有人在评论区@我解一下
关于傲慢与偏见
\
可能有人认定Java就是垃圾,要逃离它,而Rust是系统级编程语言,他一定是能甩过Java
几条街的,如果测试结果不是这样,那测试一定是假的。
\
每个人是有偏见的,这是人性的弱点,须得小心克服。潜意识认定一件事,我们就往往
很难改变,有人认为我们理工科的,会理智一点,但其实往往理工科的人,傲慢偏见,也很重。
\
最后Rust学习曲线真的很陡,语言本身的所有权是重难点,的确不好掌握,而且Rust目前没有Java
一样成熟的开发工具,调试面板也没法一目了然看到整个对象的细节。CLion对Rust的支持不太稳定,
在其MSVC,MinGW,CygWin三个平台的Debuger中都出现了三种不同的Bug,【Debuger出现了Bug】这听起来很像一个段子。
回到语言
Rust属于C++同一层的系统级编程语言,设计上非常大胆先进,但它本身就不是为Java和Go一类的应用层场景设计的,学习曲线很陡峭。但除此之外,Rust浑身都是优点,它没有GC也没有运行时,编译出来跟C++一样是二进制文件,连LLVM都没有,程序大小以kb计算,且启动极快。此外还能编译成WASM在浏览器运行。严格的生命周期和所有权管理,就很革新,让人减少很多bug,非常有利于开发稳定的多线程程序。
\
而JDK经过这么多年的优化,也早已成仙不当人了,可不是当年那个龟速虚拟机了,Java与Rust的性能都比NodeJs,Php,Python要快很多。此外我在测试中也有用到比较先进的Graalvm,据说其AOT技术,能静态编译部分Java代码,使得性能更好,事实上动态就不一定输给静态,实际测试中OpenJDK与Graalvm的性能差距非常小。 (但是顺便一说,GraalVM可以运行Js等多种语言,以接近OpenJDK性能的方式打开JS,Python的功能,还支持 用Java控制vm中JS的运行,包括权限拦截,就非常好用)
\
文章两万多字,加上代码高亮,知乎的编辑器都被卡到掉帧了,走过路过留下个评论不过分吧,XDM!!