[HN Gopher] The C++ Killers (Not You, Rust)
       ___________________________________________________________________
        
       The C++ Killers (Not You, Rust)
        
       Author : mwexler
       Score  : 54 points
       Date   : 2023-02-14 17:39 UTC (5 hours ago)
        
 (HTM) web link (hackernoon.com)
 (TXT) w3m dump (hackernoon.com)
        
       | JohnFen wrote:
       | > Because if you can write in Python and have the performance of
       | C++, why would you want to write in C++?
       | 
       | Because then you'd have to write in Python?
       | 
       | Seriously, all languages have tradeoffs. Performance is hardly
       | the only consideration.
        
       | KingLancelot wrote:
       | [dead]
        
       | SilverBirch wrote:
       | I broadly agree with the thrust here. I find it funny because as
       | a hardware engineer I know hardware languages _literally_ don 't
       | work for hardware. You can write almost any crazy valid HDL
       | (insert any systemverilog testbench code here), but there's no
       | chance it'll map to something reasonable in your target device.
       | Maybe you're targeting FPGA, maybe ASIC, whatever. The language
       | let's you express anything, but what you need to express is
       | legal. As such, what you need to bring to the table is a clear
       | understanding of what you expect your code to map to in whatever
       | device you're targeting.
       | 
       | The same is true in low level software, if you're _really_
       | interestied in performance it doesn 't matter if you express
       | yourself in Rust or C++ what you're really doing is trying hard
       | to point the compiler to one best solution- a solution you know
       | through either experience or experiment. If you care less about
       | performance, pick a higher level language and trust the compiler
       | more. If you're just trying to solve a problem and don't need
       | performance at all (any python script, or building the worlds
       | most popular code editor) just stick to python or javascript or
       | something.
        
       | zwieback wrote:
       | _" You can't win a car race if you all share the same car."_
       | 
       | Love that.
        
         | milliams wrote:
         | And then goes on to laud Numba, which uses LLVM underneath as
         | well.
        
       | comex wrote:
       | > Do you know that in MSVC uint16_t(50000) + uin16_t(50000) ==
       | -1794967296? Do you know why? Yeah, that's what I thought.
       | 
       | I was very confused by this claim before I realized it should be
       | *, not +.
       | 
       | Either way, the operands are promoted to int before performing
       | the addition or multiplication. But with addition, this is not
       | too surprising, as you just get the mathematically correct result
       | 100000. With multiplication, you get 2500000000, which is too
       | large for a 32-bit signed integer. This is undefined behavior,
       | but in practice it often results in wrapping, which is where you
       | get -1794967296.
        
         | ynik wrote:
         | Important to note: this is dependent on the implementation-
         | specific sizeof(int). So while on most architectures uint16_t
         | is the unsigned type that cannot be safely multiplied; on
         | others uint8_t or uint32_t might be unsafe to multiply.
         | 
         | As a result, it is nearly impossible to write platform-
         | independent code that does something like hashing (where
         | wraparound is intended) while also producing consistent results
         | across platforms. If course in reality, it's the other way
         | around: 64-bit platforms were forced to define `int` as a
         | 32-bit integer, because otherwise they wouldn't be able to run
         | any existing code.
         | 
         | C23 finally fixes this by introducing new fixed-size integer
         | types _BitInt(N) that don't suffer from this numeric promotion
         | mess. But of course the typedefs everyone is using will have to
         | stay broken.
        
       | rpep wrote:
       | Never heard of Spiral but it looks really interesting,
       | particularly compared to MKL and FFTW. It's been possible for a
       | long time for C and C++ compilers to use "profile guided
       | optimisation", but it's not widely used in practice because it's
       | pretty awkward to leverage.
        
       | zitterbewegung wrote:
       | I've been noticing more and more ML projects such as hugging face
       | adding support for JAX which makes Python have automatic
       | differentiation which is a key feature of Julia.
       | 
       | https://github.com/google/jax
        
       | tmtvl wrote:
       | There are two issues I have with discussions of code performance:
       | 
       | 1: Unsubstantiated and vague statements, e.g. X is 50% faster
       | than Y, well, how is it measured, what are the actual numbers?
       | 
       | 2: Measuring performance by measuring how much time a program
       | takes, e.g. X takes 3 seconds while Y takes 6 seconds, well, is
       | that consistent across measurements, have you measured it on
       | different machines, have you measured that under various
       | different conditions?
       | 
       | Various factors can impact the apparent performance of code,
       | among which is memory layout. Considering on GNU/Linux (and maybe
       | other platforms) the global environment is passed into a program
       | by putting it above the stack (remember, stack grows down) the
       | memory layout can differ by virtue of having different values for
       | the environment variables USER (username), HOST (hostname), and
       | PWD (current working directory); apparent performance is not
       | really reliable.
       | 
       | I don't know of the best practices for measuring performance, but
       | if I were responsible for measuring the performance of a program
       | I'd do so by looking at its disassembly, assigning a cost to the
       | different types of instructions, and adding the costs together.
       | That probably is a terrible way to go about it, so I welcome any
       | insight into why that isn't done.
        
         | katmannthree wrote:
         | It is done, in a sense, in cases where it really matters (video
         | codecs etc). More tooling for better optimization is cool and
         | exciting and all that, but it's also not free -- extended
         | compilation times in Rust are a nice example.
        
       | lacker wrote:
       | _[Languages like Rust] do help you to write more features with
       | fewer bugs, but they are not of much help when you need to
       | squeeze the very last flop from the hardware you rent._
       | 
       | I _do_ think that Rust helps you squeeze out that last 1% of
       | performance over C++. Because instead of spending all day finding
       | that one line of code where an unsafe threaded memory access
       | leads to a race condition bug, I can spend more time performance
       | profiling and optimizing the hot spots.
        
         | politician wrote:
         | Compared to Spiral, though? I'd prefer 'automatically finding
         | the optimal solution for a target architecture' to 'spending my
         | time performance profiling' with Rust.
        
         | rapsey wrote:
         | Yeah I guess OP never heard of the Rust based components in
         | Firefox.
        
         | katmannthree wrote:
         | Beyond that, this article is just a little bit absurd. Say what
         | you will about Rust but it's a functional and gradually
         | maturing ecosystem powering more and more things as time goes
         | by. Given that it's sorta-kinda intended to build the kind of
         | things C++ does, the idea that it'll eventually overtake C++ is
         | at least plausible. In comparison this article claims that
         | 
         | 1) A cool research language packaged into a VS Code plugin 2) A
         | cool research compiler for Python + numpy 3) A cool generic ISA
         | project
         | 
         | will kill C++? Maybe for the author's _very_ specific use case
         | of blindly copying things from SymPy, but really I think you'd
         | have better odds betting on a D resurgence.
        
           | Joker_vD wrote:
           | > 3) A cool generic ISA project
           | 
           | I wish we had an ISA with non-linear, two-level memory space,
           | so you can have non-overlapping arrays for free (well, as
           | free as virtual memory required to support that would be
           | free): that would mean that e.g. realloc() will never have to
           | call memmove(), range checks are inescapable since they're
           | baked into hardware, etc.
           | 
           | But implementing (and using) C on such an ISA will be deeply
           | unpleasant, so it will just die because if something doesn't
           | run C efficiently... it won't be used.
        
       | nayuki wrote:
       | > Do you know that in MSVC uint16_t(50000) + uin16_t(50000) ==
       | -1794967296?
       | 
       | That problematic value is produced by multiplication (*), not
       | addition (+). It happens because uint16_t is unsigned short, and
       | all arithmetic must be promoted to at least rank int.
       | 
       | The easiest universal fix for proper signed/unsigned arithmetic
       | promotion is: (0U + uint16_t(50000)) * uint16_t(50000). See
       | https://stackoverflow.com/questions/39964651/is-masking-befo...
       | 
       | > And suddenly it turns out that all the "C++ killers", even
       | these which I wholeheartedly love and respect like Rust, Julia,
       | and D, do not address the problem of the XXI century. They are
       | still stuck in the XX.
       | 
       | You forgot to mention Carbon (
       | https://en.wikipedia.org/wiki/Carbon_(programming_language) ) and
       | cppfront ( https://github.com/hsutter/cppfront ).
        
         | rwbt wrote:
         | I think any reasonable C++ programmer should expect that adding
         | two 16 bit uints of values 50000 will be greater than what a
         | 16bit uint can hold and will overflow.
        
           | Koshkin wrote:
           | > _any reasonable programmer_
           | 
           | FTFY
        
             | Joker_vD wrote:
             | No, it's exactly an _unreasonable_ programmer who 'd expect
             | that, being conditioned to such ridiculous extravagancies
             | by an inadequate language, which was the point of my
             | another comment in this thread.
             | 
             | Multiplication of intX_t by intX_t should produce int2X_t
             | which is btw is what actually happens in hardware! And
             | detection of overflow of addition/multiplication in a sane
             | language should not require ridiculous algebraic acrobatics
             | that involve additional additions/multiplications (or a
             | division, yeah, I've seen that too).
        
               | Koshkin wrote:
               | But in a (typed) language, types are _constraints_ that
               | must be respected (regardless of what hardware does), and
               | integer precision (as specified in code) is one of them.
        
               | Joker_vD wrote:
               | Sorry, what does "respect" means? Ruby and Pascal have
               | division operator that takes two integers and returns a
               | real -- is this disrespectful? Is it disrespectful for
               | multiplication to have signature
               | template<size_t N> operator*(int<N>, int<N>) -> int<2*N>
               | 
               | instead of                  template<size_t N>
               | operator*(int<N>, int<N>) ->
               | int<min(numeric_limits<int>::width, N)>
               | 
               | ? Why or why not?
        
               | Koshkin wrote:
               | Types exist for a reason, and one is the desire for
               | consistency (or uniformity) of representation: say, I
               | want to be able to store the result of an operation in
               | the storage element which looks the same as those where
               | the operands came from (think of an array, for example).
        
               | naniwaduni wrote:
               | int-n * int-n -> int-n (n > 1) is fundamentally the wrong
               | type _logically_ for a multiplication, though, so
               | something has to give. Sure, some languages have chosen
               | for the arithmetic to be broken in order to maintain
               | type, but that 's certainly not clearly the right choice
               | when others have chosen auto-promotion and most hardware
               | has chosen a (limited selection of) int-n * int-n ->
               | int-2n multiplication primitives to work with.
        
           | [deleted]
        
           | dannymi wrote:
           | Actually, what will happen according to the C++ standard
           | depends on the size of _int_.
           | 
           | C++ is doing implicit integer promotion of integer variables
           | with types smaller than _int_ , and that promotion converts
           | those values (of the operands of the _+_ ) to _int_ (gross
           | generalization yeah yeah).
           | 
           | So the result on g++ amd64 will be 100000 (as you would
           | expect) if _int_ is more than 16 bits (nowadays it is), even
           | WITH `(uint16_t(50000) + uint16_t(50000))`.
           | 
           | I've also tried it in MSVC 2010 and it says the result of
           | `std::cout << (uint16_t(50000) + uint16_t(50000)) <<
           | std::endl` is 100000 (both on win32 and on x64).
           | 
           | Try it on an arduino and you will get 34464 (with g++
           | targeting 8 bit atmel).
           | 
           | Think you want implicit integer promotion in a _systems_
           | language? You really don 't. They are an unnecessary language
           | feature.
           | 
           | Also, the article is only tangentially about that--that's
           | just an intro. The actual body makes very good points, and I
           | think it's more than a little tongue-in-cheek :)
        
             | cynwoody wrote:
             | Apparently, as the GP points out, the article author meant
             | to write asterisk where he wrote plus.
             | 
             | uint16(50_000) * uint16(50_000) is uint32(2_500_000_000),
             | which turns out to be int32(-1_794_967_296), the garbage
             | result the author cites.
        
           | Joker_vD wrote:
           | To paraphrase Dijkstra, "the use of C++ cripples the mind;
           | its teaching should, therefore, be regarded as a criminal
           | offence". Maybe a reasonable C++ programmer would expect
           | that, sure, but: _should_ a reasonable programmer expect
           | that? Automatic promotion to reasonably sized integers (and
           | in the limit, integers of unlimited precision) is _ancient_
           | tech.
        
             | Koshkin wrote:
             | Please do not put your own words in Dijkstra's mouth: he
             | said exactly what needed to be said.
        
             | otabdeveloper4 wrote:
             | > just promote my arithmetic to bigints randomly, #YOLO
        
       | tines wrote:
       | > Do you know that in MSVC uint16_t(50000) + uin16_t(50000) ==
       | -1794967296?
       | 
       | This is pretty basic C++/computer architecture stuff.
       | 
       | > They just don't give you a competitive advantage over C++. Or,
       | for that matter, even over each other. Most of them, for
       | instance, Rust, Julia, and Cland even share the same backend. You
       | can't win a car race if you all share the same car.
       | 
       | But it's the same backend that C++ uses...?
       | 
       | > Because if you can write in Python and have the performance of
       | C++, why would you want to write in C++?
       | 
       | I'm no rust fanboy, but isn't this the argument he used against
       | Rust in the first place?
        
         | Kranar wrote:
         | What does computer architecture have to do with this? What is
         | your explanation for why this happens?
        
           | Koshkin wrote:
           | If you care about efficiency, you have to take computer
           | architecture into account. (One important example is that
           | floating-point numbers are _not_ reals we tend to think of
           | them as.)
        
         | naniwaduni wrote:
         | > This is pretty basic C++/computer architecture stuff.
         | 
         | Well, it would be if they'd written it correctly--that should
         | be a multiplication, not an addition. As written, it would
         | certainly be very mysterious, but mostly just because it isn't
         | true.
        
         | otabdeveloper4 wrote:
         | Python in 2023 is like Perl in 1996.
         | 
         | Given the choice I'd rather program in anything else.
        
       ___________________________________________________________________
       (page generated 2023-02-14 23:02 UTC)