X86 Assembly Code Resources
The main reason for knowing assembly these days is not so much writing it from scratch, as knowing what's going on when your debugger won't show you source code. But it's also handy for systems programming when you need to execute instructions that aren't available from a high-level language, for example programming the Memory Manager Unit, and of course for writing particularly fast or compact object code libraries.
Books
Pentium Processor Optimization Tools by Michael L. Schmit
This is perhaps the best introduction to x86 assembly code that I have come across. Schmit's writing is clear and easy to read.
It explains that the way to get Pentium assembly code to run the fastest is by treating the Pentium as a RISC processor. If you limit yourself to a certain subset of the Pentium's instructions, it acts just like any RISC CPU. This is done by avoiding instructions that would stall the processor's pipeline.
Intel cheated, in that they got the performance of a RISC, while maintaining binary compatibility with CISC code. This can be handled transparently by the compiler - unless you yourself are writing a compiler's code generator, or writing assembly libraries to speed up some operation.
Unfortunately, the book is from 1995, and only covers the original Pentium - not even MMX! So many of its methods don't apply to today's processors, and of course it doesn't cover the instructions that are only implemented in newer CPUs.
The book comes with a floppy disk containing a tool that runs under DOS, that will examine your assembly code and make suggestions as to how to rewrite it to get it to run faster. Unfortunately it's a limited version, and Schmit's company Quantasm is no longer in business, so the full version is no longer for sale.
Websites
Two nice items in Wikibooks:
It has a PDF version for nice printing.
One unique thing about GAS (and GCC inline assembly), is that it is an "optimizing assembler". Rather than manually choose the registers to be used for particular data items, you can declare your data as needing to be in registers, but not particular ones. You place certain constraints on each register data item, and then GAS takes care of finding the best registers to use.
Unfortunately, this can make learning GNU Assembler a little confusing.
This covers a number of other assemblers in addition to GAS.
The Art of Assembly Language Programming by Randall Hyde
Webster - The Place on the Internet to Learn Assembly
How to optimize for the Pentium family of microprocessors
Software Optimization Resources
x86 Assemblers
GNU Binutils - includes the GNU Assembler
Because GAS is primarily meant as a code generator for GCC, it lacks many features that human assembly code programmers need, such as rigorous error checking. Nasm provides all those features.