7 ms·
The title is a bit click-baity, but the core point is sound: C is not fast, it's optimizable[1]. If you just write readable, friendly C code it won't be all th
by tikhonj 10y ago
The title is a bit click-baity, but the core point is sound: C is not fast, it's optimizable[1].
If you just write readable, friendly C code it won't be all that much faster than normal code in a high-level language like Haskell—it might even be slower. I know, I've done that myself. But you never see that in benchmarks, do you? That's not what benchmarks are about.
Here's another illustration: we can compile high-level languages like Haskell and Scheme to C. Does this make them "as fast as C"? Yes—they're literally running as C. But also no. Hand-optimized C code is going to beat those compilers any day. It's not even close.
C is not magical performance sauce you can sprinkle over your code to make it fast. It's just a language that doesn't have much mandatory overhead (no GC, minimal runtime... etc) and gives you access to certain low-level knobs that you can twiddle to optimize your code. I mean, that's important and useful, but unless you're going to apply that level of effort to your whole codebase, most of your C code won't be all that fast. Some applications need this level of optimization; most don't.
[1]: I actually wrote a little article about this myself too: http://www.forbes.com/sites/quora/2014/01/09/can-a-high-level-language-like-python-be-compiled-thereby-making-it-as-fast-as-c http://www.forbes.com/sites/quora/2014/01/09/can-a-high-leve...
- Ace17 10y agoSo, basically, write your code in Haskell, then, profile, and rewrite performance critical parts in C and hand-optimize them. Which means ... C has become the new assembly language!
- niccaluim 10y agoThey don't call C "portable assembly language" for nothin'. :)
- m_mueller 10y agoJust two things to add: 1) Often the "performance critical parts" are actually 60-80% of your code. This is the problem with the "don't optimize prematurely" philosophy. Think of it in advance instead. Do you need the highest possible performance? Are you sure? How fast should it be then? Per request? At setup? Per data point calculated? Choose your tools accordingly. 2) For numerical code I'd actually choose Modern Fortran over C. Much less to do until it's fast.
- mightybyte 10y ago> Often the "performance critical parts" are actually 60-80% of your code. That has not been my experience at all. But I suppose it depends on what kind of programs you're writing.
- m_mueller 10y agoI find that much of what I do is highly generic code, so it gets reused in all kinds of different places. That's when this happens. So for me this is the case, but yes, it certainly depends. I just want to warn people that the thinking of no premature optimisation can backfire.
- rtpg 10y agoWhen talking about performance, C actually can have overhead though. For example, people rely on how C lays things out in memory, so C compilers have to lay things out in memory in specific ways. Haskell doesn't have this constraint and so can do a bunch of tricks. And then there are things where C cannot move a variable/optimise out a variable because it's unknown whether some unrelated bit of code my go poke at its memory. Now one might say "well if you know what you're doing then you can get the compiler to do what you want." Everything's possible, since everything's turing complete. But I would consider "variables need to be somewhere in memory" to be mandatory overhead.
- ktRolster 10y agoThat's talking about automated compiler optimizations; if you want to in C, you can move all those things around yourself by changing the code.
- caf 10y agoVariables don't necessarily need to be somewhere in memory. Compilers absolutely can and will notice if you never take the address of a variable to do this optimisation - it's very common for local variables in C to be materialised nowhere but in a register.
- tacos 10y agoThank you. ANSI C defines a language containing 32 words. And one of them is "register."
- bluetomcat 10y ago> For example, people rely on how C lays things out in memory, so C compilers have to lay things out in memory in specific ways. One can rely on C compilers laying out things in memory in a specific way only within a struct and when compiler-specific alignment/packing attributes are used. All other assumptions about variables being adjacent in memory are wrong and lead to undefined behavior.
- 10y ago
- pandaman 10y agoAnother thing about C is it's also debuggable and, consequently, profilable. You don't need to guess like the article's author at what is slow. You can know exactly what functions are slow and also why by examining hazards reported for every instruction. And then you can change your program in a way, which will eliminate the hazards if you wish so (and if it's possible, of course). With a smart high level compiler, even if you will manage to match the binary to source somehow, good luck predicting what modifications to the source are going to do to the binary. Debuggability is also often overlooked. You can attach a debugger to a running image, and, provided you have symbol names, figure what's going on. Even if all you have is a core dump you can make a good guess of what has happened most of the time. Trying Haskell and doing some research on debugging it seems the only way other than debug prints is to instrument your program and even then you are not getting much.
- pjmlp 10y agoHaskell also has debugging and profiling tools, e.g. ThreadScope. Actually the languages that don't, aren't worth using in production environments, this isn't anything C special.
- pandaman 10y agoLanguage-specific profiling and debugging tools are not the tools I had in mind. Being language-specific and system-agnostic they are naturally blind to the details of the execution which don't exist in the language. E.g. things like cache misses, TLB misses, jump miss-predictions, etc etc. I somehow doubt Haskell has tools for this. Same with the debugger - you sure can set breakpoints in GHCi, but what do you do with a core dump? What if you have a bug, which happens on a single machine in Bangladesh and takes 18 hours to reproduce? With C I can just connect with the system level debugger and, at least, know what happens. Is there such a tool for Haskell? PS. And, again, even if such tools existed they were just for informational purposes since you don't have much control over the generated code so you cannot sensibly modify source to change the binary in expected way. For example, in C, if I see a high number of cache misses in some loop I can: a) change the data layout (e.g. align structure fields or move simultaneously accessed fields together) b) add a prefetch. What am I to do in Haskell?
- qznc 10y agoThe core point is: If performance is not important, you might as well use Haskell. If performance is important, it is a good idea to use an optimizable language.
- pjmlp 10y agoAlso many that praise C's performace, tend to forget that once upon a time, hobby Z80, 6052, 80x86, 68000 programmers in home micros could write better code than C compilers were able to generate. So there are a few decades of engineering time spent improving code generation of C compilers.
- maccard 10y agoI write lots of C++ (granted, not exactly the same, but still); I regularly beat the compiler.
- cm2187 10y agoBut how much faster would an optimized C be vs a high level language? There are a few applications where every little bit of performance counts, like if you want to squeeze as many frame per second from a game. But I'd say for 95% of the code written, performance only matters if it is 10x or 50x faster. If you need to run a script and the script runs in 15s instead of 20s, it might be an impressive 25% performance gain but it's probably not worth the additional time spent optimising. Surely C can't be that much faster than say .net?
- geocar 10y agoYou can try it for yourself! It's my experience that people use the term "fast" and "faster" differently than I do. Go get some data[1] and see how long it takes you to process 600 million rows of anything. For example, take the 100 most popular symbols and find their last bid price. Once it's in memory, top open source SQL engines take 5-10 seconds, but writing it in C you can do it within 20msec. I'd be really curious to see how fast .net actually is: I'm told Spark took 35 seconds to work that out, and it's referred to as "Lightning-fast". I don't use magnitudes except when being illustrative because multiplying magnitudes like 10x and 50x become more exaggerated as the data gets bigger. When the cpu stalls, and waits for memory to come in, or the "runtime" takes a diversion to garbage collect some things, we're really talking about doing things that don't need to be doing. When I say something is "fast", I usually mean it isn't doing anything that doesn't need to be done, or I mean I can justify everything that it is doing that doesn't need to be done (as, for example, waiting for other slower things, or making it easier for me to write). .net may do fewer things that don't need doing than (say) perl, but programming in C we can actually make "fast" programs; we can simply choose to not do anything that doesn't need doing, and that means .net will never catch up. [1]: ftp://ftp.nyxdata.com/Historical%20Data%20Samples/Daily%20TAQ/
- cm2187 10y agoIt would be interesting to run the test, but for that it would take someone proficient in both C and .net to run a consistent algorithm on the same machine (I can only do the latter). But to just process raw data, I'd expect .net to be pretty fast. There is a bit of an overhead for the JIT compilation, but at the end you run machine code. You will loose a little bit of performance for things like array boundary checking, but surely it can't be more than a fraction. I would assume differences would appear when you are running a problem that requires a lot of memory allocation / de-allocation, creating and deleting objects.
- johncolanduoni 10y agoThis is why I think the biggest advance we could have towards removing the efficiency ceiling of high-level code would be having better ways to write C-level code that can interact efficiently and easily with high level code. For example, Java and .NET can call C code (very easily in the .NET case) but require the computational overhead of a GC transition and the maintenance overhead of having to write wrappers around any calls in the reverse direction. It would be awesome if you could easily write code that intermixed statements dealing with things at a lower level (malloc, pointer arithmetic, etc.) and those that access high level abstractions (GC'ed objects, virtual dispatch, etc.). The CLR and to a lesser degree C# technically allow for this kind of thing, but it's still sub-optimal from both an ease of use standpoint (it's very tedious to write C# like this) and a performance standpoint (the CLR's JIT produces non-optimal code compared to a static C compiler). .NET Native may solve the second one (though I am skeptical at this point that this will be Microsoft's focus with the technology, as opposed to easing deployment/lowering start up times) but I still feel we lack a language that makes this kind of thing easy.
- poizan42 10y ago> It would be awesome if you could easily write code that intermixed statements dealing with things at a lower level (malloc, pointer arithmetic, etc.) and those that access high level abstractions (GC'ed objects, virtual dispatch, etc.). The CLR and to a lesser degree C# technically allow for this kind of thing, but it's still sub-optimal from both an ease of use standpoint (it's very tedious to write C# like this) and a performance standpoint (the CLR's JIT produces non-optimal code compared to a static C compiler). Have you looked into C++/CLI?
- johncolanduoni 10y agoYes, unfortunately as far as I can tell it still requires transitions between managed/unmanaged code. Though I will admit it does wonders from a usability perspective.
- pjmlp 10y ago.NET Native uses the same backend as Visual C++, including many of the code optimizations that Visual C++ enjoys. https://blogs.msdn.microsoft.com/dotnet/2016/04/18/whats-new-for-the-net-native-compiler-and-runtime-in-visual-studio-2015-update-2/ https://blogs.msdn.microsoft.com/dotnet/2016/04/18/whats-new... I think it was a very big mistake to have Java and .NET use a JIT by default instead of AOT compilers like Eiffel, Modula-3, Oberon(-2), Component Pascal had. It created a bad perception of what memory safe programing languages compilers are capable of.