一行代码,为何使 24 核服务器比笔记本还慢
时间:2023-06-27 01:44 来源:网络整理 作者:墨客科技 点击:次
【编者按】想象一下,你编写了一个处理并行问题的程序,每个线程都独立执行其被分配的任务,除了在最后汇总结果外 , 线程之间不需要协同。显然,你会认为如果将该程序在更多核心上运行,运行速度会更快。你首先在笔记本电脑上进行基准测试,发现它几乎能完美地利用所有的 4 个可用核心。然后你在更多核服务器上运行该程序,期待有更好的性能表现,却发现实际上比笔记本运行的还慢。太不可思议了! 原文链接:https://pkolaczk.github.io/server-slower-than-a-laptop/ 未经允许,禁止转载! 作者 | pkolaczk 译者 | 明明如月 责编 | 夏萌 出品 | CSDN(ID:CSDNnews) 我最近一直在改进一款 Cassandra 基准测试工具 Latte ,这可能是你能找到的 CPU 使用和内存使用都最高效的 Cassandra 基准测试工具。设计思路非常简单:编写一小部分代码生成数据,并且执行一系列异步的 CQL 语句向 Cassandra 发起请求。Latte 在循环中调用这段代码,并记录每次迭代花费的时间。最后,进行统计分析,并通过各种形式展示结果。 基准测试非常适合并行化。只要被测试的代码是无状态的,就很容易使用多个线程调用。我已经在《Benchmarking Apache Cassandra with Rust》和《Scalable Benchmarking with Rust Streams》中讨论过如何在 Rust 中实现此功能。 然而,当我写这些早期的博客文章时,Latte 几乎不支持定义工作负载,或者说它的能力非常有限。它只内置两个预设的工作负载,一个用于读取数据,另一个用于写入数据。你只能调整一些参数,比如列的数量和大小,没有什么高级的特性。它不支持二级索引,也无法自定义过滤条件。对于 CQL(Cassandra Query Language)文本的控制也受到限制。总而言之,它几乎没有任何过人之处。因此,在那个时候,Latte 更像是一个用于验证概念的工具,而不是一个真正可用于实际工作的通用工具。当然,你可以 fork Latte 的源代码,并使用 Rust 编写新的工作负载,然后重新编译。但谁想浪费时间去学习一个小众基准测试工具的内部实现呢 ? Rune 脚本 去年,为了能够测量 Cassandra 使用存储索引的性能,我决定将 Latte 与一个脚本引擎进行集成,这个引擎可以让我轻松地定义工作负载,而无需重新编译整个程序。在尝试将 CQL 语句嵌入 TOML 配置文件(效果非常不理想)后,我也尝试过在 Rust 中嵌入 Lua (在 C 语言中可能很好用,但在与 Rust 配合使用时,并不如我预期的那样顺畅,尽管勉强能用)。最终,我选择了一个类似于 sysbench 的设计,但使用了嵌入式的 Rune 解释器代替 Lua。 说服我采用 Rune 的主要优势是和 Rust 无缝集成以及支持异步代码。由于支持异步,用户可以直接在工作负载脚本中执行 CQL 语句,利用 Cassandra 驱动程序的异步性。此外,Rune 团队极其乐于助人,短时间内帮我扫清了所有障碍。 以下是一个完整的工作负载示例,用于测量通过随机键选择行时的性能 : const ROW_COUNT = latte::param! ( "rows", 100000 ) ; const KEYSPACE = "latte";const TABLE = "basic"; pub async fn schema ( ctx ) { ctx.execute ( `CREATE KEYSPACE IF NOT EXISTS ${KEYSPACE} WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }` ) .await?; ctx.execute ( `CREATE TABLE IF NOT EXISTS ${KEYSPACE}.${TABLE} ( id bigint PRIMARY KEY ) ` ) .await?;} pub async fn erase ( ctx ) { ctx.execute ( `TRUNCATE TABLE ${KEYSPACE}.${TABLE}` ) .await?;} pub async fn prepare ( ctx ) { ctx.load_cycle_count = ROW_COUNT; ctx.prepare ( "insert", `INSERT INTO ${KEYSPACE}.${TABLE} ( id ) VALUES ( :id ) ` ) .await?;ctx.prepare ( "select", `SELECT * FROM ${KEYSPACE}.${TABLE} WHERE id = :id` ) .await?;} pub async fn load ( ctx, i ) { ctx.execute_prepared ( "insert", [ i ] ) .await?;} pub async fn run ( ctx, i ) { ctx.execute_prepared ( "select", [ latte::hash ( i ) % ROW_COUNT ] ) .await?;} 如果你想进一步了解如何编写该脚本可以参考:README。 对基准测试程序进行基准测试 尽管脚本尚未编译为本机代码,但速度已可接受,而且由于它们通常包含的代码量有限,所以在性能分析的顶部并不会显示这些脚本。我通过实证发现,Rust-Rune FFI 的开销低于由 mlua 提供的 Rust-Lua,这可能是由于 mlua 使用的安全检查。 一开始, 为了评估基准测试循环的性能,我创建了一个空的脚本 : pub async fn run ( ctx, i ) {} 尽管函数体为空 , 但基准测试程序仍需要做一些工作来真正运行它 : 使用 buffer_unordered 调度 N 个并行的异步调用 为 Rune VM 设置新的本地状态(例如,栈) 从 Rust 一侧传入参数调用 Rune 函数 衡量每一个返回的 future 完成所花费的时间 收集日志,更新 HDR 直方图并计算其他统计数据 使用 Tokio 线程调度器在 M 个线程上运行代码 我老旧的 4 核 Intel Xeon E3-1505M v6 锁定在 3GHz 上,结果看起来还不错: |
