我用 Rust 重写网站,性能居然提升了 18 倍

摘要:对于构建中小型网站和个人博客来说,Hakyll 是一个不错的静态网站生成器库,9 年前的 Jonas Hietala 正是选择了 Hakyll 编写博客网站。但随着时间的推移,网站出现各种问题,考虑多种因素之后,Jonas Hietala 决定用 Rust 重写,看看他是怎么实现吧!

原文链接:https://www.jonashietala.se/blog/2022/08/29/rewriting_my_blog_in_rust_for_fun_and_profit/

声明:本文为 CSDN 翻译,未经授权,禁止转载

作者 | Jonas Hietala

译者 | 弯月

出品 | CSDN(ID:CSDNnews)

我使用Hakyll编写我的静态网站已经有9年了。在此之前,我使用过Jekyll,还使用过Perl的Mojolicious和PHP的Kohana编写更动态的页面。

但这些已成为过去,最近我决定使用Rust重新编写我的网站。

需要解决的难题

我想通过这次重写网站,重点解决以下难题:

1.速度越来越慢

在我的笔记本电脑上,重构一次网站需要75秒(不是编译,只是生成网站)。我的网站只有240个帖子,所以我认为不至于如此之慢。虽然我可以使用缓存系统,并在编辑期间通过watch命令查看更新后的帖子,但这个速度仍然是我无法接受的。

2.许多外部依赖项

虽然站点生成器本身是用Haskell编写的,但除了许多Haskell库之外,还有一些依赖项。我的博客助手脚本是用Perl编写的,我使用sassc转换sass,然后利用Python的pygments高亮显示语法,并使用s3cmd将生成的站点上传到S3。

安装所有这些工具并保持最新状态是一件很麻烦的事情。我希望能有一个统一的工具解决所有问题。

3.设置问题

有时,网站会出现一些问题,我必须花时间调试和修复。每当我有一些新的写作思路,却发现网站生成器有问题,就会觉得很沮丧。

你可能会想,什么地方会出问题?

有时,一些软件包的更新可能会破坏网站。例如,

GHC更新后,就找不到cabal包了。在运行Haskell可执行文件时,遇到了以下错误:[ERROR] Prelude.read: no parse(只有我的台式机会遇到这个错误,在我的笔记本电脑上运行良好。)

还有如下Perl的错误:Magic.c: loadable library and perl binaries are mismatched (got handshake key 0xcd00080, needed 0xeb00080)(只有我的笔记本电脑会遇到这个错误,这我的台式机上运行良好。)

如果Hakyll升级时会修改Pandoc参数,那么就会破坏Atom feed中渲染的代码。我知道上述问题都可以得到解决,但我只想要一些可以正常工作的东西。

4.Haskell的精神开销

我很喜欢Haskell,尤其是纯函数的部分,而且我非常喜欢Hakyll通过声明式的方法处理站点配置。以生成静态页面(即单独的页面)为例:

match "static/*.markdown" $ do

route staticRoute

compile $ pandocCompiler streams

>>= loadAndApplyTemplate "templates/static.html" siteCtx

>>= loadAndApplyTemplate "templates/site.html" siteCtx

>>= deIndexUrls即使你不理解 $ 和 >>=,也可以看懂我们想从static/文件夹中查找文件,然后将它们发送到pandocCompiler(以转换markdown代码)和模板,并删除URL中的index(避免链接以index.html结尾)。

简单明了!

但是我已经很多年没有使用过Haskell了,而且我不想在网站上添加复杂的东西。

例如,我曾想在帖子中添加链接“下一个/上一个”,但后来我不得不花时间重新学习Haskell和Hakyll。即便如此,最后我想到的解决方案还是超级慢,因为我采用了一个线性搜索来查找下一个/上一个帖子,但我未能搞清楚如何正确地利用Hakyll实现这个链接。

我相信你有更好的方法,但对我来说,这样的小功能不值得付出这么大的努力。

为什么选择Rust?

1.我很喜欢Rust,而且它很适合业余项目

2.Rust非常高效,非常擅长转换文本。

3.Cargo很流行,只要安装了Rust,运行cargo build,就可以构建网站

为什么要重新发明轮子?

我想编写一个静态站点生成器,这是一个非常有趣的项目,难度应该不大,但可以让我全权控制网站,而且灵活性完胜我使用过的所有网站生成器。

实现细节

