AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / coding / 问题 / 79576182
Accepted
Serdar Didan
Serdar Didan
Asked: 2025-04-16 07:35:01 +0800 CST2025-04-16 07:35:01 +0800 CST 2025-04-16 07:35:01 +0800 CST

Rust 结构中使用数组代替向量

  • 772

我用多种编程语言编写了一些实现相同功能的小程序。我比较了它们的性能和内存占用。这是我编写的测试程序之一。

两个程序读取同一个文件。该文件包含由 \t(制表符)和 \n(回车符)连接的数据。内容类似如下。

aaaa\tbbb\tccc\tddd\teee\tfff\tggg\thhh\n
aaaa\tbbb\tccc\tddd\teee\tfff\tggg\thhh\n
aaaa\tbbb\tccc\tddd\teee\tfff\tggg\thhh\n
aaaa\tbbb\tccc\tddd\teee\tfff\tggg\thhh

我创建的文件有 14 列和 63 行。这些数字可能会更改。这并不重要,因为我正在测试它。

我使用 split('\n') 获取行。然后使用 split('\t') 获取行中的字段。这是一个非常简单的反序列化过程。程序读取文件一次,然后对其进行 200,000 次反序列化。然后将时间打印到控制台。

去:

package main

import (
    "fmt"
    "os"
    "strings"
    "time"
)

type Datatable struct {
    id   int
    rows [][]string
}

func main() {
    start := time.Now()

    dat, err := os.ReadFile("C:\\Temp\\test1.txt")
    if err != nil {
        panic("file not found")
    }
    str := string(dat)

    count := 200_000
    tables := make([]Datatable, count)

    for i := 0; i < count; i++ {
        table := Datatable{i, nil}
        var lines []string = strings.Split(str, "\n")
        table.rows = make([][]string, len(lines))
        for j, l := range lines {
            table.rows[j] = strings.Split(l, "\t")
        }
        tables[i] = table
    }

    end := time.Now()
    elapsed := end.Sub(start)
    fmt.Println("Time: ", elapsed)

    var b []byte = make([]byte, 1)
    os.Stdin.Read(b)
}

锈:

use std::fs;
use std::time::SystemTime;
use std::io::{self, BufRead};

struct Table<'a>{
    id: usize,
    rows: Vec<Vec<&'a str>>,
}
fn main() {
    let start = SystemTime::now();
    let str = fs::read_to_string("C:\\Temp\\test1.txt")
        .expect("read_to_string: failed");

    let count = 200_000;
    let mut tables = Vec::with_capacity(count);
    for i in 0..count {
        let lines = str.split('\n');
        let mut table = Table {
            id : i,
            rows : Vec::with_capacity(lines.size_hint().0),
        };
        for item in lines {
            table.rows.push(item.split('\t').collect::<Vec<&str>>());
        }
        tables.push(table);
    }
    println!("Time: {}", start.elapsed().expect("elapsed: failed").as_millis());

    let mut line = String::new();
    let stdin = io::stdin();
    stdin.lock().read_line(&mut line).expect("read_line: failed");
}
  • go版本go1.24.2 windows/amd64
  • rustc 1.85.1 (4eb161250 2025-03-15)
  • 操作系统:Windows 11

构建命令:

go build -ldflags "-s -w"
cargo build --release

在我的电脑上结果如下:

去:

Time     : 4510 milis
RAM usage: 3217 MB

锈

Time     : 5845 milis
RAM usage: 3578 MB

我尽量把代码写得简单一点,大家可以直接复制粘贴试试。

Rust 代码可以运行。但它比 Go 慢,而且占用更多内存。在编写代码之前,我希望 Rust 能跑得更快。也许有什么我不知道的地方。

在 Rust 的结构体中使用数组可能会使其运行得更快。但我不确定这是否可行。我想知道的是,如何用 Rust 编写这段代码才能使其运行得更快?

arrays
  • 3 3 个回答
  • 159 Views

