TE
TechEcho
Home24h TopNewestBestAskShowJobs
GitHubTwitter
Home

TechEcho

A tech news platform built with Next.js, providing global tech news and discussions.

GitHubTwitter

Home

HomeNewestBestAskShowJobs

Resources

HackerNews APIOriginal HackerNewsNext.js

© 2025 TechEcho. All rights reserved.

Rust Performance Pitfalls

424 pointsby blacksmythealmost 8 years ago

7 comments

agwaalmost 8 years ago
&gt; To get rid of the checks, we can either use bytes directly (usually via Vec&lt;u8&gt; &#x2F; &amp;[u8]) or, if we are absolutely sure the input will be valid UTF-8, use str::from_utf8_unchecked(_) (note that this will require unsafe and break your code in surprising ways should the input not be valid UTF-8).<p>I believe this needs a stronger warning. Functions that operate on strings are allowed to assume that their input is valid UTF-8 and may perform out-of-bounds memory accesses if the input is not valid UTF-8. Therefore, creating a string containing invalid UTF-8 can lead to a memory safety vulnerability. The suggestion to use this function seems out-of-place in this article, which otherwise avoids suggesting unsafe code (e.g. indexing arrays without bounds checking).
评论 #14515292 未加载
评论 #14516743 未加载
Lercalmost 8 years ago
I have been doing some exploration of how well Rust optimizes Iterators and have been quite impressed.<p>Writing a iterator to provide the individual bits supplied by an iterator of bytes means you can count them with<p><pre><code> fn count_bits&lt;I : Iterator&lt;Item=bool&gt;&gt;(it : I) -&gt; i32{ let mut a=0; for i in it { if i {a+=1}; } return a; } </code></pre> Counting bits in an array of bytes would need something like this<p><pre><code> let p:[u8;6] = [1,2,54,2,3,6]; let result = count_bits(bits(p.iter().cloned())); </code></pre> Checking what that generates in asm <a href="https:&#x2F;&#x2F;godbolt.org&#x2F;g&#x2F;iTyfap" rel="nofollow">https:&#x2F;&#x2F;godbolt.org&#x2F;g&#x2F;iTyfap</a><p>The core of the code is<p><pre><code> .LBB0_4: mov esi, edx ;edx has the current mask of the bit we are looking at and esi, ecx ;ecx is the byte we are examining cmp esi, 1 ;check the bit to see if it is set (note using carry not zero flag) sbb eax, -1 ;fun way to conditionally add 1 .LBB0_1: shr edx ;shift mask to the next bit jne .LBB0_4 ;if mask still has a bit in it, go do the next bit otherwise continue to get the next byte cmp rbx, r12 ;r12 has the memory location of where we should stop. Are we there yet? je .LBB0_5 ; if we are there, jump out. we&#x27;re all done movzx ecx, byte ptr [rbx] ;get the next byte inc rbx ; advance the pointer mov edx, 128 ; set a new mask starting at the top bit jmp .LBB0_4 ; go get the next bit .LBB0_5: </code></pre> Apart from magical bit counting instructions this is close to what I would have written in asm mysef. That really impressed me. I&#x27;m still a little wary of hitting a performance cliff. I worry that I can easily add something that will mean the optimiser bails on the whole chain, but so far I&#x27;m trusting Rust more than I have trusted any other Optimiser.<p>If this produces simiarly nice code (I haven&#x27;t checked yet) I&#x27;ll be very happy<p><pre><code> for (dest,source) in self.buffer.iter_mut().zip(data) { *dest=source }</code></pre>
评论 #14517912 未加载
评论 #14522896 未加载
评论 #14518266 未加载
评论 #14517948 未加载
Analemma_almost 8 years ago
I&#x27;m not a compiler expert, but it seems like some of these should be unnecessary, especially with Rust&#x27;s strong knowledge of types and ownership lifespans. Like this example from the article:<p><pre><code> let nopes : Vec&lt;_&gt; = bleeps.iter().map(boop).collect(); let frungies : Vec&lt;_&gt; = nopes.iter().filter(|x| x &gt; MIN_THRESHOLD).collect(); </code></pre> where he recommends avoiding the first collect(). Can&#x27;t the optimizer do that for you if you don&#x27;t do anything else with nopes?
评论 #14515229 未加载
评论 #14515211 未加载
评论 #14515214 未加载
blinkingledalmost 8 years ago
&gt; for i in 0..(xs.len()) { let x = xs[i]; &#x2F;&#x2F; do something with x } &gt; should really be this:<p>&gt; for x in &amp;xs { &#x2F;&#x2F; do something with x }<p>I am curious why the compiler can&#x27;t rewrite the former to the latter?
评论 #14516476 未加载
评论 #14516460 未加载
评论 #14520042 未加载
smitherfieldalmost 8 years ago
<i>&gt;Sometimes, when changing an enum, we want to keep parts of the old value. Use mem::replace to avoid needless clones.</i><p><pre><code> use std::mem; enum MyEnum { A { name: String, x: u8 }, B { name: String } } fn a_to_b(e: &amp;mut MyEnum) { &#x2F;&#x2F; we mutably borrow `e` here. This precludes us from changing it directly &#x2F;&#x2F; as in `*e = ...`, because the borrow checker won&#x27;t allow it. Therefore &#x2F;&#x2F; the assignment to `e` must be outside the `if let` clause. *e = if let MyEnum::A { ref mut name, x: 0 } = *e { &#x2F;&#x2F; this takes out our `name` and put in an empty String instead &#x2F;&#x2F; (note that empty strings don&#x27;t allocate). &#x2F;&#x2F; Then, construct the new enum variant (which will &#x2F;&#x2F; be assigned to `*e`, because it is the result of the `if let` expression). MyEnum::B { name: mem::replace(name, String::new()) } &#x2F;&#x2F; In all other cases, we return immediately, thus skipping the assignment } else { return } } </code></pre> Don&#x27;t get me wrong, I think Rust is an incredibly impressive language, but this is <i>nuts.</i> For the first time ever, I had a moment of appreciation for the &quot;simple clarity&quot; of C++11 move constructors. If it weren&#x27;t for the comments and the documentation[1] I wouldn&#x27;t have had the slightest clue what this code is doing (a hack to fool the borrow checker while allowing for Sufficiently Smart Compilation to a no-op[2] ... I think).<p>This is a good example of the main conceptual aspect of Rust where I feel it could use improvement.[3] A lot of its features marry very high-level concepts (like algebraic data types) to exposed, low-level implementations (like tagged unions). Now, there&#x27;s nothing wrong with the obvious choice to implement ADTs as tagged unions, but the nature of Rust as a language that exposes low-level control over allocation and addressing, in combination with the strictures of the borrow checker, means enums and other high-level features live in a sort of uncanny valley, falling short of either the high-level expressiveness of ADTs or the low-level flexibility of tagged unions (without expert-level knowledge or using `unsafe`).<p>Similarly, the functional abstractions almost feel like a step backwards from the venerable C++ &lt;algorithm&gt;, abstraction and composition-wise. Very nitty-gritty, low-level implementation details leak out like a sieve — you can&#x27;t have your maps or folds without a generous sprinking of `as_slice()`, `unwrap()`, `iter()` &#x2F; `iter_mut()` &#x2F; `into_iter()` and `collect()` everywhere, although at least for this case you would be able to figure out the idioms by reading the official docs. But nevertheless it seems like reasonable defaults could be inferred from context (with the possible exception of `collect()` since it allocates), while still allowing explicitness as an option when you want low-level control over the code the compiler generates.<p>In this case, enums are a language-level construct, not a library, so the borrow-checker really shouldn&#x27;t be rejecting reasonable patterns. It (legal to alias between the structural <i>and</i> nominal common subsequence of several members of an enum) should be the default behavior and not require an nonobvious, unreadable hack such as the above. At the very least that behavior (invaluable for many low-level tasks) should be easy to opt in to with e.g. an attribute.[4]<p>[1] <a href="https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;mem&#x2F;fn.replace.html" rel="nofollow">https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;mem&#x2F;fn.replace.html</a><p>[2] Or rather since it is a tagged union, in MASMish pseudocode something like<p><pre><code> jnz x_nonzero mov MyEnum.B, e.tag x_nonzero: ret </code></pre> Although it could still be a no-op depending on what else Sufficiently Smart Compiler&#x2F;LLVM inlines.<p>[3] And I&#x27;m not saying I have the solution or even that all-around-better solutions exist.<p>[4] Something like<p><pre><code> #![safe_alias_common_subsequence(structural)] #![safe_alias_common_subsequence(nominal_and_structural)]</code></pre>
评论 #14517335 未加载
评论 #14517608 未加载
jancsikaalmost 8 years ago
&gt; With some investment into optimizations, matching or exceeding C’s speed should be possible in most cases.<p>What class of algorithms is faster in Rust than it is in C?
评论 #14518654 未加载
mcguirealmost 8 years ago
&quot;<i>Similarly, the Read::lines() iterator is very easy to use, but it has one downside: It allocates a String for each line. Manually allocating and reusing a String will reduce memory churn and may gain you a bit of performance.</i>&quot;<p>Back when Rust used &quot;internal&quot; iterators, this could be the default. Now, you can encapsulate it by passing a function to handle each line.