Actually, Int 80h is a op-code in x86 assembly language inherited from the Intel 8008 and 8080 8-bit processors. The post announces a Free BSD assembly language tutorial for the x86.
Note that for modern x86 processors there are the fast systemcall instructions syscall/sysret (AMD64) and the older sysenter/sysexit. In contrast to int 80h, those instructions do not bear the full interrupt overhead. I couldn't find this info anywhere on the website.
See e.g. [1] for a more comprehensive overview concerning the handling of system calls on Linux.
For those interested, Linux has a remarkably interesting way to determine whether to use the modern sysenter/sysexit routines or the ancient 0x80 interrupt vector. http://www.trilithium.com/johan/2005/08/linux-gate/ discuses the system quite nicely.
> Secondly, there is a common myth among programmers that assembly language is very hard to use and that it takes much longer to code the same program in assembly language than in a HLL. [...] An experienced assembly language programmer can and does code as fast in assembly language as an experienced C programmer does in C. It is simply a matter of familiarity.
I don't really understand this argument. If a few lines of c can generate dozens of lines of assembly, how can it not be faster to have the compiler write assembly for you?
Also when writing C you don't have to be 'an experienced assembly programmer' to write performant and portable code. That is one of the driving points of a HLL: you don't need to know as much minutiae to write good maintainable code. I understand getting into assembly for the purpose of learning or squeezing out that last bit of performance, but it isn't a general purpose programming language anymore and shouldn't be a default choice over a HLL for most applications.
I dunno. I think by any measure being able to write "x / 3" will gain you productivity vs. "imul eax,2863311531; shr eax,1" (with the trip to the Hacker's Delight page necessary to look up the magic number).
I came to post this exact thing. I guess the article is actually quite old. It talks people "migrating from MS-DOS to Unix/BSD" among other things, which sounds more than a little odd to be even from this millennium.
Your comment focuses on how fast the compiler generates lines of assembly, after the programmer has constructed a solution.
But unless I have misunderstood the author was focusing on how fast the programmer can construct a solution.
The final step of depressing keys to input the solution into the computer can be sped up by using libraries and perhaps macros.
It's also possible to write programs ("code generators") that generate lines of assembly based on a "template". And that "template" need not be written in C or any other popular computer language.
It could be some "DSL", a shorthand for assembly, that the programmer creates for himself.
djb's qhasm might be considered an example.
While it may be generally true that C requires less key punching than assembly (certainly it requires fewer "lines"), this does not mean constructing a solution in C necessarily comes faster than constructing one in assembly... if the programmer is familiar and efficient working with assembly.
Something like "(x3+(y/20)5)" will generate a few lines of assembly (about 3 to 5 in this case, assuming registers are pre-loaded). But usually no, there won't be much more code. For example "while(1){..." equals just one instruction, and "for(x=10, x<0, x--){..." is 2-3 instructions.
edit: idk how to put a asterisk up there in the example above
I have no idea where people get the idea that one line of C equals 10 lines of asm.
PS Assembly is not hard at all, and is good to understand/read assembly when coding C. The only thing is that it is a bit rough to start coding in it (no good tutorials is one of the two-three difficulties).
> I have no idea where people get the idea that one line of C equals 10 lines of asm.
Because coding in asm takes more than just the raw instructions.
x86 doesn't have many registers so you're shifting around stuff across registers and memory all the time, not to mention stack setup/teardown and the error checking (e.g. div0, overflow) you are supposed to do after operations.
I also disagree with "shifting around stuff across registers and memory all the time". An "average" function doesn't really have all that many variables and x86 has 8 GP registers (16 on amd64). You don't even have to declare variables, you just take a register (and/or a location in memory) and use it as you wish.
I'm now sitting here thinking what can be done in C that would end up being a lot of instructions, and i can't think of anything. Going over an array does not, neither does recursion (recursion is just syntactic sugar in the end). What else.. i can't think of anything. Macros ? But macros are cheating in this comparison (not to mention that the flat assembler has actual macros, that are extremely powerful)
This is one of the things where (x86) Asm is definitely easier: divide overflows naturally cause an exception which will get handled automatically, and add/subtract/multiply overflows can be checked with one extra (JO or JC) instruction. In C, this is all undefined behaviour.
I'm now sitting here thinking what can be done in C that would end up being a lot of instructions, and i can't think of anything.
Crypto algorithms come to mind, but like you said, macros can take care of that easily... and on this page is an AES-256 CFB implementation in Asm that is around the same size of source as others in C (but much smaller binary):
Its a common trope expressed by bitter people. In general this happens when you don't keep up with technology trends, you just don't understand why somebody prefers one thing over another when both, peripherally, yield the same product. Its also obviously not true.
"But times have changed. For better or worse, the absolute majority of computers in existence today are built with the Intel family of microprocessors. Thus, portability is much less of a concern."
I'm guessing this article is somewhat out of date. The systems where performance matters most - those most likely to be constrained in some way, where coding in assembly would yield the most benefit - are mobile platforms, which invariably means ARM not x86.
ARM became dominant in mobile computing, which was barely a glimmer back in 2001. x86-64 became commonplace. Just targeting Intel and ARM processors now necessitates writing code for four architectures: x86, x86-64, AArch32 and AArch64 for a full range of compatibility.
While MS-DOS syscall numbers were documented and often _required_ assembly to use, the opposite is true on modern Windows - Windows system call numbers are undocumented and change from release to release, requiring programmers to link against Kernel32.dll or equivalent to stably call into the OS.
Compiler technology has advanced significantly, and processor manufacturers now often optimize towards patterns employed by compilers rather than by humans (case-in-point: the x86 "loop" instruction, while extremely convenient for handwritten code, is significantly less performant than a cmp-jmp loop).
Code has gotten more and more complex. Whether this is a good thing or bad, the reality is that hundreds of millions of lines of assembly would be required to replicate complex modern programs like web browsers - projects of that scale will always require powerful HLLs to manage abstraction, something that assembly does not and cannot provide on its own.
View this page as what it is - a historical artifact. Although assembly programming definitely still has its uses, the arguments this page makes in favor of it are largely no longer relevant.
Well, what once was not, certainly now appears to be. It's been used so much, I think it's fair to say it has become a word. Moans and koans to contrary will be accepted, however. And hey, if you're lucky its existence will be short-lived.
"The network and hierarchical data models, introduced in the 1960's, provide a very performant data organization for well-defined, update-intensive transaction processing of large volumes of data at the cost of conceptual complexity" (1993) - https://books.google.com/books?id=Ro3zjjTNE9UC&pg=PA327&dq=p...
There are many domain-specific terms which have not yet made it to general dictionaries. For example, from your comments, I see you used the word 'multicast', This is not in Merriam-Webster, at http://www.merriam-webster.com/dictionary/suggestions/multic... . What makes it a word but not performant? Is "zero-copy" a word? "smarthost"? "type-punning"? "upstream"?
Those example words have precise meaning, "performant" does not. Engineers should communicate using precise language, like other technical disciplines.
I think that's what bugs me about "performant" --- it feels like vague buzzwordy management-speak, because there's already a well-understood word for the same concept: "fast". Ditto for "lightweight"; "small" gets the point across clearly, and if it's both fast and small, it's "efficient".
Software can be big or small, fast or slow, efficient or inefficient (for combinations of size and speed.) I don't think we need any more adjectives beyond those.
Instead of "significantly less performant", you could just say "much slower". (Note that LOOP was much slower on older Intel CPUs, but was the same as the longer sequence on AMD's. It would not surprise me if newer Intel ones now decode LOOP into the same sequence of uops. In any case, unless the loop is a tiny one, what happens in the body is likely to make a much more significant difference, especially if cache misses are involved.)
What's imprecise about: "Performant: 1) meeting the objective, often applied to a performance objective, 2) productive"?
In looking around just now, economics seems to prefer the second definition, which is why I added it. All of the uses I found are compatible with this definition.
A vague word is useful when you are trying to convey a vague concept, in much the same way as it would be hard to convey the idea that you know that some metric is between 20% and 40%, if you are not allowed to be vague and are required to arbitrarily pick some number like 38.51% and say that one instead
I basically cringe and stop taking an author seriously when they use the non-word "performant", but I've kind of given up pointing it out. Brother, we're losing the fight to people who think it's perfectly cromulent to embetter the language by innoventing their own words.
What defines a language anyways? Languages evolve. That's the beauty of them: They aren't set in stone. Who knows? Maybe in a few decades, "your" will actually have a definition added for "you are" instead of the (currently correct) "you're". "Computer" used to refer to an actual person. "Caught" and "cot" used to be pronounced differently; now they are slowly merging.[0]
Languages evolve. Whether for better or worse is up to the reader. If it weren't for evolving languages, we'd all still be writing like Shakespeare.
It is a word. Performant means "performing well". I wonder if can we apply this kind of adjective-forming to other verbs, e.g. by taking the gerund form, removing "-ing" and adding the suffix "-ant".
For example:
- observing => observant =?= observing well (yes, exists in this meaning)
- informing => informant =?= informing well (does not exist as adjective, but could in the future?)
- thinking => thinkant =?= thinking well (does not exist at all, but could in the future?)
"Performs well" has no concrete meaning. It almost always requires a second question for clarification, e.g. "What does good performance specifically mean here?" Or "performs well compared to what?"
Scientist, doctors, lawyers, and other engineering disciplines use precise words. Software engineers should too.
As more and more people use "performant" fewer and fewer people will understand what good performance actually means. I wouldn't be surprised if this encourages a culture of wasteful premature optimization.
Whether this is a good thing or bad, the reality is that hundreds of millions of lines of assembly would be required to replicate complex modern programs like web browsers - projects of that scale will always require powerful HLLs to manage abstraction, something that assembly does not and cannot provide on its own.
On the other hand, when using Asm you can often leverage techniques not possible in HLLs to avoid complexity and abstraction. This is a fact which seems not commonly known, even in CS courses involving Asm, as those tend to be more about inspecting compiler output and the few "write in Asm" exercises are mimicing code that a compiler would generate, which IMHO misses the whole point of writing in Asm.
Here is an operating system containing many nontrivial applications, all written in 100% Asm:
Among other things, it contains a minimal yet functional web browser (around the same level of minimalism as Dillo or NetSurf, i.e. no JS or fancy CSS), and the whole package fits on a single floppy disk.
An entire OS with kernel, drivers, and applications in total can be smaller than a "Hello World" application in a "modern" programming language (which depends on countless other external libraries too, including an OS several GB or more.) It really makes you think.
So, not only does GCC hugely beat this snippet of handwritten Menuet assembly in code size, it can even do so handily while folding all the repeated additions into LEA and eliminating most of the loads.
This is an excellent example of why you shouldn't write in assembler.
An assembler programmer at full attention can beat a compiler at most operations. That said, the average level of attention a compiler can give over the whole program is much greater than an assembly programmer. When performance is the ultimate goal it makes sense to only drop into assembler for your inner loop.
However assembly programmers will also instinctively avoid things that result in massive amounts of code because they have to manually write every line of it. Compared to C++ where templates are effectively a code generation meta-language. C makes you work a lot harder to generate huge amounts of machine code - and its binaries tend to be smaller because of it.
I don't think that's correct; assuming RIP-relative for all the global variables, the first 9 instructions are 7 bytes each and the last one 4, giving a total of 67 bytes.
You can cherry-pick examples all you want, but I think this is a great example of why you should use Asm: It's not even trying to be optimised, and it still somehow manages to be smaller overall!
Try to optimise it, however, and you can definitely beat GCC...
GCC may be clever to turn (-3) * x into x - 4 * x, but not clever enough to realise that turning 3 * x into x + 2 * x like it did for the other two "statements" and a subtraction would be shorter and faster because it means one less operation on the critical path.
I love this! I've been staring at it (on and off) most of the day, trying to understand what's going on. I see that the compiler has changed the order of execution of "add r14, 1". Would that be to avoid stalling the pipeline due to several rax-intensive instructions bunched together ("data hazard")? Would you be able to elaborate more on what's going on with the code and why it's smart?
r14 is not used at all in that snippet except for that one instruction; its increment can thus be put anywhere between the others, and where the compiler put it is as arbitrary as what a human would do in the absence of any further information. In any OoO architecture (for Intel x86, everything except early Atoms and the tiny embedded cores) the CPU will reorder instructions anyway and can look ahead several dozen instructions:
The repeated use of rax is also no problem because of register renaming. Despite two dependency chains specifying rax, the CPU can detect that they're independent and assign different physical registers for rax, allowing them to execute in parallel.
Sorry to rant for the nth time about this, but why does Chrome offer you to make this wonderful, CSS-free, content-oriented document "mobile friendly"? That is, why ask for permission? Can't browsers render documents as they see fit when there are no CSS rules? As far as I can tell, the only difference once you accept is more generous margins as well as a gray background for <code> elements... I'd say it does look more readable, maybe even on the desktop. Chrome, Firfox, IE, Safari, et al: be bold (or otherwise styled) with CSS-free documents!
I remember coming across this article back then, in 2001, or maybe it was even 2000. I found it fascinating at the time and wrote lots of little programs that talk to the syscall interface directly. It was a good learning experience.
I think the emphasis on the 0x80 interrupt was to draw parallels to the old DOS calls - int 21h - and make a point that nothing technically stops you from doing the equivalent on Unixy system. (A few years after this the sysenter thing came along and so now int 80h is a legacy path..)
Sadly a lost art, all of this. Talking about this is a great way to get blank stares from many people.
52 comments
[ 1.7 ms ] story [ 113 ms ] threadSee e.g. [1] for a more comprehensive overview concerning the handling of system calls on Linux.
[1] http://blog.packagecloud.io/eng/2016/04/05/the-definitive-gu...
http://web.archive.org/web/20120615223202/http://www.x86-64....
What is even worse is that I think Intel made the mistake of checking for canonical addresses and GP#ing on the SYSRET instruction itself.
I don't really understand this argument. If a few lines of c can generate dozens of lines of assembly, how can it not be faster to have the compiler write assembly for you?
Your comment focuses on how fast the compiler generates lines of assembly, after the programmer has constructed a solution.
But unless I have misunderstood the author was focusing on how fast the programmer can construct a solution.
The final step of depressing keys to input the solution into the computer can be sped up by using libraries and perhaps macros.
It's also possible to write programs ("code generators") that generate lines of assembly based on a "template". And that "template" need not be written in C or any other popular computer language.
It could be some "DSL", a shorthand for assembly, that the programmer creates for himself.
djb's qhasm might be considered an example.
While it may be generally true that C requires less key punching than assembly (certainly it requires fewer "lines"), this does not mean constructing a solution in C necessarily comes faster than constructing one in assembly... if the programmer is familiar and efficient working with assembly.
edit: idk how to put a asterisk up there in the example above
I have no idea where people get the idea that one line of C equals 10 lines of asm.
PS Assembly is not hard at all, and is good to understand/read assembly when coding C. The only thing is that it is a bit rough to start coding in it (no good tutorials is one of the two-three difficulties).
Because coding in asm takes more than just the raw instructions.
x86 doesn't have many registers so you're shifting around stuff across registers and memory all the time, not to mention stack setup/teardown and the error checking (e.g. div0, overflow) you are supposed to do after operations.
I also disagree with "shifting around stuff across registers and memory all the time". An "average" function doesn't really have all that many variables and x86 has 8 GP registers (16 on amd64). You don't even have to declare variables, you just take a register (and/or a location in memory) and use it as you wish.
I'm now sitting here thinking what can be done in C that would end up being a lot of instructions, and i can't think of anything. Going over an array does not, neither does recursion (recursion is just syntactic sugar in the end). What else.. i can't think of anything. Macros ? But macros are cheating in this comparison (not to mention that the flat assembler has actual macros, that are extremely powerful)
This is one of the things where (x86) Asm is definitely easier: divide overflows naturally cause an exception which will get handled automatically, and add/subtract/multiply overflows can be checked with one extra (JO or JC) instruction. In C, this is all undefined behaviour.
I'm now sitting here thinking what can be done in C that would end up being a lot of instructions, and i can't think of anything.
Crypto algorithms come to mind, but like you said, macros can take care of that easily... and on this page is an AES-256 CFB implementation in Asm that is around the same size of source as others in C (but much smaller binary):
http://reimagery.com/fsfd/encrypt.htm
I'm guessing this article is somewhat out of date. The systems where performance matters most - those most likely to be constrained in some way, where coding in assembly would yield the most benefit - are mobile platforms, which invariably means ARM not x86.
ARM became dominant in mobile computing, which was barely a glimmer back in 2001. x86-64 became commonplace. Just targeting Intel and ARM processors now necessitates writing code for four architectures: x86, x86-64, AArch32 and AArch64 for a full range of compatibility.
While MS-DOS syscall numbers were documented and often _required_ assembly to use, the opposite is true on modern Windows - Windows system call numbers are undocumented and change from release to release, requiring programmers to link against Kernel32.dll or equivalent to stably call into the OS.
Compiler technology has advanced significantly, and processor manufacturers now often optimize towards patterns employed by compilers rather than by humans (case-in-point: the x86 "loop" instruction, while extremely convenient for handwritten code, is significantly less performant than a cmp-jmp loop).
Code has gotten more and more complex. Whether this is a good thing or bad, the reality is that hundreds of millions of lines of assembly would be required to replicate complex modern programs like web browsers - projects of that scale will always require powerful HLLs to manage abstraction, something that assembly does not and cannot provide on its own.
View this page as what it is - a historical artifact. Although assembly programming definitely still has its uses, the arguments this page makes in favor of it are largely no longer relevant.
UNIX kernels could run on a variety of processors, including ARM; UNIX was a relative latecomer to x86.
As for Windows kernels, both then and now, UNIX kernels are open source and do not deliberately obscure system calls.
It's not unreasonable for someone to argue the benefits in writing programs for UNIX.
"View this page as what it is..."
It's one of the few pages written about UNIX assembly. That's how I view it. Years later it was put into The FreeBSD Handbook.
"... aiming to provide performant tools ..." (1995) - https://books.google.com/books?id=uZjYdk4bQgkC&pg=PA1&dq=per...
definitions for different aspects of 'peformant' (1998) - https://books.google.se/books?id=FYkmwYKBoCwC&pg=PA235&dq=%2...
"The network and hierarchical data models, introduced in the 1960's, provide a very performant data organization for well-defined, update-intensive transaction processing of large volumes of data at the cost of conceptual complexity" (1993) - https://books.google.com/books?id=Ro3zjjTNE9UC&pg=PA327&dq=p...
Here's the n-grams trend. https://books.google.com/ngrams/graph?content=performant&yea... .
There are many domain-specific terms which have not yet made it to general dictionaries. For example, from your comments, I see you used the word 'multicast', This is not in Merriam-Webster, at http://www.merriam-webster.com/dictionary/suggestions/multic... . What makes it a word but not performant? Is "zero-copy" a word? "smarthost"? "type-punning"? "upstream"?
Software can be big or small, fast or slow, efficient or inefficient (for combinations of size and speed.) I don't think we need any more adjectives beyond those.
Instead of "significantly less performant", you could just say "much slower". (Note that LOOP was much slower on older Intel CPUs, but was the same as the longer sequence on AMD's. It would not surprise me if newer Intel ones now decode LOOP into the same sequence of uops. In any case, unless the loop is a tiny one, what happens in the body is likely to make a much more significant difference, especially if cache misses are involved.)
Some interesting reading on why LOOP was slower on Intel CPUs --- because Windows 95's timing loop used it (observe the lack of "performant" in any of those posts): https://groups.google.com/d/topic/alt.windows98/liuMAIctHRg
Also, I think performant is often used as a synonym for "fast enough", which is slightly different than "fast."
In looking around just now, economics seems to prefer the second definition, which is why I added it. All of the uses I found are compatible with this definition.
What's the definition of "object oriented" again?
Languages evolve. Whether for better or worse is up to the reader. If it weren't for evolving languages, we'd all still be writing like Shakespeare.
[0]: https://en.wikipedia.org/wiki/Phonological_history_of_Englis...
For example:
- observing => observant =?= observing well (yes, exists in this meaning)
- informing => informant =?= informing well (does not exist as adjective, but could in the future?)
- thinking => thinkant =?= thinking well (does not exist at all, but could in the future?)
Scientist, doctors, lawyers, and other engineering disciplines use precise words. Software engineers should too.
As more and more people use "performant" fewer and fewer people will understand what good performance actually means. I wouldn't be surprised if this encourages a culture of wasteful premature optimization.
On the other hand, when using Asm you can often leverage techniques not possible in HLLs to avoid complexity and abstraction. This is a fact which seems not commonly known, even in CS courses involving Asm, as those tend to be more about inspecting compiler output and the few "write in Asm" exercises are mimicing code that a compiler would generate, which IMHO misses the whole point of writing in Asm.
Here is an operating system containing many nontrivial applications, all written in 100% Asm:
http://www.menuetos.net/
Among other things, it contains a minimal yet functional web browser (around the same level of minimalism as Dillo or NetSurf, i.e. no JS or fancy CSS), and the whole package fits on a single floppy disk.
An entire OS with kernel, drivers, and applications in total can be smaller than a "Hello World" application in a "modern" programming language (which depends on countless other external libraries too, including an OS several GB or more.) It really makes you think.
Here's a small snippet I found from draw.asm:
72 bytes.Here's what GCC generates with -Os for the same code:
36 bytes.As a bonus, GCC with -O2:
52 bytes.So, not only does GCC hugely beat this snippet of handwritten Menuet assembly in code size, it can even do so handily while folding all the repeated additions into LEA and eliminating most of the loads.
This is an excellent example of why you shouldn't write in assembler.
However assembly programmers will also instinctively avoid things that result in massive amounts of code because they have to manually write every line of it. Compared to C++ where templates are effectively a code generation meta-language. C makes you work a lot harder to generate huge amounts of machine code - and its binaries tend to be smaller because of it.
I don't think that's correct; assuming RIP-relative for all the global variables, the first 9 instructions are 7 bytes each and the last one 4, giving a total of 67 bytes.
You can cherry-pick examples all you want, but I think this is a great example of why you should use Asm: It's not even trying to be optimised, and it still somehow manages to be smaller overall!
Try to optimise it, however, and you can definitely beat GCC...
...with 32 bytes. This human gives, for "-O2", the following 41-byte snippet: GCC may be clever to turn (-3) * x into x - 4 * x, but not clever enough to realise that turning 3 * x into x + 2 * x like it did for the other two "statements" and a subtraction would be shorter and faster because it means one less operation on the critical path.http://blog.stuffedcow.net/2013/05/measuring-rob-capacity/
The repeated use of rax is also no problem because of register renaming. Despite two dependency chains specifying rax, the CPU can detect that they're independent and assign different physical registers for rax, allowing them to execute in parallel.
I think the emphasis on the 0x80 interrupt was to draw parallels to the old DOS calls - int 21h - and make a point that nothing technically stops you from doing the equivalent on Unixy system. (A few years after this the sysenter thing came along and so now int 80h is a legacy path..)
Sadly a lost art, all of this. Talking about this is a great way to get blank stares from many people.