7 ms·
When O3 is 2x slower than O2
- fifilura 1y agoIt seems to me that if there is one way that is always faster it would hardly deserve a secret setting, it should be the default?
- mpyne 1y agoWhat secret setting is that? Whether taking a branch or a conditional move is faster or not depends a lot on the code and the data it's being run on so it's not easy to say up front which one is always faster.
- johnisgood 1y agoOff-topic, but I seriously dislike the syntax of Rust. It is chaotic and mind-boggling to me. See the "insert" function. Good post though.
- figassis 1y agoIt’s promises make me interested, but the syntax is my main turnoff. I am a Go person, and I think what brings people to go is the opposite of what brings people to Rust. I am willing to sacrifice some memory safety (because I maybe naively think I can manage to write software without many memory bugs) for the simplicity and dev experience that Go offers.
- Sharlin 1y agoIt takes at most a week to get used to just about any syntax. It should absolutely never be a reason not to try a new language. That said, it would be lovely if Rust had a more ML-like, rather than C-like, syntax (being originally inspired by O’Caml), but that would likely not help attract devs in the intended target audience! The insert function, for what it’s worth, has nonstandard formatting; the de facto standard rustfmt tool would use more newlines, making it clearer and less dense. The function also uses a couple of syntactic features like `if let`, which may seem unfamiliar at first but become second nature in a few days at most.
- alt187 1y agoIt probably takes a week to get used to ugly pants too. OP's point is a matter of aesthetic judgement. I don't mind it either, but = |&I: can| -> Agree { Rust::Syntax }; is.a(Bit(&mut chaotic);
- Sharlin 1y agoI should probably have used something less ambivalent than "getting used to". There's certainly more elegance in Rust's semantics than syntax, but the latter doesn't really differ much from C, except for much better for loops, a sane declaration syntax (this is a BIG deal), and of course all the features that C just doesn't have, like closures^* Some of the syntax noise just has to be there in some way, to support the real deal which is the semantics. ^* I don't particularly enjoy the Ruby-borrowed || either, particularly because Alt-7 is a terrible key combination to type, but oh well.
- bombela 1y agoOut of curiosity, what language specific keyboard requires alt-7 to enter the pipe symbol? On the QWERTY keyboard, the pipe is near the enter key and easily typed.
- dman 1y agoLaughs in lisp
- Sharlin 1y agoI've written Clojure professionally. It's exactly the language I had in mind when I said that it takes a week to get accustomed to. I find it a very clean and elegant language. Shame that it's dynamically typed.
- tmtvl 1y agoCheck out Common Lisp, it's gradually typed, so if you want you can just write your code like this: (defun fib (n) (declare (type (Integer 0 100) n)) (the (Integer 0 *) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2).)
- deleted 1y ago[deleted]
- mpalmer 1y agoIt really reads quite simply once you're familiar with the language. You only see chaos because there are none of the semantic hooks you'd get from experience
- johnisgood 1y agoEveryone who tells me this, I would love them to write an useful project in Forth. ;) Guess what? They would blame Forth, Common Lisp, etc., yet I could tell them the same things I am being told about Rust.
- mpalmer 1y agoForth and Common Lisp are demanding, but they demand different things of the novice programmer than Rust. The latter's baroque syntax is not provably good or necessary, but I'd call it the other end of a compromise for safety and expressiveness. And once you learn it, you've learned much of the language by default. Not so with with the first two! The mental models involved in reasoning about a piece of code require much more cognition on the part of the programmer.
- johnisgood 1y agoSounds like you are heavily biased in favor of Rust. What you said about Forth and Common Lisp applies to Rust, too, no matter how much you are trying to deny the reality of it. Just admit that you love Rust, and quite biased in favor of it.
- mpalmer 1y agoI do like Rust quite a bit. I don't know Forth or Common Lisp as well, but I don't dislike them. I admit, I have learned very little about them from you so far. You strike me as someone who isn't really prepared to have an actual debate on technical matters, because all you have done is: - manufacture some fictional strawman who is complaining about languages you like - tell me I'm wrong without even bothering to get into why - claim that I'm biased, when all I've done is explain my opinion that all three languages are challenging in different ways
- ikkun 1y agoas a beginner rust programmer, I agree. it takes me way longer to parse someone else's rust code than it does for me to read C or C++, even though I have about the same amount of experience with them. in that example, I had to look up what "if let Err() =" does, because it's not intuitive to me. it seems like every time I read rust code, I have to learn about some strange feature that's probably convenient when you know it, but there's a billion of them and they add up to hard to read code until you've spent tons and tons of time with rust. it's just so much to memorize compared to other languages.
- piva00 1y agoIt reminds me the experience I had when working with Scala, I really tried to like it but the mind-boggling amount of features created similar issues. It took me about 2 years to feel somewhat comfortable but I'd still run into code where someone decided to use their own set of unconventional favourite features, requiring me to learn yet-another-way to do the same thing I had seen done in other ways. I just got tired of it, didn't feel more productive nor enlightened...
- tormeh 1y agoDon't understand this complaint about Rust, but I'll give you Scala. Never seen a language so finely tuned to empower architecture astronauts to make terrible decisions. And boy do they make terrible decisions.
- ben-schaaf 1y agoI have the opposite experience: C++ is what I have the most experience with by a very wide margin, but I find reading other people's rust code way easier than other people's C++ code. There's way more weird features, language extensions and other crazy stuff in C++ land.
- johnisgood 1y agoI think both Rust and C++ are behemoths. I do not even know what "proper" "modern" C++ is.
- 6r17 1y agotbh it's bit boring to get stuck on aesthetic of writing up something. It's a bit nauseous to see how this place is hostile to rust for no reason other than "it's not pretty". It's a joke at this point we could make caricature of HN viewpoint on rust. We get it, you don't like it.
- christophilus 1y agoHN is not some monolithic single perspective. There are plenty of Rust fans here, and they show up just as frequently as the complainers. In fact, there is overlap between the two groups. You can be a fan of a tech and still complain about its warts.
- johnisgood 1y agoOh come on, how many times have we seen "blazingly fast in Rust", "rewritten in Rust" etc.? Many times. We get it. You like Rust. This "rewrite in Rust" bullshit is just as nauseous and I am sick of seeing it, too. So what? In fact, it is quite a meme now. Please, every time you see someone praise Rust (every other day), tell them the same as you have told me, just the opposite way around.
- Levitating 1y agoThe first statement defines a closure. The second is an if-let statement. It's not chaotic, you're just unfamiliar with the syntax. I actually find the Rust syntax very natural, more than C in some areas.
- johnisgood 1y agoMore than C? Highly doubt that.
- Levitating 1y agoArrays are initialized with curly braces but the type notation uses brackets. typedef takes the identifier at the end of the statement. The asterisk is used to de-reference but used to denote a reference in types. While loops may take the condition after the block.
- ozgrakkurt 1y agoFor me it is the usage of macros and traits everywhere. Good luck if you want to get into the code of a library to understand what a function does. You have to go through 3 macros and 5 traits across 5 files. When it could have been just a couple function calls. People don’t stop and think if they really need that trait or macro for five seconds, they just have to use it every time
- LtdJorge 1y agoI dislike macros for that reason when reading code on GitHub or Docs.rs, but Rust was kind of made to be used with an IDE (or LSP).
- bigstrat2003 1y agoThat is poor practice IMO. The simple reality of the world is that not everyone uses such tools. Designing a language with the expectation they will is deliberately ignoring the needs of some users.
- pjmlp 1y agoIDEs were invented by a mix of XEROX PARC, Symbolics, TI efforts into graphical workstations development, we have moved beyond the point of refusal to adopt modern tooling.
- Mond_ 1y agoThis point has been litigated to death. Read this here: https://matklad.github.io/2023/01/26/rusts-ugly-syntax.html https://matklad.github.io/2023/01/26/rusts-ugly-syntax.html Almost everything that people think is ugly about Rust's syntax exists for very specific reasons. Most of the time, imo Rust made a good decision, and is just making something explicit. Some things take time to get used to (e.g. if let), but for most people that's less an issue of syntax, and more an issue of not understanding a powerful feature (e.g. pattern matching deconstructions).
- johnisgood 1y agoThe reasons do not matter here. It is still ugly / noisy / overly-complicated and probably could have been done better. I understand pattern matching deconstructions, I have seen it in other languages. Funnily enough they were nowhere as ugly / noisy / complicated as Rust's is. Rust seems to have bolted on a lot of fancy shit that may be appealing to a lot of people and that is it. In your link, the first one is fugly, the last one is fine. Maybe Rust just encourages ugly (i.e. complicated) code a bit too much.
- embedding-shape 1y agoAs someone who mostly writes Clojure code professionally during the day, I agree, Rust's syntax is complicated for no good reason, ugly and overly-verbose. But then I think that about most Algol-like language too, not just Rust. And despite that I do use Rust when I want something simple to deploy/deliver, as handing over a binary that just runs is such a nice experience, and it's real easy to make fast. As long as I don't have to maintain in long-term, Rust is fine for what it is.
- Mond_ 1y ago> It is still ugly / noisy / overly-complicated and probably could have been done better. I don't know, it feels like you're just saying that you don't like it, missed the point of the post, and are not giving us anything concrete. Can you list a very clear example of how you'd improve the syntax? Again, see the post: You can remove things, but you're losing explicitness or other things. If you want a language that's more implicit, this is fine. I don't.
- neonz80 1y agoI find the short type names for integers and float hard to read. Somehow the size of the type is more important than if it is a signed integer, unsigned integer or a floating point number. Using Vec for arrays is also annoying, repeating the mistake from C++.
- Denvercoder9 1y ago> Using Vec for arrays is also annoying, repeating the mistake from C++. Neither Rust nor C++ uses vectors as array, they're distinct things. An array is a fixed-size object, which never allocates and its elements thus always retain their memory address. In Rust they're `[T; N]`, in C++ `T[N]` or more recently `std::array<T, N>`. A vector on the other hand is dynamically sized object, that may (re)allocate to accomodate new elements, and the address of their elements can change. In Rust they're `Vec<T>`; in C++ `std::vector<T>`.
- neonz80 1y agoI'm aware of how they are used, but fundamentally there is nothing with the words "array" and "vector" that says that one has a fixed size and the other has a dynamic size. If anything, it should be the other way around. Using the name vector for dynamic arrays is annoying when doing any kind of math coding. Even the designer of STL says the name was a mistake.
- bigstrat2003 1y agoI genuinely don't understand how you can find the type names for numbers hard to read, nor how you can say the size is treated as more important. The first thing in the type is what kind of number it is!
- cannonpalms 1y agoThe insert function is very unidiomatic. Instead of defining a cmp closure, you would typically implement Ord and friends.
- cat_plus_plus 1y agoAs a denser gas, Ozone would have greater friction getting through small pores, so that would be one example?
- arthur2e5 1y agoHey! I actually appreciate this kind of HN humor. As in, when you pretend to read the title badly.
- FredPret 1y agoI thought this would be an article about how ozone is heavier than oxygen and the molecules move slower or something. I found that surprising, but not as surprising as suddenly reading about Rust!
- zippyman55 1y agoI love these type of comments! Thank you.
- pclmulqdq 1y ago-O3 -march=haswell The second one is your problem. Haswell is 15 years old now. Almost nobody owns a CPU that old. -O3 makes a lot of architecture-dependent decisions, and tying yourself to an antique architecture gives you very bad results.
- ericpauley 1y agoThis era of CPUs has held up surprisingly well. I built an Ivy Bridge desktop in 2012 that still sees daily productivity use (albeit with an NVMe and RAM upgrade).
- arthur2e5 1y agoThe author gives a godbolt link. It takes 5 minutes to add two compilers on raptorlake and see that it gives the same result. https://godbolt.org/z/oof145zjb https://godbolt.org/z/oof145zjb So no, Haswell is not the problem. LLVM just doesn't know about the dependency thing.
- hedora 1y agoAlso, if you’re building binaries for redistribution, requiring Haswell or newer is kind of an aggressive choice. I have a box that old that can run image diffusion models (I upgraded the GPU during covid.) My point being that compiler authors do worry about “obsolete” targets because they’re widely used for compatibility reasons.
- sgerenser 1y ago"Haswell or newer" is a bit misleading here, because there were low-end Celerons and Pentiums shipped for years after the release of Haswell that lacked AVX2 and wouldn't be able to run software compiled for -march=haswell.
- pclmulqdq 1y agoOh, yeah. Looking at the code the comparison function is bad in a way that will hit issues with cmovs. The "else if" there is a nearly 100% predictable branch which follows a branch that's probably completely unpredictable. One optimization you get with -O3 is that branches not marked likely or unlikely can be turned into dataflow. The proper ordering for that code is to pull the equality check forward into an if statement and mark with "unlikely" if you're using -O3, and then return the ordering of the floating point comparison. Since we're using Rust there's some decent syntactic sugar for this function you can use instead: let cmp = |other: &Neighbor| -> Ordering { other.dist.partial_cmp(&neighbor.dist).unwrap() .then_with(|| other.id.cmp(&neighbor.id)) }; You probably won't get any CMOVs in -O3 with this function because there are NaN issues with the original code.
- YesBox 1y agoWhile working on my game, I tried implementing a custom priority queue. It ended up being > 2x faster in debug build, but 2x-5x slower in the release build (??!!?) [1]. I haven't learned much about compilers/"lower level" C++, so I moved on at that point. How it worked: 1.) The P.Q. created a vector and resized it to known bounds. 2.) The P.Q. kept tract of and updated the "active sorting range" each time an element was inserted or popped. 2B.) So each time an element is added, it uses the closest unused vector element and updates the ".end" range to sort 2C.) Each time an element was removed, it updated the ".start" range 3.) In theory this should have saved reallocation overhead. [1] I believe Visual Studio uses -O0 for debug, and -O2 for release.
- FuckButtons 1y agoAnd this is why you go and look at the assembly in godbolt to see wtf is going on.
- shihab 1y agoI'm curious what you did with the "active sorting range" after a push/pop event. Since it's a vector underneath, I don't see any option other than to sort the entire range after each event, O(N). This would surely destroy performance, right?
- YesBox 1y agoThere's no need to resort when popping an item. When adding an item, it gets added to the next unused vector element. The sorting range end offset gets updated. Then it sorts (note you would actually need a custom sort since a PriorityQueue is a pair) std::push_heap( vec.begin() + startOffset, vec.begin() + endOffset ) [1] Adding an item would be something like: endOffset++; vec.insert( vec.begin() + endOffset, $value ); [1] Or maybe I just used endOffset++; vec[endOffset] = $value; [1] Popping an item: startOffset++; [1] Im writing this from memory from my attempt many months ago. May have mistakes.. but should communicate the jist
- deleted 1y ago[deleted]
- spacecadet_ 1y agoIn the branchy function, id is only compared if distance is equal, and since distance is a random float, this almost never happens and the corresponding branch is nearly perfectly predicted. The branchless function always compares both id and distance, effectively doing twice the work. It's only part of the reason why there's a 2x performance difference, but I thought it was interesting. I really don't know how LLVM picks between branches or conditional moves, but my guess is that it doesn't assume that float equality is any less likely than other conditions, and some optimization pass in O3 turns unpredictable branches into conditional moves. I base this on the fact that adding std::hint::unlikely to the "equal" branch produces the same assembly for the function in both modes. https://godbolt.org/z/erGPKaPcx https://godbolt.org/z/erGPKaPcx Whether it's safe to assume in general that float equality is unlikely for the purpose of program optimization, I'll leave to the compiler engineers. If you know the data your program will be handling, adding hints will avoid these surprises.
- charleslmunger 1y agoYou can influence the choice of conditional moves (usually inserting them) with __builtin_expect_with_probability(..., 0.5) https://github.com/protocolbuffers/protobuf/commit/9f29f02a36ae0d2689ca7feebd2d07018436bcde https://github.com/protocolbuffers/protobuf/commit/9f29f02a3...
- barishnamazov 1y agoNice read. Last week I wrote a blog post about two noteworthy cases of O3 being slower than O2 in C++: https://barish.me/blog/cpp-o3-slower/ https://barish.me/blog/cpp-o3-slower/
- anematode 1y agoGreat post. I wonder whether profile-guided optimization would have led LLVM to select a better (or worse) decision.