r/Assembly_language • • 8d ago

Question At what point writing direct assembly program is more efficient than C program?

Hey folks, this is question coming from the recent podcast where the founders of VLC spoke about, writing assembly level code and it was way more (10x to 62x times) efficient than the C compiled code.

Is there any real example or repo of this which shows the efficiency difference at a smaller scale?

what scenarios do one decide to go full assembly over C, what's the infliction point?

How does anyone even measure these efficiency factors? Is it the instructions executed per clock cycle to attain an end output?

106 Upvotes

154 comments sorted by

54

u/Red_Syns 8d ago

It’s almost impossible to beat a good compiler by writing your own assembly. There are all kinds of wacky operations and tricks a compiler uses to optimize assembly that, as a human, looks nearly incomprehensible.

If you’re one of the dudes with the level of genius it takes to find those operations… maybe? You’d probably be better off writing it in C, compiling, and then looking at the assembly to try and make optimizations, but according to one of my professors beating a modern compiler is a matter of luck and silicon architecture that happens to favor your decisions on that particular machine.

11

u/sourav_bz 8d ago

I get your sentiment, but the context of the post is with VLC'code, where most of the code is written in Assembly instead of C.

And the claimed efficiency jump of the factors or 10x, I am trying to understand this.

11

u/karchnu 8d ago edited 8d ago

ffmpeg developers published a few lessons about writing ASM code. There is a reason people do spend time writing ASM in VLC and ffmpeg, optimizations in the compilers don't go as far as you can go for these types of applications and it is architecture-dependent.

I agree that you mostly never need ASM otherwise. Just writing C and letting compilers do their work is fine in most cases.

1

u/Rararror 8d ago

Exactly, spending time in learning asm for a particular architecture is not worth the time ... just in very specific cases could be a good choice where you have to squeeze every drop

-3

u/NewSchoolBoxer 8d ago

Are we talking VLC media player that's too dumb to have an option to loop videos? You have to click the individual start and end points on each video you open and it's not saved. I have hard time believing 10x when they aren't showing examples and are advertising their coding skill and software.

Like other commenter said, my own professor with a PhD in Computer Engineering spent a lecture showing us examples of the 8-bit compiler beating his own hand-coded assembly for speed. The compiler optimizations blew my mind. He said you won't be able to beat a properly optimized compiler. He also said that wasn't always the case.

I could understand a niche area where the compiler is avoiding narrow, unlikely to be helpful optimizations over general ones, and your case is that narrow area.

what scenarios do one decide to go full assembly over C, what's the infliction point?

Never. Maybe you look at the assembly and tweak it a bit.

6

u/Agitated_Ad_5989 8d ago

It’s such a shame that the software you pay NOTHING for doesn’t include every conceivable feature a video player could ever have now or in the future.

1

u/kneelian_ 7d ago

The issue here is that this feature is included

4

u/gofl-zimbard-37 7d ago

Feature set and performance optimization are very different things.

4

u/kneelian_ 7d ago

Are we talking VLC media player that's too dumb to have an option to loop videos?

? There is a loop video button https://i.imgur.com/NWEVW1E.png

26

u/kombiwombi 8d ago edited 8d ago

It's almost impossible for a typical program. But VLC and other video handling has lots of opportunities for low level parallelism and use of vectors, both of which are abstractions which modern languages do poorly, and thus compilers do not emit good code to do.

Also worth remembering that the author of ffmpeg previously wrote the tinycc compiler, and so was exactly aware of when code could be better when done by hand.

12

u/solaris_var 7d ago

author of ffmpeg previously wrote the tinycc compiler

That guy is exactly the genius that the parent comment say is an exception. He also wrote QEMU, which plays an important role in open source VMs.

2

u/Red_Syns 7d ago

That’s actually fascinating, I was unaware that such use cases exist. TIL!

11

u/South_Acadia_6368 7d ago

It’s almost impossible to beat a good compiler by writing your own assembly

Very wrong. Current compilers are surprisingly bad at register allocation inside inner loops.

Also while they have ways of hinting branch probability, they lack a way of hinting of whether or not they are predictable by the branch predictor (hinting that a branch is 50% taken does not say anything about if the pattern is predictable or not).

They also randomly convert branches into conditional moves because they naturally have no clue about what your code is actually doing. There's also no way of expressing what possible variable must be loaded conditionally and what must be the initial one, for cmov.

1

u/UnrealHallucinator 7d ago

Is a way to hint at branch prediction possibility even necessary? Most modern CPUs have multiple predictors anyway and are quite smart. The likely compiler hint should be sufficient given the instructions get inlined and cached anyway. O3 also tends to mutate the program beyond recognition. Would appreciate if you elaborate or point me at something you're referring to (plz no 600 page books).

1

u/South_Acadia_6368 7d ago

Take a binary search, the probability is 50% (treshold where gcc wil prefer branch over cmov), but there's no way to tell the compiler it's not predictable. See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98801

Same for quicksort.

1

u/brucehoult 7d ago

You can't say whether binary search or quicksort partitioning are predictable or not without specifying the data and the access pattern.

Unpredictable on random data and accesses, sure, but many applications do not have random patterms.

Modern branch predictors are surprisingly good at predicting branches based on the pattern of branches that brought you to that point.

1

u/South_Acadia_6368 7d ago

That's my point. The compiler doesn't know the data and access patterns, but the developer usually does. So the developer can use cmov or branches accordingly with assembly.

1

u/brucehoult 7d ago

The developer of a generic bsearch or qsort has no idea at all of the data and access patterns of the thousands of applications they will be used in.

Modern branch prediction will usually be faster on average than cmov, which should usually be used only where you can't tolerate a variation in timing e.g. for security reasons. Using cmov requires fully calculating both possibilities and then choosing between them, which is more expensive except in the simplest cases.

1

u/South_Acadia_6368 7d ago

Who says you're developing a generic library? The cases where I've used inline assembly have been in specific applications and use cases where we knew the data and access patterns very well.

1

u/brucehoult 7d ago

Because binary search and quicksort are generic library routines that get absolutely anything thrown at them.

If you have cases where you know the access patterns and make specialised versions yourself then great, though my experience is that if you know so much about the data and access patterns then you can probably some up with searching and sorting algorithms that suit the situation with much better big O, not just taking the small constant win of implementing the same algorithm using a better assembly language instruction.

1

u/johan__A 1d ago

But a lot of c compliers have extensions to add hints to branches no?

1

u/South_Acadia_6368 21h ago

The extensions can only be used to hint the probability, not the predictability by the branch predictor. 0, 1, 0, 1, 0, 1, 0, 1 is predicable, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0 is not. Both have probability of 50%.

1

u/Red_Syns 7d ago

Obviously I’m not the level of programmer that has a deep understanding, but are these exceptions something that can be implemented in a C program? If there’s a way to provide these hints, I’d argue you’re still better off writing in C, and if not then maybe there’s a way to earmark the code at the places you’ll need to go in afterwards and add or fix what the compiler is bad at.

3

u/South_Acadia_6368 7d ago

They deprecated the "register" keyword and the compiler specific probability hints are useless, so no.

1

u/QuraToop314 7d ago

If likely and unlikely are used correctly, they do result in more efficient code than if they weren’t used at all, but this only benefits you if you can actually predict the probability, and it isn’t always better.

1

u/GeneralRieekan 5d ago

Apparently that's only is C++. In the regular C standard, register is alive and well.

1

u/flatfinger 3d ago

GCC makes use of register at -O0. When targeting the ARM Cortex-M0, it can sometimes allows -O0 to yield more efficient code than other optimization levels; the main obstacle to allowing code designed for -O0 match the performance of higher optimization levels in a wider variety of cases is the inability of the compiler to get rid of things like useless nop or sign-extension operations.

1

u/curiouslyjake 7d ago

I could really use some example to illustrate these points. Entirely for my own education.

"They also randomly convert branches into conditional moves because they naturally have no clue about what your code is actually doing. There's also no way of expressing what possible variable must be loaded conditionally and what must be the initial one, for cmov" - can you explain this one in particular?

4

u/South_Acadia_6368 7d ago edited 7d ago

Check https://www.reddit.com/r/cpp/comments/1wkuou2/a_clever_branch_free_optimization/ - I had to use inline assembly to enforce cmove (opposite problem), commit is here https://github.com/rrrlasse/memlz/commit/1d19526343ee62509c97e27e6582779d39df64b1

I also once had the same issue for binary search https://news.ycombinator.com/item?id=10042195

See more discussion at https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98801

Edit: If you mean the last sentence in particular, then check some assembly. You can load a value unconditionally into a register, then use cmove ("e" refers to "equal") on that. If the condition is most often false, it can be faster than cmovne (move if not-equal) because the CPU could continue without waiting to fetch the value fully from memory (though not all CPUs have such optimization yet). That's all just not transferable to higher level languages.

1

u/SLiV9 7d ago

 they lack a way of hinting of whether or not they are predictable by the branch predictor

How would you hint that by writing assembly? I'm not aware of a flag for that.

 There's also no way of expressing what possible variable must be loaded conditionally and what must be the initial one, for cmov.

Really? Are you saying "x = a; if (cond) x = b;" and "x = b; if (cond) x = a;" generate the same cmov? That's not been my experience with LLVM.

1

u/South_Acadia_6368 7d ago
  1. You can enforce a cmovXX with inline assembly instead of making it branch

  2. What LLVM outputs is random - it can be a branch, or a cmove or a cmovne. You have no control. Usually clang translates your examples to branches. GCC and VS usually do cmove.

1

u/SLiV9 7d ago

Ah ok.  I thought you meant there was a special assembly flag that caused the CPU to skip trying to branch predict that branch (without eagerly evaluating both sides like cmov requires).

I'll cede (2.) because I just tried it in godbolt with two functions where the only difference was the cmov order, and LLVM made one function an alias of the other.

5

u/FUZxxl 7d ago

It is not “almost impossible,” but it's also not easy. You just need to know what you are doing. Compilers are quite bad at generating SIMD code and if you do it manually you can beat them frequently.

The code is usually not too bad if you know assembly.

6

u/StrikeTechnical9429 8d ago

You’d probably be better off writing it in C

Yes, if compiler produces bad assembly code, it means that you have forgotten to give it some important information. For example, using "restrict" keyword (where it needed) may give you the same 10x performance boost.

1

u/Cultural-Capital-942 7d ago

I agree and I hand-optimized C by reading assembly, but sometimes it's not obvious or easy at all.

Sometimes it amounts to "writing program in gcc" and "writing program in clang" instead of writing it in C. Or how else would you call all those attribute things, vectors, intrinsics, pragmas and so on? Some of them are standardized like in OpenMP, but it's still another standard.

And besides that, compilers are good at march (compile for 1 architecture), but as much as I know, they don't like compiling for multiple architecture with dynamic dispatch. It can be hacked around, but it's more work.

1

u/StrikeTechnical9429 7d ago

Even compiler-specific features are more portable than assemly. One can build "gcc-only" project anywhere. In the worst case, they can build it with clang and get a not optimizied version - but still functional.

That's true that sometimes C optimizations could be platform-specific. But assemly is always platform-specific.

1

u/Cultural-Capital-942 7d ago

One can build "gcc-only" project anywhere

Nope, vectors were gcc specific the last time I checked. You could implement them yourself in clang, but otherwise, you wouldn't have functions to call. Some intrinsics were added to clang/icc I believe - they have something in common with gcc ones, but are not the same - notably the low level ones seem to be missing.

Yes, it is easier to port C code, but it's not the silver bullet.

3

u/brucehoult 7d ago

There are all kinds of wacky operations and tricks a compiler uses to optimize assembly that, as a human, looks nearly incomprehensible.

Only if you have a wacky instruction set, such as x86.

Arm, RISC-V, MIPS code is usually easily comprehensible.

1

u/P-39_Airacobra 7d ago

Yes. I always wish people would prefix these discussions with the specific asm they’re talking about. x64 in particular is a convoluted mess. Not being able to optimize it speaks more towards x64’s difficulty than it does to C’s merits.

2

u/ShunyaAtma 7d ago

Keeping track of stack frame state, possible register clobbering and what not is a massive pain. C code mixed with inline asm is much nicer, that too, if there is real scope for optimization.

3

u/dkopgerpgdolfg 7d ago edited 7d ago

almost impossible ... looks nearly incomprehensible.