3 个回答

  • Voted
  1. Best Answer
    Chayim Friedman
    2025-04-16T11:54:41+08:002025-04-16T11:54:41+08:00

    Windows 上的系统分配器非常慢。Rust 默认使用系统分配器,而 Go 有自己的分配器。

    如果你替换它,你会发现它会更快。一个可选的分配器是mimalloc。另一个可能的选项是jemalloc,但它在 Windows 上构建起来很困难。

    • 3
  2. Masklinn
    2025-04-17T03:17:58+08:002025-04-17T03:17:58+08:00

    挂钟问题在于

    Vec::with_capacity(lines.size_hint().0),
    

    和

    item.split('\t').collect::<Vec<&str>>()
    

    ASplit是一个惰性迭代器,所以它具有size_hint(),(0, None)因此第一次调用不会进行任何预分配,并且IntoIterator也没有针对 split 执行此操作的专门化。

    Go stdlib 确实会计算分隔符出现的次数,并预先分配生成的切片strings.Split:https://cs.opensource.google/go/go/+/refs/tags/go1.24.2 :src/strings/strings.go;l=298-300

    因此,在这两种情况下,向量的容量都是 0,并且在追加时会调整大小,因此 Rust 版本比 Go 版本分配更多(每行 3 个,每个表 5 个,所以每个表 194 个,而 Go 版本为 1 个和 1 个,所以每个表 64 个)。

    如果你正确地预分配向量,就可以避免这种情况。在我的机器上(运行 macOS 的 mbp 和 m1 pro),这将运行时间从 4 秒缩短到 3 秒,Go 的运行时间为 3.5 秒。

    use std::fs;
    use std::time::SystemTime;
    use std::io::{self, BufRead};
    
    struct Table<'a>{
        id: usize,
        rows: Vec<Vec<&'a str>>,
    }
    fn items(s: &str, sep: u8) -> usize {
        s.bytes().filter(|&c| c == sep).count() + 1
    }
    fn split(s: &str, sep: u8) -> Vec<&str> {
        let mut r = Vec::with_capacity(items(s, sep));
        r.extend(s.split(char::from(sep)));
        r
    }
    fn main() {
        let start = SystemTime::now();
        let str = fs::read_to_string("C:\\Temp\\test1.txt")
            .expect("read_to_string: failed");
    
        let count = 200_000;
        let mut tables = Vec::with_capacity(count);
        for i in 0..count {
            let mut table = Table {
                id : i,
                rows : Vec::with_capacity(items(&str, b'\n')),
            };
            for item in str.split('\n') {
                table.rows.push(split(item, b'\t'));
            }
            tables.push(table);
        }
        println!("Time: {}", start.elapsed().expect("elapsed: failed").as_millis());
    
        let mut line = String::new();
        let stdin = io::stdin();
        stdin.lock().read_line(&mut line).expect("read_line: failed");
    }
    

    我没有做过任何内存测量,但这也应该是内存开销的来源:预分配后,缓冲区的大小将精确到每行 14 个项目,每个表go rust 63 个项目。如果不进行预分配,Rust 的增长因子为 2,意味着每行分配 16 个项目,每个表playground分配 64 个项目。

    这是每个表的 2 * 8 * 2 * 63(2 乘以 63 行)+ 24(未使用的行)的冗余量&str,乘以 200_000 个表,大约是 390MB。

    • 1
  3. Bmik Tyg
    2025-04-16T15:17:28+08:002025-04-16T15:17:28+08:00
    use std::fs;
    use std::time::SystemTime;
    use std::io::{self, BufRead};
    
    struct Table<'a> {
        id: usize,
        rows: Vec<Vec<&'a str>>,
    }
    
    fn parse_table<'a>(id: usize, lines: &'a [&'a str]) -> Table<'a> {
        let mut table = Table {
            id,
            rows: Vec::with_capacity(lines.len()),
        };
        for line in lines {
            table.rows.push(line.split('\t').collect());
        }
        table
    }
    
    fn main() {
        let start = SystemTime::now();
    
        let contents = fs::read_to_string("C:\\Temp\\test1.txt").expect("read_to_string: failed");
        let lines: Vec<&str> = contents.lines().collect(); // parse once!
    
        let count = 200_000;
        let mut tables = Vec::with_capacity(count);
    
        for i in 0..count {
            let table = parse_table(i, &lines);
            tables.push(table);
        }
    
        println!("Time: {} ms", start.elapsed().unwrap().as_millis());
    
        let mut line = String::new();
        let stdin = io::stdin();
        stdin.lock().read_line(&mut line).unwrap();
    }
    

    通过不分割计数来尝试此操作。

    • -1

相关问题

  • 可以从指针数组中的值初始化指针吗?

  • 可以初始化指向类型变量数组的指针吗?

  • Swift Array,如何从数组中检索嵌套枚举中的所有元素

  • 为什么 C 字符串并不总是等同于字符数组?

  • PowerShell:如何像这样转换 hastable 数组值?

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    重新格式化数字,在固定位置插入分隔符

    • 6 个回答
  • Marko Smith

    为什么 C++20 概念会导致循环约束错误,而老式的 SFINAE 不会?

    • 2 个回答
  • Marko Smith

    VScode 自动卸载扩展的问题(Material 主题)

    • 2 个回答
  • Marko Smith

    Vue 3:创建时出错“预期标识符但发现‘导入’”[重复]

    • 1 个回答
  • Marko Smith

    具有指定基础类型但没有枚举器的“枚举类”的用途是什么?

    • 1 个回答
  • Marko Smith

    如何修复未手动导入的模块的 MODULE_NOT_FOUND 错误?

    • 6 个回答
  • Marko Smith

    `(表达式,左值) = 右值` 在 C 或 C++ 中是有效的赋值吗?为什么有些编译器会接受/拒绝它?

    • 3 个回答
  • Marko Smith

    在 C++ 中,一个不执行任何操作的空程序需要 204KB 的堆,但在 C 中则不需要

    • 1 个回答
  • Marko Smith

    PowerBI 目前与 BigQuery 不兼容:Simba 驱动程序与 Windows 更新有关

    • 2 个回答
  • Marko Smith

    AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String”

    • 1 个回答
  • Martin Hope
    Fantastic Mr Fox msvc std::vector 实现中仅不接受可复制类型 2025-04-23 06:40:49 +0800 CST
  • Martin Hope
    Howard Hinnant 使用 chrono 查找下一个工作日 2025-04-21 08:30:25 +0800 CST
  • Martin Hope
    Fedor 构造函数的成员初始化程序可以包含另一个成员的初始化吗? 2025-04-15 01:01:44 +0800 CST
  • Martin Hope
    Petr Filipský 为什么 C++20 概念会导致循环约束错误,而老式的 SFINAE 不会? 2025-03-23 21:39:40 +0800 CST
  • Martin Hope
    Catskul C++20 是否进行了更改,允许从已知绑定数组“type(&)[N]”转换为未知绑定数组“type(&)[]”? 2025-03-04 06:57:53 +0800 CST
  • Martin Hope
    Stefan Pochmann 为什么 {2,3,10} 和 {x,3,10} (x=2) 的顺序不同? 2025-01-13 23:24:07 +0800 CST
  • Martin Hope
    Chad Feller 在 5.2 版中,bash 条件语句中的 [[ .. ]] 中的分号现在是可选的吗? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench 为什么双破折号 (--) 会导致此 MariaDB 子句评估为 true? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng 为什么 `dict(id=1, **{'id': 2})` 有时会引发 `KeyError: 'id'` 而不是 TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String” 2024-03-20 03:12:31 +0800 CST

热门标签

python javascript c++ c# java typescript sql reactjs html

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve