Readit News logoReadit News
tptacek · 8 years ago
Isn't this basically a web server for Linux written in C, except with absolutely no standard library optimizations?

Yes, it's assembly, but it's basically a thin script over a set of string library functions which are themselves just naive versions of what's already in libc. Most of this is just strcpy/strcat/strchr/strstr; the only reason it's not one giant stack overflow is that it doesn't actually implement most of HTTP (for instance: nothing reads Content-length, and everything works off a static buffer that is sized to be as large as the maximum request it will read). What's the point of writing assembly if you're going to repeatedly scan to the end of strings as you try to build them?

I'm not taking away anything from the project as an exercise. It's neat! But like: it's not something you'd ever want to use, or a productive path to go down to make a good webserver.

userbinator · 8 years ago
What's the point of writing assembly if you're going to repeatedly scan to the end of strings as you try to build them?

I'd have to benchmark to make any claims about speed, but from my past, quite extensive experience with using Asm, it's not uncommon for even a simple/naive/asymptotically less efficient algorithm written in Asm to outperform a more complex/clever/asymptotically efficient one in an HLL at the problem sizes encountered in practice, simply by virtue of the constant factor being much smaller. For the same reason a bubble/insertion sort in Asm can easily beat the standard library sort if your inputs aren't that big.

Of course if you combine Asm with efficient algorithms (which can sometimes be easier to write than in a HLL), then you can get much closer to how much the CPU can actually do.

MaxBarraclough · 8 years ago
But for web servers, it's not about micro-optimisations, but having an architecture capable of efficiently handling concurrency, no?

nginx and Apache are both written in C, but nginx can outperform/outscale Apache. That's because of high-level architectural differences, not from tweaking assembly code. An HTTP server isn't doing much in data-transformation/algorithmic terms, it just has to scale efficiently.

If your code is synchronous and non-parallel, it's going to lose every time, even if it's highly tuned assembly.

pcwalton · 8 years ago
> it's not uncommon for even a simple/naive/asymptotically less efficient algorithm written in Asm to outperform a more complex/clever/asymptotically efficient one in an HLL at the problem sizes encountered in practice, simply by virtue of the constant factor being much smaller

You're conflating two different things: constant factors based on algorithm choice and the merits of hand-written assembler. It's true that sometimes asymptotically worse algorithms beat asymptotically better ones in practice. It's a lot less clear that humans can regularly beat compilers for typical scalar code.

1996 · 8 years ago
I do not care about good. I care about good enough. I would want to use that, on systemd nspawns (or whatever the current name of systemd container is) to see if I could get more performance than nginx for APIs returning fixed length things.

I wonder how efficient it would be to spawn a few hundreds.

saagarjha · 8 years ago
As I've mentioned in another comment, I wouldn't place any bets on the efficiency of this code. It's written with easy of understanding in mind at the expense of code size and speed.
mabynogy · 8 years ago
Nobody mentioned rwasa so I do it (an HTTPs webserver written in asm):

https://2ton.com.au/rwasa/

I proposed to debian (on an IRC channel for that) to make a package for rwasa but I've been told that "nobody does asm in 2017". I wonder if it's still true in 2018.

shakna · 8 years ago
There are still programs available in Debian written in asm, but they tend to be exceptional.

Picolisp 64bit

Luajit (mostly)

lispre · 8 years ago
Really? Picolisp was build with asm? not C as SBCL
M00nF1sh · 8 years ago
FYI: nobody does asm in 2018
bumholio · 8 years ago
Sounds like an excelent front end, for best performance. For the back end stability is paramount, that's why I would always recommend to pair this with a shell script server: https://github.com/avleen/bashttpd

Bash is undeniably the language with the highest possible stability, you can literally feel the stability and security when a file is served and the hard drives go crazy.

tptacek · 8 years ago
Why would you assume best performance? Web server performance has more to do with I/O strategy than it does with raw compute.
mst · 8 years ago
Re-read the comment you're replying to. It's a joke.
saagarjha · 8 years ago

    %macro stackpush 0
        push rdi
        push rsi
        push rdx
        push r10
        push r8
        push r9
        push rbx
        push rcx
    %endmacro
    
    %macro stackpop 0
        pop rcx
        pop rbx
        pop r9
        pop r8
        pop r10
        pop rdx
        pop rsi
        pop rdi
    %endmacro