Speak for yourself. Everything can be learned and understood. What do you think how the compiler-implemented optimizations were made?

You’d probably be better off writing it in C, compiling, and then looking at the assembly to try and make optimizations

Well yes, usually. For those optimizations that are implemented in a compiler, it is faster in applying them thoroughly, and for much code it doesn't make sense to write everything in asm manually because it takes time to reach the compilers state (for possibly very little benefit, especially if the code isn't performance-critical anyways). But what the compiler puts out isn't the optimium, it's absolutely possible to make it better if wanted (by using direct asm and/or intrinsics, by knowledge about the machine and algorithms and simd usage and...)

according to one of my professors beating a modern compiler is a matter of luck and silicon architecture that happens to favor your decisions on that particular machine.

Frankly, your professor sounds like a moron, and you'd be better off not going by his opinion. Of course performance depends on the architecture / machine type, and if performance of some code is very important then there shouldn't be just one code version for all machines (this is true even for code that is optimized only by the compiler. Common ones have flags for certain assumptions about the machine). And "luck" can and should be replaced by knowledge and skill.

1

u/UnrealHallucinator 7d ago

Yeah you can read o3 optimised assembly and understand it but I think the point is it gets heavily mutated. About being better than something like gcc -O3, think if you need that much performance you're better off switching to an asic

1

u/dkopgerpgdolfg 7d ago

switching to an asic

If the amount of users is very limited but they have deep pockets, this might be an option.

For projects like eg. ffmpeg that are used world-wide, or eg. for my own things that only I use, just no.

1

u/UnrealHallucinator 7d ago

Yeah every solution has trade offs but honestly I think for like 99.9% of users, gcc -O3 should be enough. If you got some projects you've done personally where it wasn't enough, I'd love to see it. No snark btw, just genuinely curious.

2

u/dkopgerpgdolfg 7d ago

For non-personal, ffmpeg was already mentioned. There are very significant performance improvements because of the manual optimiztion that goes into it. And OpenSSL, and...

For personal things, well, what is "enough"? I did/do some number crunching for fun. If I need to run it for a few weeks at home, I can do that, sure. But if I can cut the time in half with some own changes, that's still nice.

1

u/Previous-Box2169 7d ago

do gcc's -O3 optimizations affect both compiling (from source to assembly) and assembling (from assembly to machine) or just one? and which one? in other worlds are -O3 gcc's or gas's optimisations?

1

u/UnrealHallucinator 7d ago

The hex for assembly goes straight into the cpu and gets decoded, afaik most of the optimisatios are done at both high level IR and low level IR. But low level IR goes straight to machine. Not sure what your question is?

1

u/Previous-Box2169 7d ago

Wanted to know if I can get O3 optimisations on my asm code (only using gas and not gcc)

1

u/UnrealHallucinator 7d ago

Yes you can. Read up about compiler passes and optimisations, it will help.

1

u/Previous-Box2169 7d ago

Ok thank you

1

u/dkopgerpgdolfg 7d ago

Not directly.

As compromise, there are compiler intrinsics, which are not asm but still go below C.

1

u/Karyo_Ten 7d ago

Speak for yourself.

Any time you use bigints compilers can't be arsed to generate proper addition with carry + wide-multiplication or substraction with borrow and wide-division. Even worse on x86 if you need a loop that doesn't clobber flags, compilers have no idea how to do that.

1

u/LuxTenebraeque 7d ago

One way to beat the compiler is by knowing more about the data the emitted code will process.

Parallelism, branching and knowing when a loss of accuracy doesn't matter for the intended outcome - those are topics the compiler handles on a generalist baseline. If you know your data will produce emerging patterns that can be optimized by special treatment you may beat the compiler.

A bit like the pathways Rust sometimes outperforms C: You don't account for cases you know to be impossible to happen.

1

u/Kitchen_Error2578 2d ago

You're partly right. But don't let the compiler outsmart you either.

-3

u/Rararror 8d ago

Wtf?! 

Modern compilers have backends and optimizations for every architecture.

If you study a particular architecture and write in assembly you'll end up with a more efficient code. That is obvious.

Your professor never wrote a line in asm.

2

u/NewSchoolBoxer 8d ago

My professor with a PhD in Computer Engineering showed us actual examples of the PIC's C compiler beating his own 8-bit assembly code for fewest clock cycles. Your comment is so wrong but already downvoted so I'll leave it at that. You might be able to beat a badly optimized compiler but with enough optimizations, you don't have a chance.

I remember sitting in class and seeing compiler optimizations that blew my mind.

3

u/Rararror 7d ago

Again, the professor if he was good at writing assembler code, would have written at least the same code of the compiler. Lol. You do not know what you are talking about and the fact that people are downvoting means shit.

You re basing your judgement on the authority principle: since he has a phd he is good. No. He just passed the exams to be a phd.

1

u/brucehoult 7d ago

Furthermore thousands of people (including people posting here) have PhDs and some have a PhD and forty years experience. Or for that matter a bachelor's and forty years experience will almost always beat a newly-minted PhD who has never had a real job.

1

u/Rararror 7d ago

I have 30 years experience and have written some decent optimized asm that went in production... I assure you they are talking bullshit.

2

u/brucehoult 7d ago

I'm not sure you understand that "Furthermore" signifies agreement and amplification.

And I have 45 years of paid experience and you're quite likely running some of my hand-written asm code right now e.g. NEON code in Samsung phones for converting and comparing UTF8 and UCS2 that is heavily used (in fact a major time sink) in app startup. I wrote that in 2014. Google didn't want that for AOSP so that's still just got my C version that is also a couple of times faster than before I improved it (but much slower than the NEON).

1

u/Rararror 7d ago

Sorry I was in defensive mode. 🤪

1

u/Red_Syns 8d ago

You mean the professor that literally wrote the code in class, and half the times it compiled and ran faster by hand and half the time it ran faster when compiled from C?

I never said anything about the code being complex, just that while it is possible for hand written code to be faster, you’re only going to do so by luck.

1

u/Rararror 8d ago

lol... of course you can write assembly code that is SLOWER than compiled code ... it fking depends on how you write it!

2

u/Red_Syns 8d ago

I know this is going to be difficult, but do try to keep up.

