14 ms·
I translated a simple C program to x86_64 and it was slower
- tambre 7y agoMaybe aligning functions would help. The compiler likely does that for the C version.
- jdsully 7y agoThe assembly is not bad for a beginner but far from optimal. A few things that stand out from a quick glance: 1) Try to load things sequentially don’t load from offset 32, then 0, then 16. 2) Don’t use the loop instruction 3) this code: dec n cmp n, 0 jne 1b Can be replaced with: sub n, 1 jnz 1b Which will actually be executed by the processor as a single instruction (macro-op fusion). 4) Interleave your expensive instructions with less expensive ones. Try to interleave multiple dependency chains to let the processor see more of the parallelism. Your two divisions one after another will be limited by available execution units capable of doing the divide. 5) Lastly align your loop labels to 16-byte offsets. The assembler will do this for you with the ALIGN directive.
- heeen2 7y agoShouldn't a superscalar CPU figure out concurrent execution chains by itself with register renaming
- jdsully 7y agoIt will do a bit of that, but remember it has to work in real time and and can only look ahead so far. Give it a fighting chance by helping out whenever you can.
- manaskarekar 7y agoTangent, but this reminds me of this great talk by Matt Godbolt from CppCon 2017: “What Has My Compiler Done for Me Lately? Unbolting the Compiler's Lid” https://youtu.be/bSkpMdDe4g4 https://youtu.be/bSkpMdDe4g4. And the follow up https://youtu.be/nAbCKa0FzjQ https://youtu.be/nAbCKa0FzjQ.
- battery_cowboy 7y agoIt's comments like these that make me realize I have a lot to learn about computers, I understand those words separately but together it sounds like a line from star trek.
- celrod 7y agoCPUs can execute multiple instructions per clock cycle (recent x86_64 can do 4-5). However, instructions can take 1 or (commonly) several clock cycles to complete, before their results are available for any instructions depending on said result to start executing. Such dependencies are the babe of superscalar parallelism. But sometimes things that look like dependencies are fake. XMM0 = XMM1 + XMM2 XMM3 = XMM0 + XMM4 XMM0 = XMM5 + XMM6 XMM7 = XMM0 + XMM8 At first glance it may look like you have to calculate these instructions serially. But, by renaming the last two `XMM0` you eliminate the dependency on the specific register, and can calculate instructions 1 and 3 in parallel, followed by 2 and 4 in parallel.
- vardump 7y agoExcept all those times it fails miserably. Like doing this to AVX SIMD.
- bluGill 7y agoCpus are designed with compiler optimizer experts in the loop. If the compiler can do it the cpu won't try. Instead the cpu does things that the optimizer can't do. Note that this goes both ways, if an optimizer won't use something the cpu won't do it, if the optimizer wants something the cpu will do it. (obviously within the limits of what is possible, and all the other trade offs) Regiser renaming is designed assuming the optimizer
- _bxg1 7y agoAre there linters that will overlay compiler-tier optimization hints on top of handwritten assembly?
- sesuximo 7y agoThis would be cool. I usually write up some C snippets and compare what different compilers do with what I have. The compiler explorer makes this really easy
- monocasa 7y agoHuh, yeah, that'd be neat. Specifically just ripping out the peephole optimizer from a backend just for matching would be a great start with a decent amount of value and not a god tier amount of work. That being said, you're not going to see the really crazy optimizations that compilers can do, because they need a bit of a higher level view of what you're trying to do.
- emidln 7y agoYou could try to use one of the c language reversers to get c source code and then ask gcc/clang.
- pandaman 7y agoDon't know of anything that works on the source code level but static analysis in profilers does that for the object code (ones I use are proprietary but I imagine something like V-Tune does this as well).
- CalChris 7y agoThe closest thing would be the Godbolt Compiler Explorer: https://godbolt.org/ https://godbolt.org/
- pjmlp 7y agoIt is nice tool for online exploration, but not as V-Tune.
- dbcurtis 7y ago
- ezoe 7y ago4. is interesting fact I didn't know. I learned in modern hardware, the simple data structure actually runs faster(linked node vs contiguous storage), but in assembly level, it's still quite opposite I guess.
- chas 7y agoUnder what circumstances to non-contiguous data structures run faster? I know of circumstances where structures with pointers make it easier to get better asymptotic behavior with a large amount of data, but none where a linked structure out-performs the analogous contiguous one on moderate amounts (a few cache lines) of data.
- bluGill 7y agoI wondered when he learned that. I learned the same thing in 1996, but modern cpus were not yet (the the knowledge of the class, I'm sure they were doing it...) dining prediction in the cache to load the next required memory before it was required. This optimization changed things such that linear search often is faster than binary search (unless n is very large of course)
- jjdredd 7y agoWhere could I read about advanced assembly optimization techniques like this?
- bluGill 7y agoIn the data sheets for your cpu. Note that this is cpu specific, even when the instruction set is shared that doesn't mean that the same optimization applies.
- drivebycomment 7y agohttps://www.agner.org/optimize/ https://www.agner.org/optimize/ is the best.
- fallat 7y agoDoing most of your changes made essentially no difference X) I suspect the assembly generated by the compiler is pretty fancy. Edit: Just briefly looking at the objdump again, gcc is using a lot of scalar variants of instructions, instead of vector. Maybe this is giving the CPU better hints? Maybe my code would run better on a more modern CPU (while still constraining the original C program to SSE3 only) ?
- jdsully 7y agoStrength reduction and algorithmic changes will make the most difference, but that requires a much deeper look at the code. I wouldn’t be surprised if the compiler found a way to reduce the number of divisions. If you have a lot of practice the compiler is not difficult to beat. But it definitely takes some effort and study of the optimization manuals.
- alecco 7y agoHand written 294 lines vs. gcc -O3 526 lines. https://godbolt.org/z/yEcMKf https://godbolt.org/z/yEcMKf At a first glance, it seems the compiler version is better at hiding the latency of some of the div instructions. It might be hiding memory access latency, too. But that's more involved analysis.
- msla 7y agoI don't know why you're using objdump instead of -S to have gcc generate assembly directly. You get named constants and named labels that way, and you can even choose the sane Intel syntax with -masm=intel which you can also do with objdump.
- kragen 7y agoI like -Wa,-adhlns=foo.lst -g to see the C, asm, and machine code all together.
- Phylter 7y agoFor x86-64 compilers will use intrinsic instructions which will automatically optimize for that processor. Unless you're really good at knowing the processor you're almost always better off doing it like this. It's almost no benefit writing assembly because of these optimizations these days.
- OrgNet 7y agoin any other language it would be something like if (x-1) != 0
- kazinator 7y agoI once found some book on MC68000 programming on the Macintosh. In a chapter on graphics, the book presented an assembly routine for drawing a filled circle and bragged about how machine code gets it down to just .25 seconds. I went, "what???" and read the code: it was calling an integer square root subroutine for every scan line of the circle. Not even a good integer square root routine.
- saagarjha 7y agohttps://www.folklore.org/StoryView.py?story=Round_Rects_Are_Everywhere.txt https://www.folklore.org/StoryView.py?story=Round_Rects_Are_... might be relevant?
- cheerlessbog 7y agoInteresting but it left me wondering what was his fast routine for drawing rounded rects. The squarical formula?
- jlokier 7y agoProbably the fastest way to calculate it is the Bresenham circle algorithm. But if there are a lot of roundrects with the same few small corner size (likely in a window system), a table for that size might be better.
- Someone 7y agoHere’s my guess: the Mac’s drawing routines all did clipping in software. That was accomplished by having drawing of rectangles, circles, ovals, polygons and rounded rectangles (for various operations such as erasing or filling them, drawing a frame along the outside, inverting the bits inside) all come down to: - compute the set of bits (called a ‘region’) to operate on - call the appropriate function to erase/fill/… that region I would guess drawing ovals was done this way: - create a region for a circle with radius equal to that of the radius of the corners. - insert horizontal parts to each row of bits in the region to ‘stretch’ the region horizontally into a rounded rectangle that has the correct width, but is only as high as the circle. - insert vertical parts to each column of bits in the region to ‘stretch’ the region vertically into a rounded rectangle that has the correct width and height. The first step was identical for the code for drawing circles; the region data structure made the last two operations cheap; they did not require any memory allocations. You had to walk the entire data structure, but for small corner radiuses, it wasn’t that large. Also, one could probably optimize for speed by doing that while creating the region for the circle. I can’t find a good description of the region data structure, but https://www.folklore.org/StoryView.py?project=Macintosh&story=I_Still_Remember_Regions.txt https://www.folklore.org/StoryView.py?project=Macintosh&stor... might be enough for some to figure out how it, conceptually, worked.
- MattPalmer1086 7y agoSometime around 2000, I tried to hand optimise an image processing routine in x86 assembly. Previously I'd only done 6502. My first attempt was massively slower than the compiled code. I had to get into the whole pipelining that was going on and other hardware optimisations. When I discovered putting a NOP in a tight loop sped up the code, I realised I wasn't going to beat the compiler most of the time!
- praptak 7y agoWas it instruction alignment or something more mysterious? My anecdote from hand optimization is that I didn't even know some instructions existed.
- ufo 7y agoI also had that thing with the NOP happen to me once, with the program with the extra NOP running 2x as fast! Took a couple of days until I finally figured out what was going on. After much investigation what I found out was that the original code without the NOP was actually running at only 1/2 the speed that it should. Due to very bad luck, the addresses of the jump targets in the inner loop where placed in a configuration where the branch predictor failed to predict the jumps (perhaps because of collisions in the internal "hash tables" used by the jump predictor). Any nudge to the executable would get the program out of the pathological configuration. Using a different compiler version, different OS , or different CPU model all did the trick. But the most fun of course was that adding or removing a NOP also made the difference :)
- ijidak 7y agoReally insightful comment. It's true. C compilers are so optimized, the day of re-write in assembly have long passed for most of us.
- lizardmancan 7y agoit is the dance between compilers and chip designs that unmakes hand work.
- lmkg 7y ago
- mhh__ 7y agoAll this usually proves is that compilers are smarter than you think. You can get some gains from SIMD code (i.e. autovectorization is hard), e.g. you've laid out data deliberately but the compiler doesn't quite see it, but modern CPUs are so complicated even executing scalar instructions that I wouldn't bother. I think optimizing memory is more productive half the time anyway, most programs don't spend that much time number crunching.
- crest 7y agoThis program is a special case because the whole state fits in registers unless the compiler detects this a human should be able to beat the compiler. Even unrolled the code would fit in the L1 instruction cache.
- nneonneo 7y agoThese days, it’s not only compilers that are optimizing for the particulars of the CPU, but CPUs optimizing for compiled code. My favorite examples of this are the inconsistent and sometimes downright crappy performance of “rep” string instructions and the “loop” instruction. Both seem like ideal easy things to write for handwritten assembly, yet the performance of both constructs can be quite awful on certain modern CPUs compared to the naive loops that compilers output. Much of this can be blamed on the fact that compilers rarely use either construct, so chipmakers had no reason to make either of them efficient (and, indeed, seem to have actually pessimized them on some chips!).
- userbinator 7y agoREP MOVS is still extremely fast --- and also small. It'll copy cacheline-sized chunks if the size is large enough. LOOP is a bit of a weird case. I've seen it benchmark both slower and faster than dec/jnz depending on the surrounding instructions.
- netrikare 7y agoloopXX instructions do not use CPU LSD (Loop Stream Detector) while cmp/jnz construct takes advantage of it. This speeds up some small loops. Also, there are some rules in intel manuals for instructions within cmp/jnz loop like no mismatched push/pop, etc. now, does anyone know why?
- userbinator 7y agolike no mismatched push/pop, etc. My guess is virtual stack pointer update prediction latency. To expand on that, Intel's CPUs have had for a long time a separate piece of hardware dedicated to a "virtual" stack which speeds up push/pop instructions. If pushes and pops are not mismatched, then all stack operations can stay entirely within that and there's no need to update the "real" stack pointer nor stack entries upon leaving the loop.
- netrikare 7y ago
- Porygon 7y agoThis is the n-body problem from the Computer Language Benchmark Game with the attribution to the original author removed. https://benchmarksgame-team.pages.debian.net/benchmarksgame/program/nbody-gcc-3.html https://benchmarksgame-team.pages.debian.net/benchmarksgame/... The faster programs usually use SIMD. https://benchmarksgame-team.pages.debian.net/benchmarksgame/performance/nbody.html https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
- MaxBarraclough 7y agoAny idea what's going on with the mem values in the listing in your second link? Wouldn't expect a C++ implementation to use 200x the memory of a C implementation. Different algorithms presumably? Or a significant compiler optimisation being missed?
- heeen2 7y agoMaybe because the cpp version includes stdlib on top of stdio?
- MaxBarraclough 7y agoAh, of course. So the problem doesn't involve any real allocations, and it's just the language overhead that we're seeing.
- fallat 7y agoReally sorry about that one. I've added the link a few minutes ago. I just did not expect this to be notable at all to anyone. Thanks for pointing it out!
- dependenttypes 7y agoThis is offtopic but I really wish that they re-added the graphs in the benchmarksgame.
- igouy 7y ago
- petermcneeley 7y agoYou have five bodies and 16 sse registers. The entire state of your simulation can fit into register space and you dont need to ever access memory during the stepping part of your code. You can loop unroll all gravity interactions so you end up with one large branchless memoryless block of code. Now that its completely inline you can rearrange your dependencies based on expected latency and throughput of operations (https://software.intel.com/sites/landingpage/IntrinsicsGuide/#techs=MMX,SSE,SSE2,SSE3,SSSE3,SSE4_1,SSE4_2&text=sqrt&expand=5385 https://software.intel.com/sites/landingpage/IntrinsicsGuide...) Then after that you can merge the operations where you can. (for SSE4 at most you are going to get is 2x because you are using doubles) You may think the full inlining is cheating but the compiler has the same information as your bodies list is entirely constant. (since your dt and your masses are constant they can also potentially be folded).
- CodeWriter23 7y agoCompiler beats novice assembly programmer ¯\_(ツ)_/¯
- arm64future 7y ago>The saying that "processors are optimizing for C" is totally correct. I thought that the latest consensus on HN was that C is nowhere closer to the hardware.
- AnimalMuppet 7y agoTwo things are both true. 1. Processors no longer look all that much like C's idea of what a processor is. 2. Processors are optimized to be able to execute C code very well. But an optimizing compiler can make it much better. There's a fair amount of magic in compilers to make this work.
- comex 7y agoC is close to the interface the hardware presents you – that is, the instruction set – but far from what the hardware actually does. Caching, out-of-order execution, branch prediction, speculation... all of those things are just as opaque to assembly as they are to C.
- msla 7y ago> C is close to the interface the hardware presents you – that is, the instruction set – but far from what the hardware actually does. C isn't even close to that anymore, now that so many processors have SIMD ISAs which C can't usefully model.
- pjmlp 7y agoAny good optimizing compiler is able to achieve this, there is nothing special about C in it.
- jchw 7y agoI think the point was adequately made. You can, of course, write better assembly, and you can of course write the exact same assembly a compiler would. But if all of the suggestions for optimization are things compilers would do anyways, you are better off coding in a higher level, portable language in the first place, and only dropping down to asm when it is sufficiently beneficial. All nitpicking about the quality of the assembly is valid of course, but it does inadvertently help prove the point, especially since most or all of these things are things compilers do today.
- dkersten 7y agoWell, you have to also consider that most of us write a lot of high level code and very little, if any, assembly code, so we shouldn't expect to be good at writing high performance assembly without practicing. I've seen some rather amazing high performance hand-written assembly code, by people who write it often, but I certainly wouldn't be able to do it, without a lot of study and practice. Of course, for most people and most uses, its not worth the effort.
- jchw 7y agoI frequently make this point so I may sound like a broken record, but I believe this is more or less a fallacy. Again, the point isn’t that better assembly couldn’t be written. It’s that it most likely wouldn't be significantly better than the compiler because all of the suggestions are things compilers would be doing anyways. There are some cases where this isn’t true, especially when dealing with vectorization, but those are mostly just exceptions (and intrinsics often offer easier ways to do such optimizations...) But here’s the point that I feel is often ignored when it comes to programming language debates in general: just because you are experienced with and aware of advanced usages of the environment you’re programming in, does not mean the complexity and especially cognitive overhead of said complexity has disappeared. Looking at the C version, it doesn’t really look especially optimized, which is not really something that you would see in assembler, at least not in my opinion. Complexity adds up over time; abstractions are the antidote to that problem. On top of that, assembly language is obviously not portable, which IMO is even more reason to use a high level language and drop to asm only when needed; you can easily swap implementations and have a fallback for architectures that aren’t specifically optimized.
- optimiz3 7y agoAuthor really needs to put the assembly under a profiler. Others have mentioned things like poor instruction selection (loop). In my experience it's good to first attempt a rewrite in C using intrinsics. This gets you thinking about data and register layout at a high level and let's you better identify mid level optimizations before committing to assembly.
- meche123 7y ago"Let me quickly set the scene for when I started: I had zero exposure to x86_64 before this." So, to sum it up: guy who has no clue writes worse code then one generated by a decent compiler.
- e12e 7y agoMaybe I missed it - but did the author list compiler options? I wonder if the handwritten assambler was slower than a compile with optimizations off, too.
- 205guy 7y agoFrom the article: “The gcc options for the C code used was -O3 -lm, because my processor doesn't support AVX“.
- Franciscouzo 7y agoI came to the same result three years ago by writing the exact same problem into x86-64 asm, I guess I didn't think of it as something worth of sharing. https://github.com/franciscouzo/nbody-asm https://github.com/franciscouzo/nbody-asm
- bogomipz 7y agoUnfortunately the links in the README file in that repo no longer resolve (https://benchmarksgame.alioth.debian.org/ https://benchmarksgame.alioth.debian.org/) Might you or anyone else have the updated link(s) to the nbody problem?
- brobinson 7y agohttps://benchmarksgame-team.pages.debian.net/benchmarksgame/performance/nbody.html https://benchmarksgame-team.pages.debian.net/benchmarksgame/... Looks like the subdomain was changed at some point.
- enitihas 7y agoWhat is the best book for learning the nuances of x86 assembly?
- pjmlp 7y agoIt is now about 20 years old, but you can start with "Zen of Assembly Language" and carry on with "Graphics Programming Black Book". https://github.com/jagregory/abrash-zen-of-asm https://github.com/jagregory/abrash-zen-of-asm https://github.com/jagregory/abrash-black-book https://github.com/jagregory/abrash-black-book
- userbinator 7y agoThe author picked a case in which compilers can be faster, because it turns out to be the sort of code that compilers are usually optimising for; code that contains plenty of scientific/numerical computations. Thus the result is not so suprising. On the other hand, with branchy "business logic"/general-purpose code and algorithms that can't really be vectorised, it's pretty easy to beat a compiler with handwritten Asm --- on speed, size, or often both.
- drivebycomment 7y agoPlease show us a proof - I have yet to see any non-toy program / routine with complex control flow written in assembly beating a modern optimizing compiler. You can optimize a small routine. Anything that's not a toy rapidly becomes impossible to optimize by hand. Just the register allocation itself can be a gigantic pain to do by hand, even with a limited register set of x86, let alone other RISCs with much larger register files. EDIT: Added clarification in the wording.
- userbinator 7y agohttp://menuetos.net/ http://menuetos.net/ Just the register allocation itself can be a gigantic pain to do by hand, even with a limited register set of x86, Actually, that's one of the main advantages of using Asm --- compilers are relatively horrible at register allocation, because they don't take a more "holistic" view of the program, and end up shuffling values between registers or registers and memory far more often. This is why handwritten Asm has a unique "texture" to it. let alone other RISCs with much larger register files There's much less room for optimisation with handwritten Asm on a RISC, because with x86 the opportunities are precisely those the compiler can't easily see, unlike a very uniform and boring RISC.
- drivebycomment 7y agoYou say those things. Please show me an example of a real world, general purpose, non toy sized program or routine that is hand optimized and shown to perform better than the optimizing compiler.
- aj7 7y ago“I haven't looked particularly close at it but I'm sure someone could spot why mine is almost 2x as slow.” Isn’t that the whole point?
- stephc_int13 7y agoA typical illustration of Dunning-Kruger here. This guy is jumping to conclusions after spending only a few days learning to write code in machine language. In my opinion, it takes about six months someone smart to reach a beginner level, and at least a few years to reach expertise. He says he is not an expert, and he is right, but he jumps to conclusions anyway... It is like saying, I am not an expert on climate, but I did this little experiment in my garden and I haven't seen any proof of climate change.
- xf1cf 7y agoA novice learned a language hard for even experts, discovers a strange result, and makes a post about it. On what grounds do you even have to criticize the OP? Your post history doesn't seem to indicate any long term experience or expertise in assembly optimization. Get over yourself.
- magicalhippo 7y agoNot too long ago while optimizing some x86 code on my i7-4900k, I discovered that at least for this code it was appreciably faster to do mov [mem], reg div [mem] rather than simply div reg I'm no asm guru, but I expected the L1 cache to be slower than a register, and certainly not a round-trip via L1 to be several percent faster. The reason I stumbled upon this was that the compiler emitted the first variant and I tried to optimize it to the second, but lost speed. That's when I decided I'll stick to optimizing my high-level code... FWIW this div was in a somewhat tight loop, but sadly I've totally forgotten the rest of details. I do recall adding nops here and there to play with loop alignment, it wasn't that.
- temac 7y agoModern processors are so complex that changes sometimes have paradoxical consequences, simply in a kind of chaotic way. That seems to be the case in what you describe. Those are typically not robust effects though, and changing seemingly completely unrelated things might put the perf right back at what you expect.
- magicalhippo 7y agoIndeed. Having slept on it, I do recall someone mentioning that my performance discrepancy could be related to busy load/store ports. If the issue is that it doesn't have free reg-reg load/store capacity but reg/mem and mem/reg is otherwise available, then it makes kinda sense I guess. But in that case the performance could totally change again if just one more instruction is added.
- bialpio 7y agoSince the compiler took advantage of it, doesn't it mean that the speedup is a well-known effect in the circumstances the compiler encountered? I'm nitpicking slightly on "paradoxical" and "chaotic". The behavior may be documented but to a human that does not remember every little rule, it may seem random.
- Veedrac 7y agoThis comparison is a bit unreasonable because the Benchmarks Game has a heavy selection effect going on; if the compiler generates bad code for a program, people won't submit it. There are plenty of instances where compilers go haywire and generate code far worse than even a half-competent novice would, because compilers don't know the same things about your code that you do.
- igouy 7y ago> if the compiler generates bad code for a program, people won't submit it Seems just to be speculation. Might we just as easily speculate that — if the compiler generates bad code for a program,that still looks a lot better than the comparison interpreters so people will submit it ?
- ggm 7y agoSome optimisation can only be performed after the fact (in as much as profile of a real run against real data informs the branch prediction and choices in speculative execution and order) Some execution fails: hardware has pathological instruction sequences which force sub optimal choices to be made. Alternative algorithms cannot always be identified from an instruction sequence. What if a higher order function identifed better seeds and led to faster detection in a large field of candidate solutions?
- fallat 7y agolol, I did not expect this. I submitted this myself yesterday and it got 2 upvotes. To the people with suggestions, especially jdsully: thank you! I will probably give these a shot. To the people who say "blah blah someone who doesnt know x giving an opinion on y blah blah", ok, what is wrong with sharing an experience? I'm not sure how to interpret this other than you take what I write way too seriously? It's an explorative piece... Edit: I wanted to mention too that it's really cool reading other's stories around this topic. Thank you for sharing!
- teleforce 7y agoInteresting, but personally I'd be more interested with the feasibility of X programming language with GC translated to C program and C version was slower type of article.
- nkurz 7y ago@fallat: I started to take a look at your problem, but I wasn't able to run the code that you posted to test. For the C code, the "#include" statements at the top are missing their <header.h>. Easy to solve now that you posted the link to the original, but maybe you could fix? For the assembly, you are relying on "list.macro", which I couldn't find. Could you post this, or even better link to a repository that can be used? Separately, it would probably be helpful to post the exact model number of your processor. Speed doesn't (shouldn't?) really matter, but the "generation" is essential. Also, I don't think you mention the number of iterations you are running for your timing. [I see now in the assembly that you seem to be using 5000000?] Post a bit more info, and someone here (maybe me, maybe not) will figure it what's causing the slowdown.
- fallat 7y agoOh whoops, I'll add it in. EDIT: It's a damn blogger encoding error. Yep, it's 5 million iterations. I will update the post with list.macros immediately! This is my CPU: Intel(R) Pentium(R) CPU N3700 @ 1.60GHz Edit: it is updated. :) list.macros has been inlined in the snippet.
- nkurz 7y agoGreat, I'll take a look in a bit, although it might take me until tomorrow to have time to do much with it. In the meantime, I'll mention that my first quick discovery is that clang seems to be significantly faster than gcc on the standard C code. The ratio changes with different versions and compilation options, but on Skylake with "-Ofast -march=native" I find clang-6.0 to be almost twice as fast as gcc-8. So if you have clang installed, check and see if it might be a better baseline. Also, what system are you running? This shouldn't make a difference with execution speed, but will make it easier to make tool suggestions. If you are running some sort of Linux, now would be a good time to get familiar with 'perf record'! Edit: > Intel(R) Pentium(R) CPU N3700 @ 1.60GHz Hmm, that's a "Braswell" part, which unfortunately isn't covered in Agner's standard guide to instruction timings (https://www.agner.org/optimize/microarchitecture.pdf https://www.agner.org/optimize/microarchitecture.pdf) and I'm not familiar with it's characteristics. This might make profiling a little more approximate.
- toddinsights 7y agoOne thing I was told in college is that compilers are much better at assembler than people.
- aeyes 7y agoI have a good real-world counter example: The game Rollercoaster Tycoon was written in assembly and ran full speed on a Pentium 1. OpenRCT2 is a code conversion to C++. You can turn off all the visual tweaks they have implemented on top of the original game and it still requires significantly more CPU time than the original. And thats with a compiler being able to take advantage of modern CPU instructions while the original game can't do that.
- temac 7y agoMaybe the slowness is more due to poor high level design for the problem (& if you want an highly optimized result)? E.g. AoS everywhere instead of SoA if needed. I don't know I have not checked the source code, but it is a possibility.
- chj 7y agoI recently had some fun writing arm64 assembly. My experience is that hand written assembly is faster if you apply the same cheats that the compiler uses. 3 things to keep in mind: 1) Make use of the registers; 2) Avoid branches; 3) Get familiar with the instruction set and make good use of them. https://litchie.com/2020/03/arm64-fib https://litchie.com/2020/03/arm64-fib
- ksaj 7y agoI would compile the C code with the -S option to output your C code as assembler, and then compare them to see what decisions were made by the (pre)compiler that made the compiled C code run faster than your hand-wrung code. The code is short enough that you should be able to relate the different parts and easily see the "improvements". This is how I learned to program assembler for Linux. I already knew TASM / MASM / a86 assembler languages for DOS/Windows - all similar but different in their own regards - but gas is a different un-backward beast, and of course the system calls are totally different between Linux and DOS/Windows. For example, here are 6 different ways to program helloworld on an ARM cpu (Raspberry Pi) running Raspbian Linux: https://github.com/ksaj/helloworld https://github.com/ksaj/helloworld I kept each of them as close to the same as possible, so that it is easy enough to see the differences in how each call is set up before execution. I'm sure if you added timer code before and after the example code, you'd very quickly discover which methods are more optimal and then begin to dissect why that would be, and how to use that knowledge in future programs. Keep in mind that the order of opcodes will impact execution speed, because of the many techniques modern cpus use to speed things up (branching, prediction, encoding techniques, entirely rewriting opcodes to something else with the same functionality - eg, most compilers understand that mov ax,0, sub ax,ax and xor ax,ax all do the same thing), etc.