I guess that's one way of saving registers. Not particularly efficient, I guess, but it works…

userbinator · 8 years ago
You can thank AMD for mysteriously removing PUSHA/POPA from the opcode map in 64-bit mode (not even replaced by any new useful instructions, just made invalid.)
simcop2387 · 8 years ago
I don't think they've given reasons for it, but it's likely because they just completely destroy any kind of out of order execution. They'd end up requiring a fence before either instruction to ensure that you only push or pop the correct values and you couldn't speculate at all past them. Combine that with more than double the size of needed space (double number of registers, and double the size of them) it seems pretty wasteful. And then there's the fact that modern compilers are likely already avoiding those instructions because of the timing of saving extra registers that you aren't using in a given function, it probably just doesn't make much sense to keep them anymore.
agumonkey · 8 years ago
I wonder if there are cpus with 1-instr state save

ps: thank you all for the answers

rwmj · 8 years ago
ARMv7 (not AArch64) has the STM instruction that lets you push a register set, selected by bitmask. eg:

    STMFD sp!, {r3-r7,lr}
(I believe the "FD" suffix is to do with the stack growing down - "full descending")

Of course this is just implemented with microcode so it's not really any more efficient than a series of PUSHes, except there's a bit less I-cache pressure.

PeCaN · 8 years ago
Itanium does this automatically. You declare what registers you're using in the the function prologue and it handles popping/pushing/renaming. No register window exceptions too.
pjc50 · 8 years ago
ARM has stmdb / ldmia which appear in practically every function prolog/epilog. It also has "banking" systems to swap to a different set of registers on interrupts, which saves time and stack space.
pwg · 8 years ago
Another one: the old Z-80 CPU had two sets of registers, and a single instruction to swap between the main and the alternate register set.

http://landley.net/history/mirror/cpm/z80.html (search for "alternate registers").

saagarjha · 8 years ago
Even if there was one, you'd still be saving registers unnecessarily. If you only clobber one register in a procedure there's no need to save and restore all of them.
kijiki · 8 years ago
x86 has LOADALL and SAVEALL.

https://en.wikipedia.org/wiki/LOADALL

elcritch · 8 years ago
BeagleBone’s processor the AM335x PRU processors by TI has two “PRU” coprocessors. They have “xin” and “xout” instructions that can copy a register bank in 1 cycle. Pretty handy for quick data gathering but tricky to use in C.
blattimwind · 8 years ago
Sure. It's called windowed registers and you have one instruction each to move the window right/left.
inamberclad · 8 years ago
This is some of the best written assembly I've seen. I can actually follow what's going on.
k1ns · 8 years ago
This is really cool. I admire your bravery.

I noticed that certain options are hardcoded (such as the port number). As someone with very little experience in assembly, how difficult would it be to make this dynamic via environment variables?

tormeh · 8 years ago
Slightly off topic, but I don't get environment variables. To me they look like implicit options that are based on what shell session you're currently in. Stuff that hangs around waiting to kill you when you're not looking, like a cecum.

Why would anyone want environment variables when you can have config files or explicit options instead?

jolmg · 8 years ago
They have their advantages over config files and command line options. They're better than config files because you can ask for different behavior in different invocations, and they're better than command line options because it allows you to control a program even when you don't invoke it directly.

Consider, for example, wanting different options for less between when it's called by you, or by man, or by git, or by journalctl, or any other utility.

    alias man='LESS="X$LESS" man'
    alias journalctl='LESS="S$LESS" journalctl'