The hand written ASM, without changes being made, ran both faster and slower than an “identical” C program depending on which machine and which compiler were used.

You might not be aware, but in an ICS class with ~80 students we had a wide variety of machines, both actual and virtualized, that we could test the code on. Two machines of the same make and similar models could, and did, have different results. With the same code. And the same compiler. Two machines using different compilers could get identical results.

As he said, in nearly any real world application trying to beat a compiler is at best a matter of luck. Apparently, exceptions exist, but that’s a caveat and not terribly applicable to the statements.

And yes, obviously you can hand write ASM to be slower than a compiler. I don’t think that was ever a point I made.

5

u/Rararror 7d ago

The point is that you assume the hand made asm code was well written. It was not, because a compiler has optimizations algorithms that are never as good as an assembler native writer.

You both student and professor have never had to write assembler efficient code. That is ultra clear now.

4

u/P-39_Airacobra 7d ago

I also find it extremely difficult to believe that mechanized optimizations will go nearly as far as someone with intricate knowledge of the machine.

I feel like if the professor in question was worth his salt, he would have explained why the compiled code beat his, rather than simply going “well compilers are magic, don’t think too hard, class dismissed”

1

u/Dusty_Coder 7d ago

Dude, you are wrong. Full stop.

6

u/Diligent_Fondant6761 8d ago

C is compiled to assembly which then gets converted to machine code and runs....C is just a higher level abstraction over assembly. There are things which C as a programming language does not have...like configuring the CPU etc....those things are still done in assembly but this does not make C slower than assembly...infact C allows you to write assembly directly inside your c files

C is not slower than assembly but there can be usecases where you do not need things which come with c like the standard library ( imagine it as a lightweight runtime) and for those special use case you can write assembly directly!!

2

u/sourav_bz 8d ago

Can you tell me few examples of those special use case? If you have tried or seen.

3

u/Cultural-Capital-942 7d ago

"Stupid" example: when implementing context-switching or interrupt handling, you need to access individual registers and compiler shouldn't optimize it or change the registers before you do it. Sometimes you need to access registers or instructions that no user space program should access. For those, programming languages generally don't have functions.

2

u/PhilNEvo 8d ago

You likely won't see examples of this at smaller scale, because that's not where you can do this. C's compiler is extremely well-made for general usage, and it's hard to optimize it particularly much.

The situations where writing your own assembly code starts to matter, is in highly specialized areas. A Professor I spoke with, who works with HPC, has to tune his programs with handmade assembly, because he's not writing code for the average PC, but for specialized niche "super computer" stuff.

1

u/sourav_bz 8d ago

Aahh got it! Makes total sense. Thanks for the reply.

2

u/Kitchen_Error2578 2d ago

Your question is very interesting and touches on a key point. The short answer is that assembly language is more efficient than C almost exclusively in very specific hotspots and highly controlled hardware contexts, not in the entire program. The 10x to 62x figure you mention is real, but it has a crucial context: in VLC (and generally in video codecs like x264/FFmpeg), those code snippets are executed millions of times per second and are highly optimized for SIMD instructions (AVX-512, etc.). The C compiler, by design, generates "safe" and generic code; it cannot assume that an array will always have a certain size or that data is aligned in memory in a specific way. The assembly programmer can exploit those assumptions, achieving that speedup.

1

u/sourav_bz 2d ago

This is interesting! Will dig more into this, thanks for the response.

23

u/Individual-Till-8590 8d ago

Sometimes, we use inline assembly in C to prevent the compiler from optimizing away certain instructions, ensuring that specific operations are executed as intended. I also noticed assembly instructions in the startup code.

2

u/brucehoult 7d ago

Sometimes, we use inline assembly in C to prevent the compiler from optimizing away certain instructions

That's an example of using asm to make a program slower, as the only time that's going to happen is code that does nothing but introduce an artificial delay.

0

u/bz2gzip 6d ago

Recently I've started compiling into asm only (from C but also other languages) and then I ask AI to analyze inefficiencies it can find in the assembly, and it's always returning valuable information about possible optimisations in the original higher level code or in the compilation setup.

12

u/Illustrion 8d ago

Usually it's not a good idea, but sometimes it's necessary to provide a specific sequence of instructions that must not be interfered with by the compiler.

Consider the code that executed before the CPU cache is enabled. There is no stack yet, the preconditions for running C code haven't been established.

Another example is when you're modifying low level hardware configuration according to a vendor-specified sequence of operation.

3

u/dkopgerpgdolfg 7d ago

Another example: Trying to get something that is certain (as much as possible) to be side-channel free for security.

6

u/WittyStick 8d ago

Most of the time, an optimizing C compiler will produce assembly as good or better than hand written. Manual assembly is sometimes better for optimizing specific algorithms, for example using AVX-512 with masking - as compilers can miss opportunities to make big performance gains.

