黄金容易生锈吗_生锈的单元测试和基准

黄金容易生锈吗

For a couple months now, we’ve focused on some specific libraries you can use in Rust for web development. But we shouldn’t lose sight of some other core language skills and mechanics. Whenever you write code, you should be able to show first that it works, and second that it works efficiently. If you’re going to build a larger Rust app, you should also know a bit about unit testing and benchmarking. This week, we’ll take a couple simple sorting algorithms as our examples to learn these skills.

几个月来,我们一直专注于一些可以在Rust中进行Web开发的特定库。 但是,我们不应忽视其他一些核心语言技能和机制。 无论何时编写代码,您都应该能够首先证明其有效,其次才有效。 如果您要构建更大的Rust应用程序,则还应该对单元测试和基准测试有所了解。 本周,我们将以几个简单的排序算法为例来学习这些技能。

As always, you can take a look at the code for this article on our Github Repo for the series. You can find this week’s code specifically in sorters.rs! For a more basic introduction to Rust, be sure to check out our Rust Beginners Series!

与往常一样,您可以在我们的Github Repo系列上查看本文的代码。 您可以在sorters.rs找到本周的代码! 有关Rust的更基本介绍,请务必查看我们的Rust初学者系列

插入排序 (Insertion Sort)

We’ll start out this article by implementing insertion sort. This is one of the simpler sorting algorithms, which is rather inefficient. We’ll perform this sort “in place”. This means our function won’t return a value. Rather, we’ll pass a mutable reference to our vector so we can manipulate its items. To help out, we’ll also define a swap function to change two elements around that same reference:

我们将从实现插入排序开始本文。 这是较简单的排序算法之一,效率很低。 我们将“就地”执行这种排序。 这意味着我们的函数不会返回值。 相反,我们将对我们的向量传递一个可变的引用,以便我们可以操纵它的项。 为了提供帮助,我们还将定义一个swap函数来更改同一引用周围的两个元素:

pub fn swap(numbers: &mut Vec<i32>, i: usize, j: usize) {
let temp = numbers[i];
numbers[i] = numbers[j];
numbers[j] = temp;
}pub fn insertion_sorter(numbers: &mut Vec<i32>) {
...
}

At its core, insertion sort is a pretty simple algorithm. We maintain the invariant that the “left” part of the array is always sorted. (At the start, with only 1 element, this is clearly true). Then we loop through the array and “absorb” the next element into our sorted part. To absorb the element, we’ll loop backwards through our sorted portion. Each time we find a larger element, we switch their places. When we finally encounter a smaller element, we know the left side is once again sorted.

从本质上讲,插入排序是一种非常简单的算法。 我们保持不变,即数组的“左”部分总是被排序。 (开始时只有1个元素,这显然是正确的)。 然后,我们遍历数组并将下一个元素“吸收”到排序的部分中。 为了吸收元素,我们将在排序后的部分中向后循环。 每次找到更大的元素时,我们都会切换位置。 当我们最终遇到一个较小的元素时,我们知道左侧再次被排序。

pub fn insertion_sorter(numbers: &mut Vec<i32>) {
for i in 1..numbers.len() {
let mut j = i;
while j > 0 && numbers[j-1] > numbers[j] {
swap(numbers, j, j - 1);
j = j - 1;
}
}
}

测试中 (Testing)

Our algorithm is simple enough. But how do we know it works? The obvious answer is to write some unit tests for it. Rust is actually a bit different from Haskell and most other languages in the canonical approach to unit tests. Most of the time, you’ll make a separate test directory. But Rust encourages you to write unit tests in the same file as the function definition. We do this by having a section at the bottom of our file specifically for tests. We delineate a test function with the testmacro:

我们的算法很简单。 但是我们怎么知道它有效呢? 显而易见的答案是为此编写一些单元测试。 在单元测试的规范方法上,Rust实际上与Haskell和大多数其他语言有点不同。 大多数时候,您将创建一个单独的测试目录。 但是Rust鼓励您在与函数定义相同的文件中编写单元测试。 为此,我们在文件底部有一个专门用于测试的部分。 我们用test宏来描述一个测试函数:

[#test]
fn test_insertion_sort() {
...
}

To keep things simple, we’ll define a random vector of 100 integers and pass it to our function. We’ll use assert to verify that each number is smaller than the next one after it.

为了简单起见,我们将定义一个100个整数的随机向量,并将其传递给函数。 我们将使用assert来验证每个数字是否小于其后的下一个数字。

#[test]
fn test_insertion_sort() {
let mut numbers: Vec<i32> = random_vector(100);
insertion_sorter(&mut numbers);
for i in 0..(numbers.len() - 1) {
assert!(numbers[i] <= numbers[i + 1]);
}
}

When we run the cargo test command, Cargo will automatically detect that we have a test suite in this file and run it.

当我们运行cargo test命令时,Cargo将自动检测到此文件中包含测试套件并运行它。

running 1 test...
test sorter::test_insertion_sort ... ok

标杆管理 (Benchmarking)

So we know our code works, but how quickly does it work? When you want to check the performance of your code, you need to establish benchmarks. These are like test suites except that they’re meant to give out the average time it takes to perform a task.

因此我们知道我们的代码可以工作,但是它能工作多快呢? 当您要检查代码的性能时,需要建立基准 。 这些测试套件类似于测试套件,只是它们旨在给出执行任务所需的平均时间。

Just as we had a test macro for making test suites, we can use the bench macro for benchmarks. Each of these takes a mutable Bencher object as an argument. To record some code, we'll call iter on that object and pass a closure that will run our function.

正如我们有一个用于制作测试套件的test宏一样,我们可以将bench宏用于基准测试。 每个对象都将一个可变的Bencher对象作为参数。 为了记录一些代码,我们将在该对象上调用iter并传递一个将运行我们的函数的闭包。

#[bench]
fn bench_insertion_sort_100_ints(b: &mut Bencher) {
b.iter(|| {
let mut numbers: Vec<i32> = random_vector(100);
insertion_sorter(&mut numbers)
});
}

We can then run the benchmark with cargo bench.

然后,我们可以在cargo bench运行基准测试。

running 2 tests
test sorter::test_insertion_sort ... ignored
test sorter::bench_insertion_sort_100_ints ... bench: 6,537 ns
/iter (+/- 1,541)

So on average, it took about 6ms to sort 100 numbers. On its own, this number doesn’t tell us much. But we can get a more clear idea for the runtime of our algorithm by looking at benchmarks of different sizes. Suppose we make lists of 1000 and 10000:

因此,平均来说,排序100个数字大约需要6毫秒。 单靠这个数字并不能告诉我们太多。 但是,通过查看不同大小的基准,我们可以对算法的运行时间有一个更清晰的认识。 假设我们列出了1000和10000:

#[bench]
fn bench_insertion_sort_1000_ints(b: &mut Bencher) {
b.iter(|| {
let mut numbers: Vec<i32> = random_vector(1000);
insertion_sorter(&mut numbers)
});
}#[bench]
fn bench_insertion_sort_10000_ints(b: &mut Bencher) {
b.iter(|| {
let mut numbers: Vec<i32> = random_vector(10000);
insertion_sorter(&mut numbers)
});
}

Now when we run the benchmark, we can compare the results of these different runs:

现在,当我们运行基准测试时,我们可以比较这些不同运行的结果:

running 4 tests
test sorter::test_insertion_sort ... ignored
test sorter::bench_insertion_sort_10000_ints ... bench: 65,716,130 ns
/iter (+/- 11,193,188)
test sorter::bench_insertion_sort_1000_ints ... bench: 612,373 ns
/iter (+/- 124,732)
test sorter::bench_insertion_sort_100_ints ... bench: 12,032 ns
/iter (+/- 904)

We see that when we increase the problem size by a factor of 10, we increase the runtime by a factor of nearly 100! This confirms for us that our simple insertion sort has an asymptotic runtime of O(n^2), which is not very good.

我们看到,当我们将问题大小增加10倍时,运行时间也增加了将近100倍! 这为我们确认了我们的简单插入排序具有O(n^2)的渐近运行时,这不是很好。

快速排序 (Quick Sort)

There are many ways to sort more efficiently! Let’s try our hand at quicksort. For this algorithm, we first “partition” our array. We’ll choose a pivot value, and then move all the numbers smaller than the pivot to the left of the array, and all the greater numbers to the right. The upshot is that we know our pivot element is now in the correct final spot!

有很多方法可以更有效地排序! 让我们尝试一下quicksort 。 对于此算法,我们首先对数组进行“分区”。 我们将选择一个枢轴值,然后将所有小于枢轴的数字移到数组的左侧,将所有较大的数字移到右侧。 结果是我们知道我们的枢轴元素现在处于正确的最终位置!

Here’s what the partition algorithm looks like. It works on a specific sub-segment of our vector, indicated by start and end. We initially move the pivot element to the back, and then loop through the other elements of the array. The i index tracks where our pivot will end up. Each time we encounter a smaller number, we increment it. At the very end we swap our pivot element back into its place, and return its final index.

这是分区算法的样子。 它适用于向量的特定子段,由startend指示。 我们首先将枢轴元素移到后面,然后遍历数组的其他元素。 i索引跟踪我们的枢轴将在哪里结束。 每次遇到较小的数字,我们都会增加它。 最后,我们将枢轴元素交换回其位置,并返回其最终索引。

pub fn partition(
numbers: &mut Vec<i32>,
start: usize,
end: usize,
partition: usize)
-> usize {
let pivot_element = numbers[partition];
swap(numbers, partition, end - 1);
let mut i = start;
for j in start..(end - 1) {
if numbers[j] < pivot_element {
swap(numbers, i, j);
i = i + 1;
}
}
swap(numbers, i, end - 1);
i
}

So to finish sorting, we’ll set up a recursive helper that, again, functions on a sub-segment of the array. We’ll choose a random element and partition by it:

因此,要完成排序,我们将设置一个递归帮助器,该帮助器再次作用于数组的子段。 我们将选择一个随机元素并对其进行分区:

pub fn quick_sorter_helper(
numbers: &mut Vec<i32>, start: usize, end: usize) {
if start >= end {
return;
} let mut rng = thread_rng();
let initial_partition = rng.gen_range(start, end);
let partition_index =
partition(numbers, start, end, initial_partition);
...
}

Now that we’ve partitioned, all that’s left to do is recursively sort each side of the partition! Our main API function will call this helper with the full size of the array.

现在我们已经分区了,剩下要做的就是递归地对分区的每一面进行排序! 我们的主要API函数将使用数组的完整大小调用此帮助器。

pub fn quick_sorter_helper(
numbers: &mut Vec<i32>, start: usize, end: usize) {
if start >= end {
return;
} let mut rng = thread_rng();
let initial_partition = rng.gen_range(start, end);
let partition_index =
partition(numbers, start, end, initial_partition);
quick_sorter_helper(numbers, start, partition_index);
quick_sorter_helper(numbers, partition_index + 1, end);
}pub fn quick_sorter(numbers: &mut Vec<i32>) {
quick_sorter_helper(numbers, 0, numbers.len());
}

Now that we’ve got this function, let’s add tests and benchmarks for it:

现在我们有了此功能,让我们为其添加测试和基准:

#[test]
fn test_quick_sort() {
let mut numbers: Vec<i32> = random_vector(100);
quick_sorter(&mut numbers);
for i in 0..(numbers.len() - 1) {
assert!(numbers[i] <= numbers[i + 1]);
}
}#[bench]
fn bench_quick_sort_100_ints(b: &mut Bencher) {
b.iter(|| {
let mut numbers: Vec<i32> = random_vector(100);
quick_sorter(&mut numbers)
});
}// Same kind of benchmarks for 1000, 10000, 100000

Then we can run our benchmarks and see our results:

然后,我们可以运行基准测试并查看结果:

running 9 tests
test sorter::test_insertion_sort ... ignored
test sorter::test_quick_sort ... ignored
test sorter::bench_insertion_sort_10000_ints ... bench: 65,130,880 ns
/iter (+/- 49,548,187)
test sorter::bench_insertion_sort_1000_ints ... bench: 312,300 ns
/iter (+/- 243,337)
test sorter::bench_insertion_sort_100_ints ... bench: 6,159 ns
/iter (+/- 4,139)
test sorter::bench_quick_sort_100000_ints ... bench: 14,292,660 ns
/iter (+/- 5,815,870)
test sorter::bench_quick_sort_10000_ints ... bench: 1,263,985 ns
/iter (+/- 622,788)
test sorter::bench_quick_sort_1000_ints ... bench: 105,443 ns
/iter (+/- 65,812)
test sorter::bench_quick_sort_100_ints ... bench: 9,259 ns
/iter (+/- 3,882)

Quicksort does much better on the larger values, as expected! We can discern that the times seem to only go up by a factor of around 10. It’s difficult to determine that the true runtime is actually O(n log n). But we can clearly see that we're much closer to linear time!

正如预期的那样,Quicksort在更大的值上表现更好。 我们可以看出时间似乎只增加了约10倍。很难确定真正的运行时间实际上是O(n log n) 。 但是我们可以清楚地看到,我们已经接近线性时间!

结论 (Conclusion)

That’s all for this intermediate series on Rust! Next week, we’ll summarize the skills we learned over the course of these couple months in Rust. Then we’ll look ahead to our next series of topics, including some totally new kinds of content!

这就是Rust上的这个中间系列! 下周,我们将总结在这几个月中在Rust中学习的技能。 然后,我们将展望下一系列主题,包括一些全新的内容!

Don’t forget! If you’ve never programmed in Rust before, our Rust Video Tutorial provides an in-depth introduction to the basics!

别忘了! 如果您以前从未在Rust中编程过,我们的Rust Video Tutorial将提供有关基础知识的深入介绍!

翻译自: https://medium.com/@james_32022/unit-tests-and-benchmarks-in-rust-f5de0a0ea19a

黄金容易生锈吗

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值