为何 Rust 的 --release 构建速度比 Go 慢?
有志者,事竟成!如果你在学习Golang,那么本文《为何 Rust 的 --release 构建速度比 Go 慢?》,就很适合你!文章讲解的知识点主要包括,若是你对本文感兴趣,或者是想搞懂其中某个知识点,就请你继续往下看吧~
我正在尝试了解 rust 的并发性和并行计算,并编写了一个小脚本,该脚本迭代向量的向量,就像它是图像的像素一样。因为一开始我试图看看 iter 与 par_iter 相比要快多少,所以我加入了一个基本计时器 - 这可能不是非常准确。然而,我得到了疯狂的高数字。因此,我想我应该在 go 上编写一段类似的代码,以实现轻松并发,并且性能快约 585%!
rust 已使用 --release 进行测试
我也尝试过使用本机线程池,但结果是相同的。看看我使用了多少线程,我也搞了一下,但没有成功。
我做错了什么? (不要介意创建随机值填充向量向量的绝对不高效的方法)
rust 代码(~140ms)
use rand::rng;
use std::time::instant;
use rayon::prelude::*;
fn normalise(value: u16, min: u16, max: u16) -> f32 {
(value - min) as f32 / (max - min) as f32
}
fn main() {
let pixel_size = 9_000_000;
let fake_image: vec<vec<u16>> = (0..pixel_size).map(|_| {
(0..4).map(|_| {
rand::thread_rng().gen_range(0..=u16::max)
}).collect()
}).collect();
// time starts now.
let now = instant::now();
let chunk_size = 300_000;
let _normalised_image: vec<vec<vec<f32>>> = fake_image.par_chunks(chunk_size).map(|chunk| {
let normalised_chunk: vec<vec<f32>> = chunk.iter().map(|i| {
let r = normalise(i[0], 0, u16::max);
let g = normalise(i[1], 0, u16::max);
let b = normalise(i[2], 0, u16::max);
let a = normalise(i[3], 0, u16::max);
vec![r, g, b, a]
}).collect();
normalised_chunk
}).collect();
// timer ends.
let elapsed = now.elapsed();
println!("time elapsed: {:.2?}", elapsed);
}
执行代码(~24ms)
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
func normalise(value uint16, min uint16, max uint16) float32 {
return float32(value-min) / float32(max-min)
}
func main() {
const pixelSize = 9000000
var fakeImage [][]uint16
// Create a new random number generator
src := rand.NewSource(time.Now().UnixNano())
rng := rand.New(src)
for i := 0; i < pixelSize; i++ {
var pixel []uint16
for j := 0; j < 4; j++ {
pixel = append(pixel, uint16(rng.Intn(1<<16)))
}
fakeImage = append(fakeImage, pixel)
}
normalised_image := make([][4]float32, pixelSize)
var wg sync.WaitGroup
// Time starts now
now := time.Now()
chunkSize := 300_000
numChunks := pixelSize / chunkSize
if pixelSize%chunkSize != 0 {
numChunks++
}
for i := 0; i < numChunks; i++ {
wg.Add(1)
go func(i int) {
// Loop through the pixels in the chunk
for j := i * chunkSize; j < (i+1)*chunkSize && j < pixelSize; j++ {
// Normalise the pixel values
_r := normalise(fakeImage[j][0], 0, ^uint16(0))
_g := normalise(fakeImage[j][1], 0, ^uint16(0))
_b := normalise(fakeImage[j][2], 0, ^uint16(0))
_a := normalise(fakeImage[j][3], 0, ^uint16(0))
// Set the pixel values
normalised_image[j][0] = _r
normalised_image[j][1] = _g
normalised_image[j][2] = _b
normalised_image[j][3] = _a
}
wg.Done()
}(i)
}
wg.Wait()
elapsed := time.Since(now)
fmt.Println("Time taken:", elapsed)
}
正确答案
加快 rust 代码速度最重要的初始更改是使用正确的类型。在 go 中,您使用 [4]float32 来表示 rbga 四元组,而在 rust 中,您使用 vec。用于性能的正确类型是 [f32; 4],这是一个已知恰好包含 4 个浮点数的数组。已知大小的数组不需要进行堆分配,而 vec 始终进行堆分配。这会极大地提高您的性能 - 在我的机器上,这是 8 倍的差异。
原始片段:
let fake_image: vec<vec<u16>> = (0..pixel_size).map(|_| {
(0..4).map(|_| {
rand::thread_rng().gen_range(0..=u16::max)
}).collect()
}).collect();
...
let _normalised_image: vec<vec<vec<f32>>> = fake_image.par_chunks(chunk_size).map(|chunk| {
let normalised_chunk: vec<vec<f32>> = chunk.iter().map(|i| {
let r = normalise(i[0], 0, u16::max);
let g = normalise(i[1], 0, u16::max);
let b = normalise(i[2], 0, u16::max);
let a = normalise(i[3], 0, u16::max);
vec![r, g, b, a]
}).collect();
normalised_chunk
}).collect();
新片段:
let fake_image: vec<[u16; 4]> = (0..pixel_size).map(|_| {
let mut result: [u16; 4] = default::default();
result.fill_with(|| rand::thread_rng().gen_range(0..=u16::max));
result
}).collect();
...
let _normalised_image: vec<vec<[f32; 4]>> = fake_image.par_chunks(chunk_size).map(|chunk| {
let normalised_chunk: vec<[f32; 4]> = chunk.iter().map(|i| {
let r = normalise(i[0], 0, u16::max);
let g = normalise(i[1], 0, u16::max);
let b = normalise(i[2], 0, u16::max);
let a = normalise(i[3], 0, u16::max);
[r, g, b, a]
}).collect();
normalised_chunk
}).collect();
在我的机器上,这导致大约 7.7 倍的加速,使 rust 和 go 大致持平。为每个四元组进行堆分配的开销极大地减慢了 rust 的速度,并淹没了其他所有内容;消除这一点可以让 rust 和 go 处于更平衡的地位。
其次,您的 go 代码中有一个小错误。在 rust 代码中,您计算归一化的 r、g、b 和 a,而在 go 代码中,您仅计算 _r、_g 和 _bzqbendczq b.我的机器上没有安装 go,但我想这让 go 比 rust 具有轻微的不公平优势,因为你所做的工作更少。
第三,你在 rust 和 go 中仍然没有做同样的事情。在 rust 中,您将原始图像分割成块,并为每个块生成一个 vec<[f32; 4]>。这意味着内存中仍然有一堆块,稍后您必须将它们组合成单个最终图像。在 go 中,您可以分割原始块,并将每个块写入一个公共数组中。我们可以进一步重写您的 rust 代码以完美模仿 go 代码。这是 rust 中的样子:
let _normalized_image: vec<[f32; 4]> = {
let mut destination = vec![[0 as f32; 4]; pixel_size];
fake_image
.par_chunks(chunk_size)
// the "zip" function allows us to iterate over a chunk of the input
// array together with a chunk of the destination array.
.zip(destination.par_chunks_mut(chunk_size))
.for_each(|(i_chunk, d_chunk)| {
// sanity check: the chunks should be of equal length.
assert!(i_chunk.len() == d_chunk.len());
for (i, d) in i_chunk.iter().zip(d_chunk) {
let r = normalise(i[0], 0, u16::max);
let g = normalise(i[1], 0, u16::max);
let b = normalise(i[2], 0, u16::max);
let a = normalise(i[3], 0, u16::max);
*d = [r, g, b, a];
// alternately, we could do the following loop:
// for j in 0..4 {
// d[j] = normalise(i[j], 0, u16::max);
// }
}
});
destination
};
现在,您的 rust 代码和 go 代码确实在做同样的事情。我怀疑您会发现 rust 代码稍微快一些。
最后,如果您在现实生活中这样做,您应该尝试的第一件事是使用 map,如下所示:
let _normalized_image = fake_image.par_iter().map(|&[r, b, g, a]| {
[ normalise(r, 0, u16::max),
normalise(b, 0, u16::max),
normalise(g, 0, u16::max),
normalise(a, 0, u16::max),
]
}).collect::<vec<_>>();
这与在我的机器上手动分块一样快。
use rand::Rng;
use std::time::Instant;
use rayon::prelude::*;
fn normalise(value: u16, min: u16, max: u16) -> f32 {
(value - min) as f32 / (max - min) as f32
}
type PixelU16 = (u16, u16, u16, u16);
type PixelF32 = (f32, f32, f32, f32);
fn main() {
let pixel_size = 9_000_000;
let fake_image: Vec<PixelU16> = (0..pixel_size).map(|_| {
let mut rng =
rand::thread_rng();
(rng.gen_range(0..=u16::MAX), rng.gen_range(0..=u16::MAX), rng.gen_range(0..=u16::MAX), rng.gen_range(0..=u16::MAX))
}).collect();
// Time starts now.
let now = Instant::now();
let chunk_size = 300_000;
let _normalised_image: Vec<Vec<PixelF32>> = fake_image.par_chunks(chunk_size).map(|chunk| {
let normalised_chunk: Vec<PixelF32> = chunk.iter().map(|i| {
let r = normalise(i.0, 0, u16::MAX);
let g = normalise(i.1, 0, u16::MAX);
let b = normalise(i.2, 0, u16::MAX);
let a = normalise(i.3, 0, u16::MAX);
(r, g, b, a)
}).collect::<Vec<_>>();
normalised_chunk
}).collect();
// Timer ends.
let elapsed = now.elapsed();
println!("Time elapsed: {:.2?}", elapsed);
}
我已将使用数组切换为元组,并且该解决方案已经比您在我的计算机上提供的解决方案快了 10 倍。通过削减 vec 并使用 arc 或某些 mpsc 通道(通过减少堆分配量)甚至可以提高速度。
到这里,我们也就讲完了《为何 Rust 的 --release 构建速度比 Go 慢?》的内容了。个人认为,基础知识的学习和巩固,是为了更好的将其运用到项目中,欢迎关注golang学习网公众号,带你了解更多关于的知识点!
提高 Go 1.20 中编译时的严格一致性
- 上一篇
- 提高 Go 1.20 中编译时的严格一致性
- 下一篇
- 实现用户身份验证的 GoLang SSH 服务器
-
- Golang · Go问答 | 1年前 |
- 在读取缓冲通道中的内容之前退出
- 139浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 戈兰岛的全球 GOPRIVATE 设置
- 204浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何将结构作为参数传递给 xml-rpc
- 325浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何用golang获得小数点以下两位长度?
- 478浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何通过 client-go 和 golang 检索 Kubernetes 指标
- 486浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 将多个“参数”映射到单个可变参数的习惯用法
- 439浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 将 HTTP 响应正文写入文件后出现 EOF 错误
- 357浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 结构中映射的匿名列表的“复合文字中缺少类型”
- 352浏览 收藏
-
- Golang · Go问答 | 1年前 |
- NATS Jetstream 的性能
- 101浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何将复杂的字符串输入转换为mapstring?
- 440浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 相当于GoLang中Java将Object作为方法参数传递
- 212浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何确保所有 goroutine 在没有 time.Sleep 的情况下终止?
- 143浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3198次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3411次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3441次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4549次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3819次使用
-
- GoLand调式动态执行代码
- 2023-01-13 502浏览
-
- 用Nginx反向代理部署go写的网站。
- 2023-01-17 502浏览
-
- Golang取得代码运行时间的问题
- 2023-02-24 501浏览
-
- 请问 go 代码如何实现在代码改动后不需要Ctrl+c,然后重新 go run *.go 文件?
- 2023-01-08 501浏览
-
- 如何从同一个 io.Reader 读取多次
- 2023-04-11 501浏览