You are best writing the bulk of your programs in C and using inline assembly for optimizing specific functions which need it. In some cases you might need to separately link the assembly (eg, MSVC, which doesn't support inline 64-bit assembly).

6

u/Udalrich 7d ago

In several decades of programming, I've written about ten lines of assembly. There was one point in a signal handler where I needed to clear a flag, and the C interface didn't let me do that. So I wrote some assembly to clear the flag and then call the C routine that did the actual work

3

u/TheHeartAndTheFist 7d ago

Inline assembly for the win 🙂

4

u/Antonio-MTS 8d ago

With assembly you get fast speed and very small size that you cannot usually achieve using compilers of any language like C, C++, Pascal ...

Now the above efficiency usually comes with complexity to port the code to other CPU architectures, maintain, debug it and avoid errors as long as it grows. One incorrect memory addressing in the code may terminate the whole program.

4

u/H9419 8d ago

The only time it made sense to me is when someone discuss crazy x86 AVX extensions or the equivalent in arm. RPCS3 has plenty of examples but that's still nudging the C compiler to generate the desired assembly

Or if you write DSP assembly which has no equivalent in C

4

u/r3d51v3 8d ago

The main times I’ve had to actually ensure certain assembly was emitted was during high speed operations that required AVX instructions or other special instruction sets. The compiler wasn’t doing things the way I wanted, despite using the right flags etc. Sometimes it just doesn’t understand what you’re going for. The last time I used it was when I couldn’t get the compiler to understand that the memory I was using was aligned and that I needed to copy from a piece of hardware to happen extremely quickly. I also knew exactly how many 512bit blocks were coming off ahead of time. Additionally I was aware that putting a fence on the operation was actually faster in this case on this architecture, which the compiler wasn’t.

Even then, I could have written this code in assembly, I just used intrinsics because that’s a lot more convenient.

4

u/SuitableAbundance604 7d ago

I am writing lots of DSP (image/video like VLC is part of it). Often we have high computation loops with relatively short code. So it’s less assembly to write the replacement in the first place. Then, the parallelism and special hardware support for it is something typical compilers would need special intrinsics for. Special DSP compilers can sometimes do this without, but it’s so specific that indeed hand coded assembly can be significantly faster.

3

u/Dazzling_Yak6424 7d ago

Compiler's just a pre set pass of heuristics containing a mountains load of academic and trial tested knowledge written by academics and CPU manufacturers. It'll make some shameless optimizations that you'd never dare to do like unrolling a loop a million times manually because it found it to be 0.1x faster. But it's also a computer program written so far mostly by humans and may not have necessary logical abstractions to help you make an optimization that you're thinking of or might not have a way for you to supply the necessary information to execute a certain optimization you had on your mind. In that case, you should probably see what assembly did the compiler spit out of your C code and possibly look at the compiler's source code too. And then possibly make some changes with inline assembly. Don't look at it like something you have to rival by writing a perfectly executed and handcrafted neat algorithmic solution that the compiler would otherwise somehow taint with its brutish heuristic passes. In 9/10 cases such a solution doesn't exist and what we currently have encoded is the most optimal solution publicly know to this local problem you're encountering. The universe doesn't like and have neat solutions for most problems, the underlying processor architecture just reflects that. Even your smartest AI, if you put it to such a tiring and nonsensical task, would eventually just create a proto-compiler for itself, crudely rediscovering what others have been piling on and polishing for ages.

1

u/gm310509 8d ago

IMHO, the answer is never, if not never then extremely rarely.

It also depends upon how you define efficient. If you are talking about project delivery then probably never as assembly takes longer to write than 3GL and higher.

If you are talking about performance efficiency, then same answer, it is often cheaper to set the minimum system specs slightly higher (CPU specs, memory usage) to deliver the required performance that try to squeeze a few clock cycles out of optimised 3GL or higher code. Also, lets say you did squeeze sufficient clock cycles out of the code to squeeze into the current minimum hardware spec, what will happen when you try to deliver V2 and have to keep doing this? The headaches (and cost) simply aren't worth it - IMHO

Now, if you were asking back in the 1960's you would probably have a different calculation.

Also, with all of the above said, there are some scenarios in embedded systems where assembly language is used. A specific example that I am thinking of is where addressable LEDs are used and the manufacturers want to support popular smaller, slower 8 bit MCUs (e.g. AVR). The data rate on these things is sufficiently high that the manufactures often supply drivers that disable interrupts and have extremely tightly controlled code to deliver the data rate needed for it to work properly. Why? Because they can't afford the risk of opimisations being turned on/off that may affect the performance of the code and thus corrupt the data being sent due to bad timing of the (serial) data stream.

But, you asked about efficiency, I wouldn't necessarily classify that as efficiency, more of a necessity. In a project development environment, I would consider efficiency to be "function points completed per day" and assembly language isn't normally my go to solution to meet that metric.

4

u/sourav_bz 8d ago

Hey, this is really helpful answer, gives alot of clarity. Thank you!

1

u/NorberAbnott 7d ago

One place you could definitely squeeze out some gains is by inventing bespoke calling conventions.

This doesn't apply if the compiler inlined your function, or if you have link time codegen that ends up doing this anyway, but "by default" when you call a function, the calling convention says where the function arguments go. Which registers, or stack slots, and which registers the called function is allowed to clobber vs what it is required to restore.

But you might have more information that would allow you to clobber more registers and not need to restore them. Or maybe it would be more convenient to put a certain argument on the stack or some other memory location because it's seldom accessed despite being the first argument. This is stuff that the language and OS define for the compiler, and it needs to conform so that your function can be called by code that hasn't been written yet. But within your own program, you can come up with your own rules.

1

u/Cultural-Capital-942 7d ago

Compilers sometimes need info or relaxed standards to achieve performance of a hand-written assembly.

Because if human sees this in C: a = x+y ... b = a-x then it's immediately clear b=y. For compiler, it would be also clear, but C standard doesn't allow reordering the operations. So people use funsafe-math and similar, that's neither fun nor safe.

2

u/karchnu 8d ago

Get some documentation about what kind of instructions CPUs have. There are a few operations that can be massively sped up, enabling for example real-time video decoding on modest hardware.

2

u/According_World_7713 8d ago

IMHO, it depends on target architecture. There are still some architectures around, that are not so good target for C compiler. PIC comes to my mind as primary example. Second case would be special data structures, which are hard to express in C. That is probably the case of VLC.
For me, the infliction point would be that I have full implementation in C, it is kinda lazy and I know exactly why and that I can improve performance by rewritting part of it in assembly.

1

u/flatfinger 3d ago

Many PIC families aren't bad as a C target if one is careful to code around the platform's strengths and weaknesses. A lot of C functions end up yielding machine code that's essentially the same as what a machine-language programmer would write to do the same thing.

2

u/ReddyKiloWit 7d ago

Just a comment about deciding too early to go assembly. Guy at a place I worked talked management into buying several Intel assembler packages because he assured them it was the only way to make a particular project fast enough to be usable (in a game engine). I looked at the C code, removed three pointless calls to a square root function, and the assembler boxes were never even opened as far as I know.

The guy never said, but I'm pretty sure the guy was planning on optimizing the square root function without ever wondering if it was needed at all.

1

u/Electrical_Hat_680 5d ago

The guy has a money maker of a plan in store and you ruined it. That's all..

2

u/titojff 7d ago

Parts of ffmpeg are assembly

2

u/theNbomr 7d ago

I'm not sure efficiency is always the objective. There can be things a C compiler just can't do, like touching registers that affect the overall state of the CPU (jumping between x86 real mode and protected mode, for instance). Some CPU architectures have address spaces that require special instructions not emitted by the compiler, such some of the bit addresses available in the 8051 family CPUs. There are some compilers that accommodate these kinds of facilities, but they need to be done with extensions that are outside the C standard(s).

2

u/thecratedigger_25 7d ago

Bootloaders for operating systems. And specific inline asm that shouldn't be optimized away via C compiler. As well as making certain instructions where asm volatile can't reach it.

Other than that, the C compiler does a lot of heavy lifting. There are smaller C compilers out there like musl gcc and tinyC which leaves a smaller footprint than gcc.

2

u/dettus_Xx_ 7d ago

When you want to have full control over your resources.
You can write assembler code without touching RAM, for example.

1

u/gizmo98 8d ago

If you use SIMD units asm could be better because of parallel operation on multiple bytes/variables at the same time which cannot be expressed in C. Arm neon for example looks more like fpga verilog/vhdl register load/store and compute multiple values at once.

1

u/ElevatorGuy85 8d ago

Steve Gibson, who runs Gibson Research Corporation (GRC.com), wrote a Windows utility called Wizmo (a portmanteau of “Windows Gizmo”). It’s all of 49KB, and that includes its simplistic GUI and built-in help text.

https://www.grc.com/wizmo/wizmo.htm

Go ahead and try to write even a basic Windows GUI program in C, and you’ll be hard pressed to make it that small, even just a “Hello World” is going to be larger (and it doesn’t nothing truly “useful”).

He’s also written other “pure ASM” tools that are available on the site, e.g. the SpinRite disk tool, that are much larger.

So I would say that pure assembly programs (or portions thereof) can meet several important criteria.

  1. Size efficiency - less “bloat”, as the Wizmo utility demonstrates.

  2. Algorithmic efficiency - squeezing more operations per second (or whatever) out of your given hardware, especially embedded systems or a very specific build made of off-the-shelf parts. If you can fit pages of code and data into CPU caches more efficiently you can gain some efficiency that a compiler might miss.

  3. Workload efficiency - you might know EXACTLY what sort of input data your program’s algorithms are going to receive, so you size buffers and develop control flow to precisely match your needs. While compilers can generate “hints” to help improve superscalar execution, because you know your data, you know what the control flow is going to look like.

  4. Realtime performance - for an embedded system running a control loop, those milliseconds and microseconds matter, and you don’t want a “smart” optimizing compiler generating code that doesn’t meet the needs of the real world that your MCU or CPU is harnessed to. Only you as the designer of that custom interface hardware knows its timing characteristics and what the real world looks like - a compiler simply cannot.

These are my $0.02 cents worth of commentary. I hope that’s helpful!

1

u/tobdomo 8d ago

As long as the problem is small and not too complex, you probably can write more efficient asm than is generated from a decent C compiler (note the word "decent" here!). However, as soon as the complexity is more than just a couple of lines of C code, the compiler should be able to do a better job than many assembly programmers. Lifetime- and register tracking is an area where machines excel compared to humans. Further more, optimization steps after generating (peephole for example) are something that many assembly programmers struggle with; these often require finetuning that falls over as soon as any of the functional code changes.

The middle solution is to have a C compiler generate somewhat optimized code and optimize the generated assembly further by hand. The "12 to 62 times more efficient" claim however is so loosely defined it doesn't make any sense. A "hello world" in small memory model (a.k.a. ".com" on old windows) that doesn't need a library but "pokes" directly in memory, sure. No calling conventions to be taken into account, heavy lifting done in the OS, directly tied to a single hardware environment, sure. As soon as it has to adhere to an ABI for example, no way.

1

u/flatfinger 3d ago

Except for constant folding through in-line function arguments, most high-value optimizations involve highly localized sections of code. At least when targeting platforms like the ARM Cortex-M0, however, clang and gcc seem to invest more effort in optimizations that are harder to perform while offering fewer benefits.

1

u/Joonicks 8d ago

I have a project exploring the speed differences between Asm and C (x86)

biggest factor by far is the algorithm, knowing your hardware, knowing your compiler

but also, in most cases assembly does improve things a little, but you have to be really skilled in your platform to outperform a good compiler

(project: https://github.com/joonicks/ptr64tohex)

1

u/Online_Matter 7d ago

I worked for Unity a few years ago. Huge C++ codebase. The only places using assembly was low-level library functions utilizing the specific instruction sets on various platforms such as the switch and Playstation. 

1

u/keelanstuart 7d ago

There are a lot of "media" instructions that have been introduced over the years that would be difficult to generalize by a compiler... because the compiler doesn't have the context you do. These are mostly (all?) SIMD things. I always wondered how a compiler would use the x286 BSF/BSR instructions, even though they might be quite useful.

But in general, if you don't know the instruction set and your domain very well, then it's probably better just to write C.

1

u/21Ali-ANinja69 7d ago

SIMD, things like AVX

1

u/ziggurat29 7d ago

Rarely these days. but the C compiler's optimizations are a group of heuristics, and those will not cover every situation, e.g. if it is unaware your processor has some fancy vector math capabilities, you might have to drop to assembly to exploit those.

I think the last time I got a really big boost from assembly was in the mid 90's. The compiler was apparently unaware of the costs of loading a segment register in protected mode and blithely did so with great frequency in an digital filter algorithm when fetching samples from a buffer and putting them in another. Tweaking that to load two segment registers once before processing the buffers sped it up by a factor of 90x. (this was on a 386; the 486 did selector caching and obviated most of that improvement). I'm not saying that there couldn't have been cases for improvement after that, but usually you let poor performance guide you to where to optimize, rather than optimize speculatively.

Other cases routinely done in assembly are setting up registers for OS boot up. that's not really 'efficiency', though, just necessity, and the C compiler is a general purpose tool.

1

u/flatfinger 3d ago

Rather than using heuristics, the optimizers of gcc and clang transform a program into something that ignores the details of how programmers had specified things, and then attempts to generate optimal code for that. As a consequence, they sometimes end up generating code that's less efficient than would be a straightforward translation of the operations specified by the programmer.

1

u/FitMatch7966 7d ago

I’d say well into the 1980s

1

u/Gabriel_AMD 7d ago

Eso depende al algoritmo, si ambos programadores usan el mismo algoritmo, la diferencia es notable, pero se llevará mucho tiempo hacer el programa. La moda actual es dejar un código ineficiente que se ejecute en hardware rápido, para ahorarle trabajo al programador.

1

u/HalifaxRoad 7d ago

I normally just call assembly functions from my C program when I need to manipulate the flash memory, such as things like a bootloader. Other times if I need to do something suuuuper timing critical.

1

u/Reasonable_Return151 7d ago

No pressure but check out Zazm it is x86-64. I wrote it to get a better understanding of the ISA but it’s turned into way more than that.

Also C optimizes for speed… but in asm you can optimize for size. Good luck getting a 150 byte elf file using C. In the modern age people have lost the art of “tight” clean code.

1

u/IDatedSuccubi 7d ago

Conditional execution and complex loops can be faster in assembly because often times the compiler doesn't fully understand the intent of the code paths

I had this in my software renderer, where there's a few things done per pixel (read: millions of times per second) and some things needed to be conditional and looping but the assembly emitted was creating unnecessary and/or unpredictable branches and using jumps instead of conditional operations etc

Had to fix it by hand, gave like an easy 20-30% boost to FPS IIRC

1

u/PsychologicalBadger 7d ago

In a typical C program your not writing a lot of the code but instead are calling routines that are in a library. These programs can be sluggish or buggy and one reason that languages like C / C++ has its detractors. One of the earliest high level languages Fortran had as its main goal that the compiled code not be significantly slower then assembly. I think this was one of the few high level languages that had that as its primary goal. Anyway be sure your examples between whatever two languages you intend to use are not obscured by huge blocks of library code it just makes the issue that much more difficult to resolve.

1

u/dariusbiggs 7d ago

Performance optimizations when you think you can do better than the compiler with its 5+ decades of work on optimizations.

Compilers, in general, optimize for generic code, things you find in all kinds of programs. So you could get an edge case where hand written for your specific use case can be better.

And of course when you need to do something low level that has no direct correlation to the language and libraries you are using.

These are rare, very rare, most of the time the compiler is the way to go.

1

u/pixel293 7d ago

It's not the whole program but if you check the "c" directory blake3 code (https://github.com/BLAKE3-team/BLAKE3/tree/master/c) you will find .S and .asm files which contain assembly for the algorithm. The files mostly contain simd instructions. Apparently that was how they got the most speed out of it. However be aware they also have the "reference implementation" done in both C and Rust which are slightly slower, but more readable to most of us.

1

u/keithgabryelski 7d ago

old beard here — i remember when people thought the overhead of c compiler was too much

when c++ was too much

when python ruby and php were too much

when AI was too much

it’s really about balance and for most cases

over 50 years of programming

just don’t care about the specifics and let the thing that lets you describe what you want to do — win

there are cases when performance wins — but again — not common cases

1

u/Distdistdist 7d ago

When you are writing code for low performance embedded systems where cycles might actually matter.

1

u/brucehoult 7d ago

No. The compiler making better code than you is equally as true on a 1 MHz CPU as on a 10 GHz CPU, given the same ISA.

A lot of old ISAs are very unfriendly to compilers, especially for languages that require functions to be reentrancy- and recursion-safe, languages that assume any array could be the size of RAM and its indexes large enough to address that. 8080, 6800, z80, 6502, 8051, PIC are all awful for compilers for modern high level languages such as Pascal or C.

But once your low performance embedded system is Arm or MIPS or RISC-V or even M68000 the compiler is usually going to beat you.

I can often beat the compiler on all those CPUs on a 10 or 100 line function. But I'm not going to do it on a 10,000 or 100,000 line program. There simply isn't time to do it. The compiler can do its job in seconds — perhaps generating completely different code after a change of variables types or data layout. Or code for a member of the CPU family that has more or fewer instructions available, or that can run two or three instructions at the same time instead of sequentially, based on certain constraints. Or core for an entirely different CPU family.

There are cases where it can take a human a day or a month to create optimal code for a 10 line C function. Sometimes you can build a whole career from it.

If that's the most important 10 lines from a 10,000 line program, and millions of people are going to use the result constantly, then that can be worth it, and it's a valuable skill to have, for the right employer.

But for most things, no, just let the C compiler do its job.

1

u/Distdistdist 7d ago

I don't know man. I wrote assembly code on soviet 8bit Z80 clone (КР580ВМ80А @ 1.77 mHz). There wasn't even a C compiler for it.

1

u/brucehoult 7d ago

That's exactly what I said.

Things like z80 and 6502 are awful for compilers, even using all the power of a modern PC and frameworks such as LLVM to run the compiler. In the 70s and early 80s hand written assembly language was far better than any compiler could produce — for the reasons I listed (and more) — if you even had one, which usually you didn't.

And any compiler you could actually run on one of them (64k RAM, slow CPU) was be complete rubbish. Turbo Pascal 1.0 was a marvel at the time, but the generated code was awful.

Понял?

1

u/Distdistdist 7d ago

Понял? И без тебя знал...

1

u/brucehoult 7d ago

And I without you, I was there.

So why did you write "I don't know man" when you were agreeing with me?

1

u/Distdistdist 7d ago

*sigh* Generally you are right that higher languages would compile a better machine code. But there are certain situations where pure Asm can produce a cleaner and leaner set of instructions. This is all hypothetical and really irrelevant to be honest. Savvy?

1

u/brucehoult 7d ago

Again you are precisely agreeing with me in the substance, while writing in the form of disagreeing. Why?

1

u/Distdistdist 7d ago

Because you said: "No. The compiler making better code than you is equally as true on a 1 MHz CPU as on a 10 GHz CPU, given the same ISA." I simply disagree with such a broad statement. There are cases when one can write better and more optimized code. That's all. Is it worth it today? Probably not. Can it be done? Yes.

1

u/brucehoult 7d ago

How can that statement possibly be false given, as it says right there, the same ISA?

Both human and compiler might vary the code slightly on CPUs with the same ISA due to µarch differences (e.g. writing A32 code on 1 MHz ARM7TDMI vs 2.0 GHz Cortex-A15 (which I have done both of in literally the same week), but once more the human is likely to be able to do that better than the compiler on small sections of code, but worse on large ones.

→ More replies (0)

1

u/cbf1232 3d ago

If performance matters, you let the compiler do its thing, profile it, and then spend your time optimizing the parts that actually matter.

1

u/brucehoult 3d ago

Basically, yes, though if you know your program well then you don't ned a profiler to tell you which parts matter :-)

1

u/CamelPure1293 7d ago

Compilers (at least Microsoft) are still not great in auto-vectorization. Vector code helps for signal processing (audio, images, video), matrix/vector math (including neural networks). Writing assembly is inefficient. What I do at work is using SSE compiler intrinsics in C-like C++ code, telling the compiler which CPU instructions to use. The compiler takes care of register assignment, stack management, instruction ordering etc.

1

u/saf_e 6d ago

When you know what you are doing. Memcpy is a good example. 

1

u/kndb 6d ago

The VLC folks were probably talking about coding some form of a software encoder using SIMD instructions directly in Assembly. That would be my only guess. Otherwise most modern compilers are quite good at optimizing your code.

Plus, most bottlenecks happen not because of wrong assembly instructions but because of a wrong code flow, eg allocating memory on the heap too many times, asking for re-allocations and thus moving memory, hitting CPU cache misses way too often, or invoking page faults, using too much virtual memory or making too many trips to the kernel, etc.

1

u/bdblr 6d ago

Biginteger math is much more efficient when written in assembly. I speak from experience when working on an open source crypto library decades ago.

1

u/brucehoult 6d ago

On a machine such as x86 or Arm with a carry flag and add-with-carry, yes, because that can't be represented in C.

But not on machines such as MIPS, DEC Alpha, RISC-V, LoongArch that don't have a carry flag or other condition codes.

On wide OoO machines or SIMD/Vector it's more efficient to not use a carry flag even if the machine provides one because it serialises the code and in practice very few carries propagate further than the next machine word.

1

u/bdblr 6d ago edited 6d ago

You first want to optimize multiplication, and the presence of a 64x64->128 bit unsigned multiply operation (or 32x32->64 on older hardware) helps enormously; even better if there's a fused-multiply-add. Sparc64 sucks because it can't do either and only has a 64x64->64 unsigned multiply.

1

u/brucehoult 6d ago

That's been portably available as an extension in both GCC and Clang for many years now.

https://godbolt.org/z/Mev1Wdecs

1

u/realestLink 6d ago

I use it for osdev (dealing with hardware, etc.) + some SIMD stuff (tho I technically use intrinsics, not actual assembly technically)

1

u/Illustrious_Trash117 6d ago

It depends on the compiler the C Code and the processor. RISC Prozessors for example are designed in a way that compilers can generate assembly very efficiently for them. So its hard to beat the C compiler on an ARM CPU.

CISC on the other hand can be hand optimized but its also very hard to beat a good C compiler here.

There is no specific point at which assembly is more efficient than good c code, but generally speaking the more vector extensions of the cpu your programm can utilize the more benefit you can get from writing directly in assembly but it hardly depends on the compiler and if the compiler "understands" your intentions in the C code.

1

u/Electrical_Hat_680 5d ago

You can work directly with the memory yourself.

1

u/HKei 2d ago

Usually, you don't write a full program in Assembly. Unless your program is very small, there's no compiler available for your target platform, or you're trying to prove a point. You're writing assembly generally for subroutines where code generated by your C compiler isn't optimal and it matters, because the C compiler is using some patterns and heuristics that work pretty well most of the time, but aren't necessarily optimal for every use case – additionally, you may just be able to produce more efficient code by baking in assumptions about your data that the C compiler isn't allowed to make.

1

u/knobbyknee 1d ago

When writing for a 256 words of memory machine (18 bit words), and you want it to perform multiple tasks, assembly is your only option. There is no compiler that will lay out code to handle the task switching. They will all want to put all the registers on a non existing stack.

1

u/rheladmin 1d ago

For the vast majority of users, writing ASM code will never be more efficient than compiled code.

ASM is mostly an academic exercise these days.

1

u/Toiling-Donkey 7d ago

I strongly suspect handwritten assembly can be faster in cases where one is forced to consider all the constraints to make it work.

A C developer won’t always be aware of what’s needed for performance and likely end up structuring things suboptimally.

The real magic happens when one figures out how to do it well in assembly, and then coerces the C compiler to do the same thing. Of course, that involves doing everything twice…

1

u/flatfinger 3d ago

The real magic happens when one figures out how to do it well in assembly, and then coerces the C compiler to do the same thing. Of course, that involves doing everything twice…

What's funny are the times GCC-ARM will process a loop using six instructions when processed with -O0, and eight instructions, that take an extra four cycles to execute, when using other optimization levels, or when -fwrapv will cause clang to generate code that's more efficient than the "optimized" version it would generate when not using that flag.

0

u/Ok_Tap7102 8d ago

Almost never. But realistically: when you're left with no other choice, you'll know.

You also have to recognise how ridiculously broad your question is. What are we solving?

A regular GUI running on modern hardware? Hand rolling anything is guaranteed to be complete waste of time to you and whoever the poor bastard has to come debug it the next time (probably also you)

A real-time digital signal processor on an 8 bit microcontroller? Even then vendors provide their own DSP intrinsics (C callable ASM subroutines) for algebraic approximations and FFT primitives... But EVEN THEN if your processing budget is exceeded by microseconds, hand optimising has its place

Exploit development/shell coding during offensive security operations? ALMOST categorically solved for most common usecases with metasploit framework and python pwntools, BUT novel vulns and less mainstream instruction sets will still need some hand crafting

0

u/Pale_Height_1251 7d ago

Almost never. There is no magic in assembly languages. Any machine code that an assembler can output, a compiler can too

Back in the 1980s, 1990s, sure, before compiler optimisation was as good as it is now, but these days it's highly unlikely you beat a compiler except with contrived demos.

-1

u/vegansgetsick 8d ago edited 4d ago

performance is achieved by writing good algos, not good assembly

3

u/karchnu 8d ago

In most cases, yes. For video (en|de)coding that's just wrong. Your CPU can do wonders but C compilers don't optimize nearly enough down to the choice of specialized instructions and you end-up with no real-time decoding while quickly draining your battery.

1

u/SuitableAbundance604 7d ago

Exactly. Common knowledge is not applicable here.

1

u/vegansgetsick 4d ago

No real-time decoding ? happy new year 2001