你可以信任由编译器优化的代码吗?( 三 )


// 450 millisecondsfn common_prefix(xs: &[u8], ys: &[u8]) -> usize {let chunk_size = 16;let mut result = 0;'outer: for (xs_chunk, ys_chunk) inzip(xs.chunks_exact(chunk_size), ys.chunks_exact(chunk_size)){for (x, y) in zip(xs_chunk, ys_chunk) {if x != y { break 'outer; }result += 1}}for (x, y) in zip(&xs[result..], &ys[result..]) {if x != y { break; }result += 1}result}【你可以信任由编译器优化的代码吗?】其实,上述代码在速度上的提升是远远不够的 。具体来说,SIMD需要以相同的方式,并行处理块中的所有值 。在上述代码中,我们用到了一个break 。这意味着第n对字节的处理,取决于第n-1对 。我们可以通过禁用短路(short-circuiting),来检查整个字节块是否匹配 。当然,我们并不关心具体哪个特定字节出现了不匹配:
// 80 millisecondsfn common_prefix3(xs: &[u8], ys: &[u8]) -> usize {let chunk_size = 16;let mut result = 0;for (xs_chunk, ys_chunk) inzip(xs.chunks_exact(chunk_size), ys.chunks_exact(chunk_size)){let mut chunk_equal: bool = true;for (x, y) in zip(xs_chunk, ys_chunk) {// NB: &, unlike &&, doesn't short-circuit.chunk_equal = chunk_equal & (x == y);}if !chunk_equal { break; }result += chunk_size;}for (x, y) in zip(&xs[result..], &ys[result..]) {if x != y { break; }result += 1}result}至此,矢量化已成功开始,而且几乎减少了一个数量级的运行时间 。我们现在可以使用迭代器来进行压缩了 。
// 80 millisecondsfn common_prefix5(xs: &[u8], ys: &[u8]) -> usize {let chunk_size = 16;let off =zip(xs.chunks_exact(chunk_size), ys.chunks_exact(chunk_size)).take_while(|(xs_chunk, ys_chunk)| xs_chunk == ys_chunk).count() * chunk_size;off + zip(&xs[off..], &ys[off..]).take_while(|(x, y)| x == y).count()}显然,此时的代码已与我们开始时有了显著不同 。可见,我们不应盲目依赖编译器的优化,而需要知道在何种情况下进行特定优化,以触发它们编写代码的方式 。例如,对于SIMD而言,我们需要根据处理元素块来表达算法 。而且在每个块中,我们应确保没有分支,让所有元素都能以相同的方式处理 。
原文链接:https://matklad.github.io/2023/04/09/can-you-trust-a-compiler-to-optimize-your-code.html
译者介绍陈峻 (Julian Chen),51CTO社区编辑,具有十多年的IT项目实施经验,善于对内外部资源与风险实施管控,专注传播网络与信息安全知识与经验 。




推荐阅读