No idea whether mainstream platforms like Ruby or Python do this...it wouldn't surprise me if there's relatively low hanging fruit for speeding up almost every webapp on the planet.
This is too obvious an issue, so there must be a solid reason. What is it?
The compiler should just provide good inlining support, so that if eg. you include the short-string optimization in your stdlib, the compiler can optimize it down to a couple bit operations, a test, and a word copy. If the test fails and your string is more than 7 bytes, it's perfectly fine to call a function - the function call overhead is usually dwarfed by the copy loop for large strings. And then if new hardware comes out and you vectorize it differently, you can get away with replacing that one function in the stdlib instead of recompiling every single program in existence.
So instead, this information/knowledge about high level data types is encapsulated by standard libraries and then the compiler below that. Most CPUs have single instructions to copy a chunk of data from somewhere to somewhere else and a nice basic way to repeat this process efficiently, and it's up to the compiler to use this.
Somewhat related is e.g. the sendfile() syscall that's used by web servers/frameworks to pass a file directly from your fcgi application to the outgoing socket.
Summary: 'Why doesn't "hardware support" automatically translate to "low cost"/"efficiency"? The short answer is, hardware is an electric circuit and you can't do magic with that, there are rules.'
Why don't they just patch the memcmp/memcpy in their libc?
(The string utilities I was referring to actually are separate APIs, focused around manipulating string pieces that are backed by buffers owned by other objects. It's like the slice concept in Go or Rust. With the growth in Google's engineering department, it got very difficult to ensure that everybody knew about them and used them correctly; this is probably easier if they're part of the stdlib. Indeed, they're in boost as string_ref, but most of Google's codebase predates boost - indeed, they were added by a Googler.)
Python 2.7.3 concatenates strings in string_concat using Py_MEMCPY, which is a macro defined at Include/pyport.h:292. That invokes memcpy, except for very short strings, where it just uses a loop, because on some platforms memcpying three bytes is a lot slower than just copying them. In http://canonical.org/~kragen/sw/dev3/propfont.c I got a substantial speedup from writing a short_memcpy function that does this kind of nonsense:
if (nbytes == 4) {
memcpy(dest, src, 4);
} else if (nbytes < 4) {
if (nbytes == 2) {
memcpy(dest, src, 2);
} else if (nbytes < 2) {
The main case in eglibc 2.13 memcpy, which is what Python is invoking on my machine, is as follows: /* Copy just a few bytes to make DSTP aligned. */
len -= (-dstp) % OPSIZ;
BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ);
/* Copy whole pages from SRCP to DSTP by virtual address manipulation,
as much as possible. */
PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len);
/* Copy from SRCP to DSTP taking advantage of the known alignment of
DSTP. Number of bytes remaining is put in the third argument,
i.e. in LEN. This number may vary from machine to machine. */
WORD_COPY_FWD (dstp, srcp, len, len);
/* Fall out and copy the tail. */
}
/* There are just a few bytes to copy. Use byte memory operations. */
BYTE_COPY_FWD (dstp, srcp, len);
In sysdeps/i386/i586/memcopy.h, WORD_COPY_FWD uses inline assembly to copy 32 bytes per loop iteration, but using %eax and %edx, not using SIMD instructions. It explains: /* Written like this, the Pentium pipeline can execute the loop at a
sustained rate of 2 instructions/clock, or asymptotically 480
Mbytes/second at 60Mhz. */
This is presumably what Jeff's code was a replacement for. Too bad he didn't contribute it to glibc, but he presumably wrote it at a time in Google's lifetime where the Google paranoia was at its absolute peak.PAGE_COPY_FWD sounds awesome but it's only defined on Mach. Elsewhere PAGE_COPY_FWD_MAYBE just invokes WORD_COPY_FWD.
My tentative conclusion is that Ulrich scared everyone else away from wanting to work on memcpy so effectively that it's been unmaintained since sometime in the previous millennium.
My sample size is only the set of projects that I've personally worked on, but in the projects where I have intentionally profiled where the time is being spent, I have only ever run into UX-impacting CPU bottlenecks when working on games. This is spanning code I've written from assembly all the way up to Python. Not counting JS because the DOM is a bit of a black box to me. In almost every other case, the bottleneck was not being able to pull data out of physical storage or some network store fast enough. In some cases it was not being able to pull data from RAM fast enough. In a few cases it was not being able to pull data from a database fast enough due to needing to cover too big of a table, which I lump into the "waiting for I/O" category under the lightly investigated hypothesis that it's not because the CPU is having trouble iterating over the indices, but because the database can't keep the whole index in memory.
Try out any "enterprise" app that thinks its a good idea to load 25,000 widgets on one page. That'll show you the meaning of "CPU bottleneck".
Depending on the implementation it might also be a GPU bottleneck.
Edit those known params were all used as a param for the same column
Sounds like you and I mostly agree. Just that when you do a root cause analysis, "more efficient CPU usage" seems to rarely be the best place to optimize, compared to "do less I/O" or "stop putting 25,000 widgets on one page", or "smaller RAM footprint", and so on.
Agreed, in most cases. That's why I'm not sure what I described is really a problem :)
(I do think that mobile apps and the client-side portion of Web apps, for example, are often CPU/GPU-bottlenecked, though.)
I was just picking at a nuance of what you said about wasted CPU cycles. Yes, some languages and toolchains these days are less efficient to trade for ease of development, but from my experience it seems like I/O to RAM/disk/network is still 80% of the problem and "less work per CPU instruction" is 20% of the problem.