首页 > 其他分享 >Rust‘s “zero-cost abstraction“

Rust‘s “zero-cost abstraction“

时间:2024-08-27 10:57:58浏览次数:14  
标签:i32 sum zero cost let Rust loop fn

Rust's "zero-cost abstraction"

Iterators vs. For Loops

Rust’s “zero-cost abstraction” can be demonstrated in many scenarios where high-level constructs produce low-level, optimized code with no additional runtime cost. Here are several examples that highlight how Rust achieves this:
Rust 的“零成本抽象”可以在许多场景中得到证明,其中高级构造生成低级优化代码,而无需额外的运行时成本。以下是几个例子,重点介绍了 Rust 如何实现这一目标:

Using Iterators:

fn sum_of_squares(v: &[i32]) -> i32 {
    v.iter().map(|&x| x * x).sum()
}

Manual For Loop:

fn sum_of_squares(v: &[i32]) -> i32 {
    let mut sum = 0;
    for &x in v {
        sum += x * x;
    }
    sum
}

To compare the efficiency of Iterators vs. For Loops in Rust, let’s write a benchmark that compares the time it takes to sum the squares of numbers using both approaches. We can use the std::time::Instant to measure the execution time.

为了比较 Rust 中迭代器与 For 循环的效率,让我们编写一个基准测试来比较使用这两种方法对数字平方求和所需的时间。我们可以使用 std::time::Instant 来测量执行时间。

use std::time::Instant;

fn sum_of_squares_iterator(v: &[i32]) -> i32 {
    v.iter().map(|&x| x * x).sum()
}

fn sum_of_squares_for_loop(v: &[i32]) -> i32 {
    let mut sum = 0;
    for &x in v {
        sum += x * x;
    }
    sum
}

fn main() {
    // Create a large vector for benchmarking
    let size = 10_00;
    let v: Vec<i32> = (1..=size).collect();

    // Benchmarking Iterators
    let start_iter = Instant::now();
    let sum_iter = sum_of_squares_iterator(&v);
    let duration_iter = start_iter.elapsed();
    println!("Iterator: Sum = {}, Time = {:?}", sum_iter, duration_iter);

    // Benchmarking For Loop
    let start_loop = Instant::now();
    let sum_loop = sum_of_squares_for_loop(&v);
    let duration_loop = start_loop.elapsed();
    println!("For Loop: Sum = {}, Time = {:?}", sum_loop, duration_loop);
}

We define two functions:

1.1 sum_of_squares_iterator uses Iterators with iter(), map(), and sum().
1.2 sum_of_squares_for_loop uses a For Loop to calculate the sum of squares.

The vector v contains 1000 integers for a meaningful comparison.

We measure the time taken for each approach using Instant::now() and compare the results.

Output:
The output will display The total sum calculated (which should be The same for both methods) and The time taken by each approach.

Iterator: Sum = 333833500, Time = 24.291µs
For Loop: Sum = 333833500, Time = 26.75µs

If use u128:

use std::time::Instant;

fn sum_of_squares_iterator(v: &[u128]) -> u128 {
    v.iter()
        .map(|&x| x*x)  // Ensure overflowing addition
        .sum()
}

fn sum_of_squares_for_loop(v: &[u128]) -> u128 {
    let mut sum: u128 = 0;
    for &x in v {
        sum += x*x;  // Ensure overflowing addition
    }
    sum
}

fn main() {
    // Create a large vector for benchmarking
    let size = 10_000_000;
    // let size = 10;
    let v: Vec<u128> = (1..=size).collect();  // Explicitly type the vector as i32

    // Benchmarking Iterators
    let start_iter = Instant::now();
    let sum_iter: u128 = sum_of_squares_iterator(&v);
    let duration_iter = start_iter.elapsed();
    println!("Iterator: Sum = {}, Time = {:?}", sum_iter, duration_iter);

    // Benchmarking For Loop
    let start_loop = Instant::now();
    let sum_loop = sum_of_squares_for_loop(&v);
    let duration_loop = start_loop.elapsed();
    println!("For Loop: Sum = {}, Time = {:?}", sum_loop, duration_loop);
}

如果size = 10_000_000,时间效率约相差4倍

Iterator: Sum = 333333383333335000000, Time = 254.317167ms
For Loop: Sum = 333333383333335000000, Time = 61.483042ms

Generics and Monomorphization

Rust allows for generic functions and types that work with any type, but at compile time, it monomorphizes them, generating specialized versions of the function or type for each concrete type used. This eliminates the overhead of dynamic dispatch or boxing.
Generic Function:
Rust 允许使用任何类型的泛型函数和类型,但在编译时,它会将它们单态化,为所使用的每个具体类型生成函数或类型的专门版本。这消除了动态调度或装箱的开销。
通用函数:

fn add<T: std::ops::Add<Output = T>>(a: T, b: T) -> T {
    a + b
}

If you call add(3, 4) and add(1.5, 2.0), the compiler generates specific versions of the function for i32 and f64, respectively. There’s no cost associated with generics at runtime since the specific code is generated during compilation.
如果调用 add(3, 4) 和 add(1.5, 2.0),编译器将分别为 i32 和 f64 生成该函数的特定版本。由于特定代码是在编译期间生成的,因此在运行时没有与泛型相关的成本。