It's also useful when you want all processes that follow from an invocation to share a certain configuration, and when they follow from another invocation to have another. An example of this is $DISPLAY. I startx on my tty1 and that invokes the Xorg server, exports DISPLAY with the display of the server, typically :0, and executes my window manager. When I press certain shortcut keys, applications will be called, and they, in turn, might call other applications, and they all need to know that when they open up a window it should be on display :0. If I login remotely through ssh, forwarding my X11 service, the applications that I call on the same machine where other processes of the same applications opened their windows on :0, should now open their windows on localhost:10, so I get to see them on the laptop I'm logging in from. If someone else wants to use their account on the same computer, they might switch to tty2 and startx themselves, and all the applications they open should open on their Xorg server process with display :1, not mixing with the other windows I opened locally or on the laptop.

This is also why $LANG, $HOME, $USER, and $TERM are useful. They're stuff that should shared by process trees. Their purpose can't be fulfilled by configuration files or command line options.

lewisinc · 8 years ago
It's harder to accidentally commit a running process's environment to version control ;)
easytiger · 8 years ago
> Why would anyone want environment variables when you can have config files or explicit options instead?

If you have an underlying .so used by an application that hasn't implemented optioning at init of said library it can then let you control things in something that has no runtime configuration source. I've written several such libraries. Also good for controlling things you override with LD_PRELOAD

geezerjay · 8 years ago
Config files set per user/system options whether they are set locally or globally, while env variables set per options per shell.

Global options > user options > env variables > command line parameters

tlarkworthy · 8 years ago
I am very pro environment vars and consider files an anti pattern. Files are like globals, sibling processes end up sharing state, plus they survive longer than their use case.

Env variables cascade, which is why they have wider uses than program args. You can organize a system as a group of processes and envs are a good way to share state and abstract out the details like prod vs staging without affecting the program implementation.

kazinator · 8 years ago
Explicit options require the cooperation of every intermediate program. If program D is called by C, which is called by B, which is called by A, and D gets a new option that we need to use, we have to modify C to pass it to D, then B to pass it to C, and A to pass it to B. Or we could just set an environment variable.

Configuration files don't let us override individual variables easily, in different invocations of the program. We have to generate a custom version of the configuration file for that job and pass that file's name to it.

Environment variables are only visible to children, so they are inherently secure; we don't worry whether permissions are too loose on an environment variable so that another user could see it or modify it. There is no such thing.

_bxg1 · 8 years ago
In addition to the other advantages mentioned here, environment variables serve as a common language that all programs can understand, in theory. They're also less complex to access than reading and parsing a file (less important for, say, Node.js than C++, but).
rixed · 8 years ago
> Why would anyone want environment variables

> when you can have config files

You still need a way to tell your program where the config file is.

Environment variables are just like a configuration file that's automatically opened by the OS when a process start and automatically parsed as a set of named strings by the libc. In the many cases where one does not need more complex data structures than that, then to introduce a config file is just making things harder.

That being said, I wish envvars had namespaces of some sort.

alxlaz · 8 years ago
It wouldn't be that difficult, it would only require some libc-related magic (but I haven't read much of the asmttpd source code to know how much of it is in there). IIRC, environment variables are passed from the parent process to the child in the form of a "naive" array of strings, so the truly unpleasant parts of this, like parsing text from a config file, are already taken care of.
jxub · 8 years ago
Well, it's not mine but I'm sure it certainly was quite a challenge to build, props to the author for accomplishing that.
haolez · 8 years ago
That's probably not a good model to fit in assembly code. It's easier to have a wrapper in another language as the launcher and have it set the variables in the assembly code and build the binary.
getcrunk · 8 years ago
Why?

Dead Comment

laurent123456 · 8 years ago
That's impressive, but I couldn't find the reason for it. Was it done just for the challenge, or can their be a use for this kind of server?
nemasu · 8 years ago
Yup, initially it was for the challenge and to learn some x86_64 assembly. As for usefulness, personally I use it all the time when I need to serve some static content and don't want to set up Apache or nginx or something else.
yjftsjthsd-h · 8 years ago
It's tiny; might have embedded use?
tambourine_man · 8 years ago
Embedded x86_64? Is that a common thing?
ashleyn · 8 years ago
Security is enough of a consideration that any well-established webserver without corporate or foundational backing is instantly unsuitable for serious use.
stingraycharles · 8 years ago
Definitely looks like it. No library dependencies such like libc, makes it an ideal use case for embedded software.