“功能性”Rust 对性能有哪些影响?

2024-05-15

我正在关注 Rust 轨道运动.io https://exercism.io/。我有相当多的 C/C++ 经验。我喜欢 Rust 的“功能”元素,但我担心相对性能。

我解决了“行程编码”问题 https://exercism.io/tracks/rust/exercises/run-length-encoding/:

pub fn encode(source: &str) -> String {
    let mut retval = String::new();
    let firstchar = source.chars().next();
    let mut currentchar = match firstchar {
        Some(x) => x,
        None => return retval,
    };
    let mut currentcharcount: u32 = 0;
    for c in source.chars() {
        if c == currentchar {
            currentcharcount += 1;
        } else {
            if currentcharcount > 1 {
                retval.push_str(&currentcharcount.to_string());
            }
            retval.push(currentchar);
            currentchar = c;
            currentcharcount = 1;
        }
    }
    if currentcharcount > 1 {
        retval.push_str(&currentcharcount.to_string());
    }
    retval.push(currentchar);
    retval
}

我注意到评分最高的答案之一看起来更像是这样的:

extern crate itertools;

use itertools::Itertools;

pub fn encode(data: &str) -> String {
    data.chars()
        .group_by(|&c| c)
        .into_iter()
        .map(|(c, group)| match group.count() {
            1 => c.to_string(),
            n => format!("{}{}", n, c),
        })
        .collect()
}

我喜欢评价最高的解决方案;它简单、实用、优雅。这就是他们向我承诺 Rust 的全部内容。另一方面,我的则很恶心并且充满了可变变量。你可以看出我已经习惯了 C++。

我的问题是函数式风格对性能有显着的影响。我使用相同的 4MB 随机数据编码 1000 次来测试这两个版本。我的命令式解决方案只用了不到 10 秒;功能解决方案大约需要 2 分 30 秒。

  • 为什么函数式风格比命令式风格慢这么多?
  • 功能实现是否存在导致如此巨大的减速的问题?
  • 如果我想编写高性能代码,我应该吗?ever使用这种功能风格?

TL;DR

功能实现can在某些情况下,比原来的程序实现更快。

为什么函数式风格比命令式风格慢这么多?功能实现是否存在导致如此巨大的减速的问题?

As Matthieu M. 已经指出 https://stackoverflow.com/a/55675389/155423,需要注意的重要一点是算法很重要。该算法的表达方式(过程式、命令式、面向对象、函数式、声明式)通常并不重要。

我发现功能代码有两个主要问题:

  • 一遍又一遍地分配大量字符串的效率很低。在最初的功能实现中,这是通过以下方式完成的to_string and format!.

  • 有使用的开销group_by,它的存在是为了给出一个嵌套的iterator,您不需要只是为了获得计数。