fn add_i32(a: i32, b: i32) -> i32 { a + b }
fn add_f64(a: f64, b: f64) -> f64 { a + b }

Trait-based Abstraction

Traits are a way to define shared behavior across types, but using traits doesn’t introduce runtime overhead.
Using Traits:
特征是一种定义跨类型共享行为的方法,但使用特征不会引入运行时开销。
使用特征:

trait Area {
    fn area(&self) -> f64;
}

struct Circle {
    radius: f64,
}

impl Area for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }
}

struct Square {
    side: f64,
}

impl Area for Square {
    fn area(&self) -> f64 {
        self.side * self.side
    }
}

Even though this uses a high-level trait abstraction, the Rust compiler optimizes the code, inlining the methods and ensuring there’s no additional cost compared to calling methods directly on the concrete types.
即使这使用了高级特征抽象,Rust 编译器也会优化代码,内联方法并确保与直接在具体类型上调用方法相比没有额外的成本。

Closures vs. Function Pointers

Closures in Rust are abstractions over functions that capture the environment. However, Rust compiles them down to efficient function pointers without runtime overhead.
Using Closures:
Rust 中的闭包是对捕获环境的函数的抽象。然而,Rust 将它们编译为高效的函数指针,没有运行时开销。
使用闭包:

fn apply<F>(f: F, x: i32) -> i32
where
    F: Fn(i32) -> i32,
{
    f(x)
}

fn main() {
    let result = apply(|x| x + 1, 5);
    println!("{}", result); // 6
}

This example shows a higher-level abstraction where a closure is passed as a function, but at runtime, this is transformed into an efficient function pointer call without any additional overhead.
此示例显示了一个更高级别的抽象,其中闭包作为函数传递,但在运行时,这会转换为高效的函数指针调用,而无需任何额外的开销。

Enums and Pattern Matching

Enums are a zero-cost abstraction for handling different types or states. Pattern matching is highly optimized and incurs no runtime overhead compared to manual branching.
Enum Example:
枚举是处理不同类型或状态的零成本抽象。模式匹配经过高度优化,与手动分支相比不会产生运行时开销。
Enum 示例:

enum Shape {
    Circle(f64),
    Square(f64),
}

fn area(shape: Shape) -> f64 {
    match shape {
        Shape::Circle(radius) => std::f64::consts::PI * radius * radius,
        Shape::Square(side) => side * side,
    }
}

The match statement compiles down to an efficient switch or branching construct. Rust doesn’t add any extra runtime cost beyond what would happen with manually written conditionals.
match 语句编译为高效的 switch 或分支结构。除了手动编写的条件之外,Rust 不会增加任何额外的运行时成本。

Smart Pointers (e.g., Box, Rc, Arc)

Rust’s smart pointers like Box, Rc, and Arc are abstractions over raw pointers but are zero-cost in the sense that they only add the exact bookkeeping needed (like reference counting in Rc or Arc). There’s no unnecessary overhead for using smart pointers, and they’re often as fast as managing raw memory by hand in C.
Boxed Values:
Rust 的智能指针(如 Box、Rc 和 Arc)是对原始指针的抽象,但它们是零成本的,因为它们只添加所需的精确簿记(如 Rc 或 Arc 中的引用计数)。使用智能指针没有不必要的开销,而且它们通常与在 C 中手动管理原始内存一样快。
装箱值:

let x = Box::new(5); // Allocates `5` on the heap, but no unnecessary overhead

In this case, the Box type ensures that the value is allocated on the heap, but the abstraction adds no additional runtime cost beyond the necessary heap allocation.
在这种情况下,Box 类型确保值在堆上分配,但除了必要的堆分配之外,抽象不会增加额外的运行时成本。

Ownership and Borrowing

Rust’s ownership system is enforced at compile time, meaning it incurs no runtime cost. The borrow checker ensures that memory is managed safely without needing garbage collection or reference counting unless you explicitly choose to use Rc or Arc.
Ownership:
Rust 的所有权系统在编译时强制执行,这意味着它不会产生运行时成本。借用检查器可确保内存得到安全管理,无需垃圾回收或引用计数,除非您明确选择使用 Rc 或 Arc。
所有权:

fn main() {
    let x = String::from("hello");
    let y = x; // Ownership of `x` is moved to `y`
    println!("{}", y); // Safe access with no runtime cost
}

In other languages, handling memory ownership might require runtime checks, but Rust handles this entirely at compile time, making ownership a zero-cost abstraction.
在其他语言中,处理内存所有权可能需要运行时检查,但 Rust 完全在编译时处理此问题,使所有权成为零成本抽象。

Static Dispatch vs. Dynamic Dispatch

In Rust, you can choose between static dispatch (zero-cost) and dynamic dispatch when using traits. With static dispatch, the compiler knows at compile time which implementation to call, and there is no additional overhead. Dynamic dispatch (dyn Trait) uses a vtable lookup, but it’s only used when explicitly chosen.
Static Dispatch:
在 Rust 中,使用特征时,您可以在静态调度(零成本)和动态调度之间进行选择。使用静态分派,编译器在编译时就知道要调用哪个实现,并且没有额外的开销。动态调度 (dyn Trait) 使用 vtable 查找,但仅在显式选择时使用。
静态调度:

