[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)