Using more迭代器工具(batching https://docs.rs/itertools/0.8.0/itertools/trait.Itertools.html#method.batching, take_while_ref https://docs.rs/itertools/0.8.0/itertools/trait.Itertools.html#method.take_while_ref, format_with https://docs.rs/itertools/0.8.0/itertools/trait.Itertools.html#method.format_with) 使两个实现更加接近:

pub fn encode_slim(data: &str) -> String {
    data.chars()
        .batching(|it| {
            it.next()
                .map(|v| (v, it.take_while_ref(|&v2| v2 == v).count() + 1))
        })
        .format_with("", |(c, count), f| match count {
            1 => f(&c),
            n => f(&format_args!("{}{}", n, c)),
        })
        .to_string()
}

4MiB 随机字母数字数据的基准,编译为RUSTFLAGS='-C target-cpu=native':

encode (procedural)     time:   [21.082 ms 21.620 ms 22.211 ms]

encode (fast)           time:   [26.457 ms 27.104 ms 27.882 ms]
Found 7 outliers among 100 measurements (7.00%)
  4 (4.00%) high mild
  3 (3.00%) high severe

如果您有兴趣创建自己的迭代器,您可以将过程代码与更多功能代码混合搭配:

struct RunLength<I> {
    iter: I,
    saved: Option<char>,
}

impl<I> RunLength<I>
where
    I: Iterator<Item = char>,
{
    fn new(mut iter: I) -> Self {
        let saved = iter.next(); // See footnote 1
        Self { iter, saved }
    }
}

impl<I> Iterator for RunLength<I>
where
    I: Iterator<Item = char>,
{
    type Item = (char, usize);

    fn next(&mut self) -> Option<Self::Item> {
        let c = self.saved.take().or_else(|| self.iter.next())?;

        let mut count = 1;
        while let Some(n) = self.iter.next() {
            if n == c {
                count += 1
            } else {
                self.saved = Some(n);
                break;
            }
        }

        Some((c, count))
    }
}

pub fn encode_tiny(data: &str) -> String {
    use std::fmt::Write;

    RunLength::new(data.chars()).fold(String::new(), |mut s, (c, count)| {
        match count {
            1 => s.push(c),
            n => write!(&mut s, "{}{}", n, c).unwrap(),
        }
        s
    })
}

1- 谢谢Stargateur 指出 https://chat.stackoverflow.com/transcript/message/45932591#45932591急切地获取第一个值有助于分支预测。

4MiB 随机字母数字数据的基准,编译为RUSTFLAGS='-C target-cpu=native':

encode (procedural)     time:   [19.888 ms 20.301 ms 20.794 ms]
Found 4 outliers among 100 measurements (4.00%)
  3 (3.00%) high mild
  1 (1.00%) high severe

encode (tiny)           time:   [19.150 ms 19.262 ms 19.399 ms]
Found 11 outliers among 100 measurements (11.00%)
  5 (5.00%) high mild
  6 (6.00%) high severe

我相信这更清楚地表明了主要基本的两种实现之间的区别:基于迭代器的解决方案是可恢复的。每次我们打电话next,我们需要查看是否有我们读过的前一个字符(self.saved)。这会向程序代码中不存在的代码添加一个分支。

另一方面,基于迭代器的解决方案更加灵活——我们现在可以对数据进行各种转换,或者直接写入文件而不是写入文件String等等。自定义迭代器可以扩展为对泛型类型进行操作,而不是char以及,使其very灵活的。

也可以看看:

  • 如何向迭代器添加新方法? https://stackoverflow.com/q/30540766/155423

如果我想编写高性能代码,我应该使用这种函数式风格吗?

我会的,直到基准测试表明这是瓶颈。然后评估why这是瓶颈。

支持代码

总是要展示你的作品,对吧?

基准测试.rs

use criterion::{criterion_group, criterion_main, Criterion}; // 0.2.11
use rle::*;

fn criterion_benchmark(c: &mut Criterion) {
    let data = rand_data(4 * 1024 * 1024);

    c.bench_function("encode (procedural)", {
        let data = data.clone();
        move |b| b.iter(|| encode_proc(&data))
    });

    c.bench_function("encode (functional)", {
        let data = data.clone();
        move |b| b.iter(|| encode_iter(&data))
    });

    c.bench_function("encode (fast)", {
        let data = data.clone();
        move |b| b.iter(|| encode_slim(&data))
    });

    c.bench_function("encode (tiny)", {
        let data = data.clone();
        move |b| b.iter(|| encode_tiny(&data))
    });
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

lib.rs

use itertools::Itertools; // 0.8.0
use rand; // 0.6.5

pub fn rand_data(len: usize) -> String {
    use rand::distributions::{Alphanumeric, Distribution};
    let mut rng = rand::thread_rng();
    Alphanumeric.sample_iter(&mut rng).take(len).collect()
}

pub fn encode_proc(source: &str) -> String {
    let mut retval = String::new();
    let firstchar = source.chars().next();
    let mut currentchar = match firstchar {
        Some(x) => x,
        None => return retval,
    };
    let mut currentcharcount: u32 = 0;
    for c in source.chars() {
        if c == currentchar {
            currentcharcount += 1;
        } else {
            if currentcharcount > 1 {
                retval.push_str(&currentcharcount.to_string());
            }
            retval.push(currentchar);
            currentchar = c;
            currentcharcount = 1;
        }
    }
    if currentcharcount > 1 {
        retval.push_str(&currentcharcount.to_string());
    }
    retval.push(currentchar);
    retval
}

pub fn encode_iter(data: &str) -> String {
    data.chars()
        .group_by(|&c| c)
        .into_iter()
        .map(|(c, group)| match group.count() {
            1 => c.to_string(),
            n => format!("{}{}", n, c),
        })
        .collect()
}

pub fn encode_slim(data: &str) -> String {
    data.chars()
        .batching(|it| {
            it.next()
                .map(|v| (v, it.take_while_ref(|&v2| v2 == v).count() + 1))
        })
        .format_with("", |(c, count), f| match count {
            1 => f(&c),
            n => f(&format_args!("{}{}", n, c)),
        })
        .to_string()
}

struct RunLength<I> {
    iter: I,
    saved: Option<char>,
}

impl<I> RunLength<I>
where
    I: Iterator<Item = char>,
{
    fn new(mut iter: I) -> Self {
        let saved = iter.next();
        Self { iter, saved }
    }
}

impl<I> Iterator for RunLength<I>
where
    I: Iterator<Item = char>,
{
    type Item = (char, usize);

    fn next(&mut self) -> Option<Self::Item> {
        let c = self.saved.take().or_else(|| self.iter.next())?;

        let mut count = 1;
        while let Some(n) = self.iter.next() {
            if n == c {
                count += 1
            } else {
                self.saved = Some(n);
                break;
            }
        }

        Some((c, count))
    }
}

pub fn encode_tiny(data: &str) -> String {
    use std::fmt::Write;

    RunLength::new(data.chars()).fold(String::new(), |mut s, (c, count)| {
        match count {
            1 => s.push(c),
            n => write!(&mut s, "{}{}", n, c).unwrap(),
        }
        s
    })
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn all_the_same() {
        let data = rand_data(1024);

        let a = encode_proc(&data);
        let b = encode_iter(&data);
        let c = encode_slim(&data);
        let d = encode_tiny(&data);

        assert_eq!(a, b);
        assert_eq!(a, c);
        assert_eq!(a, d);
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

“功能性”Rust 对性能有哪些影响? 的相关文章

随机推荐

  • 没有脚手架的 DefaultTabController?

    我正在尝试使用DefaultTabController在一些小部件的中间 所以我的TabBar不能在AppBar并且必须关闭一些小部件 所以我的问题是当我使用时TabBarView它崩溃了 这是一个 Flutter 示例的示例 但没有找到如
  • Xcode 4 调试器代码完成

    首先 很高兴他们尝试在 Xcode 4 中的 gdb 命令提示符上完成代码 但在当前状态下 它使得使用命令提示符来调查目标 c 对象几乎不可能 当我打字时 它自动将单词补全为我不想要的内容 并且如果不手动选择文本并将其删除 然后重新开始 则
  • Monodroid 示例/带有源代码的小部件

    我是一名 NET 开发人员 我对用 C 开发 Android 应用程序感兴趣 并且我得到了 monodroid 是否有任何来源可以让我获得 monodroid 示例应用程序 带有源代码 这将帮助我在 monodroid 中开发应用程序 或者
  • 有没有办法在 Next.js 的 getStaticProps 中使用 redux 工具包?

    我使用时获取数据useEffect代替getStaticProps 但在getStaticProps它表明钩子只能在功能组件中使用 import Head from next head import Image from next imag
  • 使用 jQuery 将光标位置处的文本插入到 CKEditor

    我正在尝试使用 jQuery 将一段文本添加到现有的 CKEditor 单击链接时需要完成此操作 我尝试了这个解决方案 它适用于常规文本区域 但不适用于 CKEditor jQuery fn extend insertAtCaret fun
  • 如何向 Android Studio 中的现有项目添加新活动?

    在 Eclipse 中 您只需单击 新建 按钮并选择 Android 活动即可添加新活动 但 Android Studio 有点不同 我无法找到如何向项目添加新活动 要添加一个Activity使用 Android Studio 此步骤与添加
  • “此应用程序已请求运行时以异常方式终止它”的原因是什么?

    Visual C 运行时抛出一个常见错误 此应用程序已请求运行时以异常方式终止它 请联系应用程序的支持团队以获取更多信息 该错误消息实际上是什么意思mean 让我用一个比喻来准确地解释我的问题 如果我看到一条消息 异常 访问冲突 0xc00
  • C++0x 可变参数模板按引用传递

    我想为我的应用程序使用可变参数模板功能 但我不希望对象按值传递 因为在我的情况下对象非常复杂 我想通过引用传递它们 而不是作为指针 void func template
  • 在 MATLAB 中模拟 C++ 模板

    我试图找出创建 C 模板或 Java 通用对象的替代方案的最佳方法 出于多种不同的原因 我过去曾多次想这样做 但现在我想做的是为几个相关的类创建 saveobj 和 loadobj 函数 我的想法是 我想要一组通用的例程来创建默认结构 然后
  • 谷歌电子表格中的“MMMM yy”日期

    我有一个谷歌电子表格 其中我想要一个仅包含月份和年份名称的日期 例如September 2011 而且我还希望月份和年份能够轻松更改 有没有办法获得自定义日期格式来做到这一点 我发现我可以这样做 TEXT 40295 MMMM yy 但是日
  • 确定比特币钱包地址是否“有效”

    我知道可以使用正则表达式验证比特币钱包地址 13 a km zA HJ NP Z0 9 26 33 但这并不是 100 准确 并且允许将无效地址检测为有效地址 是否有公开的 C 算法可以验证比特币钱包地址 我一直在谷歌搜索 但找不到任何东西
  • Cordova + android:无法从应用程序打开拨号盘或邮件意图

    我有一个奇怪的问题 我无法从应用程序中打开带有预定义号码或邮件意图的拨号盘 我正在使用 netbeans 8 0 1 创建 cordova 应用程序 我的 Cordova 版本是 4 0 0 我按照步骤创建了一个应用程序 并选择了 Hell
  • java inputstream 打印控制台内容

    sock new Socket www google com 80 out new BufferedOutputStream sock getOutputStream in new BufferedInputStream sock getI
  • RecyclerView 在聊天屏幕中的 notificationDataSetChanged 上滚动到顶部

    我正在尝试使用 recyclerView 创建消息传递类型的屏幕 该屏幕将从底部开始 并在用户到达聊天顶端时加载更多数据 但我面临着这个奇怪的问题 我的 recyclerView 在调用 notificationDataSetChanged
  • 1分30秒倒计时器javascript

    我有代码 但它适用于 2 分钟计时器 我需要将其修改为 1 分 30 秒计时器 我已经尝试过 但未能从 1 30 开始计时器 因为我是这一行的初学者 并且想学习如何做到这一点 这是代码 div div
  • FastAPI - 在 swagger 中添加路径参数的描述

    想象一下有一个这样的应用程序 from fastapi import FastAPI app FastAPI app get items item id async def read item item id int return item
  • ASP.NET Core 授权权限访问文件夹与Identity Server

    在我的 ASP NET Core 项目中 我与 Identity Server 集成 因此 用户必须登录 Identity Server 然后才能访问该应用程序 设计部门给了我一些 HTML5 静态页面来发布 但只有经过身份验证的人或具有特
  • 如何在 VSCode 中创建自定义对话框?

    我正在开发 VSCode 的扩展 我想显示一个自定义对话框来帮助用户配置 ini 文件 是否可以创建带有标签和输入的自定义对话框 您无法创建新的 UI 元素 但如果您想从用户那里获取输入 您可以使用如下代码 let options Inpu
  • Relay/ICommand 与 DelegateCommand——差异

    据我所知 下面的代码可以从 Relay ICommand 命令更改为 Delegate 命令 并且仍然以相同的方式绑定命令 如果我错了 它们的区别和用途是什么 private DelegateCommand something public
  • “功能性”Rust 对性能有哪些影响?

    我正在关注 Rust 轨道运动 io https exercism io 我有相当多的 C C 经验 我喜欢 Rust 的 功能 元素 但我担心相对性能 我解决了 行程编码 问题 https exercism io tracks rust