Live data from Hacker News

Rust std fs slower than Python? No, it's hardware

xuanwo.io

201–210 of 255 posts

Re: Rust std fs slower than Python? No, it's hardware

#201
post #33

> Rust developers might consider switching to jemallocator for improved performance I am curious if this is something that everyone can do to get free performance or if there are caveats. Can C codebases benefit from this too? Is this performance that is simply left on table currently?

Rust used to use jemalloc as the default, but went back to using the system malloc back in 2018-ish[0]. Since Rust now has the GlobalAlloc trait (and the #[global_allocator] attribute), apps can use jemalloc as their allocator if they want. Not sure if there's a way for users to override via LD_PRELOAD or something, though.

It turns out jemalloc isn't always best for every workload and use case. While the system allocator is often far from perfect, it at least has been widely tested as a general-purpose allocator.

[0] https://github.com/rust-lang/rust/issues/36963

Re: Rust std fs slower than Python? No, it's hardware

#202

Earlier quoted context omitted.

Of course in this case there's no FFI involved - the open function is built-in. It's as pure-Python as it can get.

How is it pure Python if it delegates all of the actual work to the Kernel?

All I/O delegates to the kernel, eventually.

It's pure Python in that there's no cffi, no ctypes, no Cython, no C extensions of any kind.

Re: Rust std fs slower than Python? No, it's hardware

#203

Earlier quoted context omitted.

The PyObject header is a target for optimisation. Performance regressions are likely to be noticed, and if a different header layout is faster, then it's entirely possible that it will be used for purely empirical reasons. Trying different options and picking the best performing one is not luck, even if you can't explain why it's the best performing.

You can expect the Python developers to look very closely at any benchmark that significantly benefits from adding random padding to the object header. Performance isn’t just trying a bunch of random things and picking whatever works the best, it’s critical to understand why so you know that the improvement is not a fluke. Especially since it is very easy to introduce bias and significantly perturb the results if you…

We're not talking about random changes. We're talking about paying attention to the measured performance of changes made for other reasons.

Just like in this article. The author measured, wondered, investigated, experimented, and finally, after a lot of hard work, made the C/Rust programs faster. You wouldn't call that luck, would you? If there had been a similar performance regression in CPython, then a benchmark could have picked up on it, and the CPython developers would then have done the same.

Re: Rust std fs slower than Python? No, it's hardware

#204

I'm a bit confused about the premise. This is not comparing pure Python code against some native (C or Rust) code. It's comparing one Python wrapper around native code (Python's file read method) against another Python wrapper around some native code (OpenDAL). OK it's still interesting that there's a difference in performance, but it's very odd to describe it as "slower than Python". Did they expect that the Python…

[deleted]

Re: Rust std fs slower than Python? No, it's hardware

#205
post #167

I'm a bit confused about the premise. This is not comparing pure Python code against some native (C or Rust) code. It's comparing one Python wrapper around native code (Python's file read method) against another Python wrapper around some native code (OpenDAL). OK it's still interesting that there's a difference in performance, but it's very odd to describe it as "slower than Python". Did they expect that the Python…

I'm a bit confused by why you are confused. It's surprising that something as simple as reading a file is slower in the Rust standard library as the Python standard library. Even knowing that a Python standard library call like this is written in C, you'd still expect the Rust standard library call to be of a similar speed; so you'd expect either that you're using it wrong, or that the Rust standard library has some…

It's just the spin of it that threw me off. You're right: "why is a C implementation so much faster than a widely used Rust implementation" is a valid and interesting question. But phrasing it as "why is a Python function faster than a Rust function", when it's clearly not the comparison at all, distracts from the real question.

Re: Rust std fs slower than Python? No, it's hardware

#206

Earlier quoted context omitted.

The sibling comments mention the hardware specific dynamic linking in glibc that's used for function calls. But if your compiler inlines memcpy (usually for short, fixed-sized copies) into the binary then yes you'll have to compile it for a specific CPU to get optimal performance. But that's true for all target-dependent optimizations. More broadly compatible routines will still work on newer CPUs, they just won yiel…

They are, glibc already has an ERMS code path for memcpy.

Did you miss the part about inlined calls? I was saying that if it weren't for these issues we could have them turn into a single instruction without worrying about perf.

Re: Rust std fs slower than Python? No, it's hardware

#207

There are two dedicated CPU feature flags to indicate that REP STOS/MOV are fast and usable as short instruction sequence for memset/memcpy. Having to hand-roll optimized routines for each new CPU generation has been an ongoing pain for decades. And yet here we are again. Shouldn't this be part of some timing testsuite of CPU vendors by now?

I'm completely making stuff up here, but I wonder if this is the effect of some last minute (or even post-release, via ucode update) bug fix, where page aligned fast rep movs had issues or were subject to some attack and got disabled.

Re: Rust std fs slower than Python? No, it's hardware

#208

Earlier quoted context omitted.

They are, glibc already has an ERMS code path for memcpy.

Did you miss the part about inlined calls? I was saying that if it weren't for these issues we could have them turn into a single instruction without worrying about perf.

I theory the compiler could always statically inline a memcpy to a (possibly nop-padded) rep mov. Then the dynamic linker could dynamically patch the instructions to a call to an out of line function if rep mov is known not to be optimal to the actual CPU the code is running. The reverse (patching a call to memmove with a rep mov) is also possible.

Re: Rust std fs slower than Python? No, it's hardware

#209

Earlier quoted context omitted.

The exact nature of the fix is unclear at present. During dynamic linking, glibc picks a memcpy implementation which seems most appropriate for the current machine. We have about 13 different implementations just for x86-64. We could add another one for current(ish) AMD CPUs, select a different existing implementation for them, or change the default for a configurable cutover point in a parameterized implementation.

This code is in the kernel, so dynamic linking and glibc is not really relevant.

It's the same design space. If glibc has ended up with 13 versions for x64, the kernel probably has a similar number. It's an argument by analogy for how much of an annoyance this is.

Re: Rust std fs slower than Python? No, it's hardware

#210
post #127

Earlier quoted context omitted.

Since the CPU instructions are the same, instruction patching at startup or install time can be used. Just patch in the correct instructions for the respective hardware.

This is generally a bad idea because it requires code modification, which has security implications. Most implementations will bring in multiple implementations and select the right one at startup (amortizing the indirect call into something like the GOT which already exists).

There are security hazards around writable + executable code. They don't apply to patching before execution (e.g. the install step) since nothing needs to be executed at that point. I don't think the security concerns apply during load time either - what does it matter if the text section is edited before it gets marked read-only&executable? It just means you're running a slightly different program, exactly as if it was edited during install.

In the memcpy case, where the library call is probably in a dynamically linked library anyway, it's particularly trivial to bind to one of N implementations of memcpy at load time. That only patches code if library calls are usually implemented that way.

Patching .text does tend to mess up using the same shared pages across multiple executables though which is a shame, and somewhat argues for install time specialisation.

Post reply on HN