fn process<T: Area>(shape: T) {
    println!("Area: {}", shape.area());
}

This will incur zero runtime cost because the compiler knows exactly which function to call.
Dynamic Dispatch (slight overhead):
这将导致零运行时成本,因为编译器确切地知道要调用哪个函数。
动态调度(轻微开销):

fn process(shape: &dyn Area) {
    println!("Area: {}", shape.area());
}

This introduces a small vtable lookup but only when explicitly requested with dyn.
这引入了一个小的 vtable 查找,但仅当使用 dyn 明确请求时才进行。

标签:i32,sum,zero,cost,let,Rust,loop,fn
From: https://blog.csdn.net/chengbin20101/article/details/141586269

相关文章

  • 折腾 Quickwit,Rust 编写的分布式搜索引擎-官方配置详解
    Nodeconfiguration(节点配置)节点配置允许您为集群中的各个节点自定义和优化设置。它被分为几个部分:常规配置设置:共享的顶级属性Storage(存储)设置:在storage部分定义https://quickwit.io/docs/configuration/node-config#storage-configurationMetastore(元存储)设置:在......
  • AlphaGo Zero论文《Mastering the game of Go without human knowledge》阅读笔记
    AlphaGoZero论文阅读笔记原论文:《MasteringthegameofGowithouthumanknowledge》简述:论文提出了一种新的围棋人工智能算法AlphaGoZero,该算法可以在完全无监督的情况下进行训练,并且超越了之前的AlphaGoFan和AlphaGoLee的表现。该算法具有如下特点:在无监督的情况......
  • 折腾 Quickwit,Rust 编写的分布式搜索引擎-官方教程
    快速上手在本快速入门指南中,我们将安装Quickwit,创建一个索引,添加文档,最后执行搜索查询。本指南中使用的所有Quickwit命令都在CLI参考文档中进行了记录。https://quickwit.io/docs/main-branch/reference/cli使用Quickwit安装程序安装QuickwitQuickwit安装程序会......
  • 【Rust光年纪】文本分析利器:探索Rust语言的多功能文本处理库
    从情感分析到关键词提取:Rust语言文本分析库详解前言随着自然语言处理技术的不断发展,对各种文本数据进行分析和处理的需求也在不断增加。本文将介绍一些用于Rust语言的文本分析和处理库,包括情感分析、自然语言处理、中文转换、语言检查和关键词提取等方面的工具和资源。......
  • rustlings v6.0 运行时出现 “ You are trying to run Rustlings using the old metho
    背景在之前学习rust时,使用过一段时间rustlings感觉还不错,但是之前的学习只把rustlings的题目刷了一半,然后想再从头到尾刷一遍rustlings的题目。在rustlings的README.md文档中也没有找到重置rustlings的方法,而且官方的分支也更新到了v6.2.0(我之前使用的似乎是v5.......
  • 折腾 Quickwit,Rust 编写的分布式搜索引擎(专为从对象存储中实现亚秒级搜索而设计)
    什么是Quickwit?Quickwit是首个能在云端存储上直接执行复杂的搜索与分析查询的引擎,并且具有亚秒级延迟。它借助Rust语言和分离计算与存储的架构设计,旨在实现资源高效利用、易于操作以及能够扩展到PB级数据量。Quickwit非常适合日志管理、分布式追踪以及通常为不可变数据......
  • 【CUDA编程笔记】thrust::device_vector<float> signal无法编译问题记录
    thrust::device_vectorsignal无法编译问题记录CUDA编程笔记一、问题记录正常编译时,无法编译二、源码#include<thrust/host_vector.h>#include<thrust/device_vector.h>#include<thrust/generate.h>#include<thrust/sort.h>#include<thrust/copy.h>#includ......
  • rust语言之所有权
    Rust受现代c++的启发,引入智能指针来管理堆内存。在rust中,所有权是系统性的概念,是rust语言的基础设施。5.1通用概念编程语言中的值主要分成两类:值类型(Value):数据直接存储在栈中的数据类型引用类型(Reference):将数据存在堆中,而栈中值存放指向堆中数据的地址(指针)为了更精确的对......
  • rust库-ouroboros中文文档
    文档原文:https://docs.rs/ouroboros/latest/ouroboros/attr.self_referencing.html属性宏ouroboros::self_referencing#[self_referencing]此宏用于将常规结构转换为自引用结构。举个例子:useouroboros::self_referencing;#[self_referencing]structMyStruct{int_d......
  • 初探 Rust 语言与环境搭建
    1.Rust简介Rust的历史起源:Rust语言最初由Mozilla研究员GraydonHoare于2006年开始设计,并于2009年首次公开。开发:Rust是Mozilla实验室的一个项目,目的是创建一种能够保证内存安全同时又不牺牲性能的系统编程语言。发布:Rust1.0稳定版于2015年发布,标志着语言......