我不打算在此介绍所有的细节,如果你感兴趣,请查看源代码(地址:https://github.com/treeman/jonashietala)。

利用现有的库处理难度较大的工作

起初我很担心重新实现我喜欢的Hakyll功能的难度会太大,例如模板引擎、多种语言的语法高亮显示,以及通过watch命令自动重新生成编辑过的页面并充当文件服务器,这样我就可以在写作的过程中随时在浏览器中查看帖子。

事实证明,我可以利用现有的库处理一些难度较大的工作。以下是我使用的一些效果很好的库:

tera:模板引擎。它比Hakyll更强大,甚至可以执行循环之类的操作:

<div class="post-footer">

<nav class="tag-links">

Posted in {% for tag in tags %}{% if loop.index0 > 0 %}, {% endif %}<a href="{{ tag.href }}">{{ tag.name }}</a>{% endfor %}.

</nav>

</div>pulldown-cmark:解析Markdown。这个库非常适合CommonMark(Markdown标准的语法规范)。

虽然速度很快,但支持的功能不如Pandoc多,所以我不得不做一些扩展。

syntect:语法高亮显示,支持Sublime Text的语法。yaml-front-matter:解析帖子中的元数据。grass:Rust中的Sass编译器。axum:创建本地托管站点的静态文件服务器。hotwatch:监视文件变更,在文件更新时更新页面。scraper:解析生成的HTML。我的一些测试和特定的转换中用到了这个库。rust-s3:将生成的站点上传到 S3 存储。即便使用了这些库,Rust源代码本身也超过了6000行。在有些情况下,Rust代码可能会很冗长,我的代码确实不够漂亮,但是最后的代码量仍然超过了预期。

Markdown转换

如果我的帖子都采用标准的markdown,这个转换过程会更加容易,但多年来我添加了很多pulldown-cmark不支持的功能和扩展。所以,我不得不自己编写代码。

预处理

我有一个预处理步骤,利用多个图像来生成插图。这是一个通用的处理步骤,形式如下:

::: <type>

<content>

:::我使用这个预处理来生成各种图像集合,例如Flex、Figure和Gallery。下面是一个例子:

::: Flex

/images/img1.png

/images/img2.png

/images/img3.png

Figcaption goes here

:::需要转换成:

<figure class="flex-33">

<img src="/images/img1.png" />

<img src="/images/img2.png" />

<img src="/images/img3.png" />

<figcaption>Figcaption goes here</figcaption>

</figure>那么,我该如何实现呢?当然是使用正则表达式。

use lazy_static::lazy_static;

use regex::{Captures, Regex};

use std::borrow::Cow;

lazy_static! {

static ref BLOCK: Regex = Regex::new(

r#"(?xsm)

^

# Opening :::

:{3}

s+

# Parsing id type

(?P<id>w+)

s*

$

# Content inside

(?P<content>.+?)

# Ending :::

^:::$

"#

)

.unwrap;

}

pub fn parse_fenced_blocks(s: &str) -> Cow<str> {

BLOCK.replace_all(s, |caps: &Captures| -> String {

parse_block(

caps.name("id").unwrap.as_str,

caps.name("content").unwrap.as_str,

)

})

}

fn parse_block(id: &str, content: &str) -> String {

...

}(实际的图像解析会更加繁琐,不在此赘述。)

扩展pulldown-cmark

此外,我还扩展了pulldown-cmark:

// Issue a warning during the build process if any markdown link is broken.

let transformed = Parser::new_with_broken_link_callback(s, Options::all, Some(&mut cb));

// Demote headers (eg h1 -> h2), give them an "id" and an "a" tag.

let transformed = TransformHeaders::new(transformed);

// Convert standalone images to figures.

let transformed = AutoFigures::new(transformed);

// Embed raw youtube links using iframes.

let transformed = EmbedYoutube::new(transformed);

// Syntax highlighting.

let transformed = CodeBlockSyntaxHighlight::new(transformed);

let transformed = InlineCodeSyntaxHighlight::new(transformed);

// Parse `{ :attr }` attributes for blockquotes, to generate asides for instance.

let transformed = QuoteAttrs::new(transformed);

// parse `{ .class }` attributes for tables, to allow styling for tables.

let transformed = TableAttrs::new(transformed);以前我喜欢降低标题等级,并嵌入原始YouTube链接,而且实现起来很简单(不过,在处理前后的步骤中嵌入YouTube链接可能会更好)。

Pandoc支持向任意元素添加属性和类,例如:

!(/images/img1.png){ height=100 }可以转换成:

<figure>

<img src="/images/img1.png" height="100">

</figure>这种用法在我的代码中比比皆是,所以我决定重新实现。

我在Pandoc中使用的功能还有一个不受支持:在HTML标签内计算markdown。比如下面这段代码就无法正确呈现:

<aside>

My [link][link_ref]

</aside>起初我的计划是,在通用预处理中实现,但后来发现这样做会丢失链接引用。比如下面这个例子:

::: Aside

My [link][link_ref]

:::

[link_ref]: /some/pathlink不会变成链接,因为我们只会在 ::: 内解析。

> Some text

{ :notice }这段代码会调用notice解析器,在这个示例中,它将创建一个<aside>标记(而不是 <blockquote>标记),同时保留已解析的markdown。

虽然现有的crate使用syntect高亮显示代码,但我自己编写了一个crate,可以将它包装在<code>标记中,并支持内联代码高亮显示。例如:

Inside row: `let x = 2;`rust可以转换成:

Inside row: let x = 2;性能提升

我没有在提高性能上花费太多时间,但以下两方面的改动对性能产生了很大影响:

第一,如果使用syntect,并且使用自定义的语法,则应该将SyntaxSet压缩为二进制格式。

第二,使用rayon并行化渲染。渲染是解析Markdown、应用模板以及创建输出文件的过程。rayon非常适合该任务,因为这项任务的瓶颈是CPU,而且rayon非常易于使用(如果代码结构正确)。例如,下面是一个简化的渲染:

fn render(&self) -> Result<> {

let mut items = Vec::new;

// Add posts, archives, and all other files that should be generated here.

for post in &self.content.posts {

items.push(post.as_ref);

}

// Render all items.

items

.iter

.try_for_each(|item| self.render_item(*item))

}如果想并行化上述处理,我们只需要将iter改为par_iter:

use rayon::iter::{IntoParallelRefIterator, ParallelIterator};

items

.par_iter // This line

.try_for_each(|item| self.render_item(*item))只需要修改这一个地方!

诚然,性能上的提升非常小,几处巨大的性能提升都来自我使用的库。例如,我的旧网站使用了一个用Python编写的外部pygments来高亮显示语法,而现在我使用了Rust的高亮显示,它的速度要快得多,并且很容易并行化。

完整性检查

以前,我的网站给我的最大困扰是,太容易出错了。例如,链接到不存在的页面或图像,忘记定义链接引用,或者忘记在发布之前更新链接。

因此,除了测试watch命令之类的基本功能之外,我还需要解析整个站点,并检查所有内部链接是否存在以及是否正确。此外,我还需要手动检查外部链接。

最终的结果

我在本文开头提到了一些难题,下面我们来看看我是否解决了这些难题。

1. 性能

如今在我的笔记本电脑上,重建完整的站点需要4秒(不包括编译时间)。18倍的性能提升看起来不错嘛。我相信,网站的性能还有进一步提升的空间,例如使用rayon处理文件I/O,使用异步处理,而且我没有缓存系统,所以每次构建都会重新生成所有文件。

请注意,这并不是说Rust的速度远超Haskell,这不过是两种实现的比较。我相信有人能够使用Haskell编写出更快的实现。

2. 单一依赖

现在网站的一切都是用Rust编写的,不需要安装外部脚本或工具。

3. Cargo正常工作

只要系统安装了Rust,cargo build就可以正常工作。我认为这是Rust最大的优势之一:构建系统可以正常工作。

你不必手动寻找丢失的依赖项,为了实现跨平台而苦恼,或者在构建系统自动拉取更新时破坏一切。你只需要静静等待代码编译完成。

4. Rust减轻了我的负担

虽然我找到了更简单的方法来实现上一个/下一个链接,但我并不认为这意味着Rust比Haskell更简单或更容易,这只是意味着Rust对我个人来说更容易理解。

最大的原因还是要归结为实践。最近我一直在使用Rust,但对于Haskell,我只在大约十年前构建这个网站时学习过一段时间,之后再也没有接触过。

我敢肯定,如果停止使用Rust,过个十年再次接触,我也会遇到很多困难。

总的来说,我很满意这次重写网站的结果。这是一个有趣的项目,尽管工作量超出了我的预期,但的确解决了我的一些烦恼。

原文地址:https://www.toutiao.com/article/7138693578934452751/

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


所以很多人都会选择将这些生活琐事来交给智能化产品,在众多产品中,扫拖机器人所给我们带来的便利性最强,扫地、拖地全都一气呵成,不需要人为过多干预,不过目前大多数扫拖机器人对于双手解放得不彻底。而石头作为
“昨天的经历都将成为明天的力量。” 将绝大部分精力都奉献给璃月港的刻晴,可以说是《原神》中的高人气角色了,虽然是常驻角色,并且对于普通玩家来说……刻师傅别刮了……不过作为开服就存在的角色,还有独特的剑法
最近,全球知名的通信产业盛会MWC 2024(2024世界移动通信大会)正式召开,其中,联发科以“连接AI宇宙”(Connecting the AI-verse)为主题,为大众展示出一系列在AI和移动通信技术等领域的最新突破,吸引了大量行业
今年上半年有很多值得关注的机型,其中华为最新的影像旗舰华为P70 Art也自然受到了业界不少的关注目光,目前关于这款机型的轮廓图已经在网上曝光。
目前,2024世界移动通信大会(MWC)正在西班牙巴塞罗那举行,值得一提的是,此次大会参展中国厂商非常多,包括华为、中兴、小米、荣耀等等多家厂商均在其列。
就在去年,真我推出了11 Pro+,用一个2亿像素传感器和zoom变焦功能,开启了中端手机影像的长焦大战,而后友商才姗姗来迟的跟进了2亿像素传感器。
【手机之家新闻】一年一度的MWC已经于当地时间2月26日在巴塞罗那正式开展,在本次MWC2024上全球各大厂商齐聚一堂,展出自家最新的技术与产品,其中中兴就参展本次MWC2024,并且展出了诸多面向企业端的产品,而旗下的
近日,联发科在MWC 2024(2024 世界移动通信大会)上展出了一系列令人瞩目的AI和移动通信技术突破,以“连接AI宇宙”(Connecting the AI-verse)的展厅吸引了无数业界精英和媒体的目光。特别是其现场的生成式AI技术
虽然目前国内已经有不少厂商入局折叠屏产品,但是努比亚却迟迟没有入局。不过在近日举办的MWC 2024展会上,努比亚发布了自家首款折叠屏手机——努比亚Flip,预计国内很快也会上市。
MWC 2024正在西班牙巴塞罗那举办,和往年一样,荣耀这次依旧携众多新产品、新技术参会。荣耀Magic6 Pro、荣耀Magic V2 RSR保时捷设计的机型在海外正式发布,并且还展示了魔法大模型、任意门等诸多新技术。
MWC 2024正在西班牙巴塞罗那如火如荼地举行,其中小米也参加了今年的大会,在会上发布了在国内大受欢迎的小尺寸旗舰——小米14。值得一提的是,高通公司CEO安蒙甚至亲临发布会现场为这款机型助阵。
《原神》是一直以来在机圈深受欢迎的游戏,在充满幻想的提瓦特大陆上,你可以邂逅不少性格迥异、能力独特的伙伴。而一加Ace系列一直就拥有非常强烈的电竞属性,也是畅玩《原神》的热门机型,而在本月,一加Ace 3将推
有不少网友发现,今年新机的发布时间相对于往年大幅提前,很多厂商在春节之前密集发布了自己最新的中高端机型,给人一种年后没什么新机可发了的感觉。不过魅族全新的大杯机型——魅族21 PRO非常值得期待,魅族科技也
2022年7月,小米12S Ultra正式发布,这款产品率先将1英寸大底主摄引入到移动影像领域,同时凭借鲜明的徕卡影调给人留下深刻的印象,同时这款产品也被视为了影像旗舰地位的机型。如果从那时算起,到现在差不多已经快过
随着智能手机的日益普及和智能化进程的加速,智能穿戴设备成为了人们关注的焦点。各大智能手机厂商纷纷进军智能穿戴市场,试图在这一新兴领域抢占先机。
早在去年秋天,HyperOS操作系统发布的时候,小米便勾勒出了“人车家全生态”的美好蓝图,而在这其中,小米的多终端统一战略是核心,目前已经有不少小米产品预装或者接受到了HyperOS操作系统的推送,在过去几个月的时
今年雷军将把更多的精力放在小米汽车上,所以接下来的手机业务将由刚刚兼任小米品牌总经理卢伟冰接管。同时雷军也在微博上表示小米2024年开年旗舰——小米14 Ultra即将在近期发布,并且将有卢伟冰进行讲解。另外,卢
新的一年有龙则灵,有愿必达。自1月19日起,荣耀加码“新年荣耀,一起成龙”年货节,在全国荣耀线下门店上线了“新年许愿处”、“龙运当头”等趣味活动,吸引大批消费者到店打卡许愿,戴龙头迎好运。与此同时,为了回
小米在官网微博中已经透露了关于小米14 Ultra信息,所以新机上市应该不会太晚。根据德国莱茵的官方消息,目前小米14 Ultra(型号为24030PN60G)获得了莱茵无频闪认证,表明这款手机可以有效减轻屏幕给用户带来的视觉疲
2月22日,上海广播电视台与华为举办鸿蒙合作签约仪式,宣布其官方客户端看看新闻APP将基于HarmonyOS NEXT鸿蒙星河版启动鸿蒙原生应用开发,为用户提供更加极致的新闻资讯服务体验。此次合作标志着上海广播电视台成为全国