Slashdot Mirror


Cliff Click's Crash Course In Modern Hardware

Lord Straxus writes "In this presentation (video) from the JVM Languages Summit 2009, Cliff Click talks about why it's almost impossible to tell what an x86 chip is really doing to your code due to all of the crazy kung-fu and ninjitsu it does to your code while it's running. This talk is an excellent drill-down into the internals of the x86 chip, and it's a great way to get an understanding of what really goes on down at the hardware and why certain types of applications run so much faster than other types of applications. Dr. Cliff really knows his stuff!"

18 of 249 comments (clear)

  1. Fast forward... by LostCluster · · Score: 5, Informative

    I can't say I've WTFV like I usually RTFA before you get to see it... but I can tell you this: The first four minutes of the video are spent asking which topic the room wants to see. No need to watch that part. Then it gets more interesting.

    1. Re:Fast forward... by Brian+Gordon · · Score: 5, Informative

      A little javascript-fu reveals that the video player points to a file (at http://flv.thruhere.net/presentations/09-sep-JVMperformance.flv) on some poor guy's machine through a dynamic DNS service! I hope somebody grabbed a copy before he (or slashdot) took his server down.

    2. Re:Fast forward... by pyrrhonist · · Score: 3, Informative

      some poor guy's machine through a dynamic DNS service!

      Some poor guy? It's on an Amazon EC2 server!

      $ host flv.thruhere.net
      flv.thruhere.net has address 67.202.36.223
      $ host 67.202.36.223
      223.36.202.67.in-addr.arpa domain name pointer ec2-67-202-36-223.compute-1.amazonaws.com.

      --
      Show me on the doll where his noodly appendage touched you.
    3. Re:Fast forward... by Brian+Gordon · · Score: 5, Informative

      You've done it! Interested slashdotters can download the video file at this link:

      http://67.202.36.223/presentations/09-sep-JVMperformance.flv.

      Good detective work, partner!

    4. Re:Fast forward... by iammani · · Score: 3, Informative

      Mirror available at http://www.mediafire.com/?j21t2ynnnzn

      And please stop hitting the server liked in GP's post. The poor server hardly sustains 30 KB/s.

    5. Re:Fast forward... by iammani · · Score: 3, Informative

      Or type http://www.infoq.com/resource/presentations/click-crash-course-modern-hardware/en/slides/1.swf

      And change the the number at the end to change slides

      PS: I am no good in javascript, but the above, in FF 3.5/Linux, just displayed a page saying "true"

  2. Re:Code in high-level by Sycraft-fu · · Score: 4, Informative

    Also either start with the assembly the compiler generates, or at the very least make sure to bench your own against what it makes. The Intel Compiler in particular is extremely good at what it does. As such, it is worth your while to see what its solution to your problem is, and then see if you can improve, rather than assuming you are smarter and can do everything better on your own.

    Of course all that is predicated on using a profiler first to find out where the actual problem is. Abrash accurately pointed out years ago that programmers suck at that. They'll spend hours making a nice optimized function that ends up making no noticeable difference in execution time.

  3. Re:Code in high-level by marcansoft · · Score: 3, Informative

    Coding in x86 ASM is never fun. Weird and odd and masochistically pleasurable for some, maybe, but not fun. Other architectures, on the other hand (like ARM), can be fun. x86-64 manages to increase the "funness" value somewhat, but I still wouldn't quite qualify it as "fun".

    On the other hand, it's very true that knowing some ASM can help you write code that the compiler will translate into better assembly code, without going through all of the trouble yourself.

  4. Re:Premature optimization is evil... and stupid by Rockoon · · Score: 3, Informative

    Using shift to multiply is often a great idea on most CPUs.

    Which CPU's are those? The fastest way to multiply today on AMD/Intel is to use the multiply instructions.

    Didn't know that? yeah... it seems like only assembly language programs know this.

    --
    "His name was James Damore."
  5. It's not just x86 by RzUpAnmsCwrds · · Score: 3, Informative

    Features like out of order execution, caches, and branch prediction/speculation are commonplace on many architectures, including the next generation ARM Cortex A9 and many POWER, SPARC, and other RISC architectures. Even in-order designs like Atom, Coretex A8, or POWER6 have branch prediction and multi-level caches.

    The most important thing for performance is to understand the memory hierarchy. Out-of-order execution lets you get away with a lot of stupid things, since many of the pipeline stalls you would otherwise create can be re-ordered around. In contrast, the memory subsystem can do relatively little for you if your working set is too large and you don't access memory in an efficient pattern.

  6. Re:Premature optimization is evil... and stupid by marcansoft · · Score: 4, Informative

    Which CPU's are those?

    Those with a barrel shifter.

    The fastest way to multiply today on AMD/Intel is to use the multiply instructions.

    Then someone needs to beat the GCC developers with a cluestick.
    $ cat test.c
    int main(int argc, char **argv) {
                    return 4*(unsigned int)argc;
    }
    $ gcc -march=core2 test.c -o test
    $ objdump -d test ...
    00000000004004ec <main>:
        4004ec: 55 push %rbp
        4004ed: 48 89 e5 mov %rsp,%rbp
        4004f0: 89 7d fc mov %edi,-0x4(%rbp)
        4004f3: 48 89 75 f0 mov %rsi,-0x10(%rbp)
        4004f7: 8b 45 fc mov -0x4(%rbp),%eax
        4004fa: c1 e0 02 shl $0x2,%eax
        4004fd: c9 leaveq
        4004fe: c3 retq
        4004ff: 90 nop

    yeah... it seems like only assembly language programs know this.

    I program in assembly language, but not for x86. I usually program in ARM, which always has a barrel shifter. I guarantee shifts are faster than multiplies there.

  7. Re:Premature optimization is evil... and stupid by tomtefar · · Score: 3, Informative

    I have the following sticker on top of my display: "Make it work before you make it fast!" Saved me many hours of work.

  8. Re:It's just outdated knowledge by Cassini2 · · Score: 3, Informative

    I'm definitely no expert on x86, but my impression was that precisely because of this trick that everyone does, modern CPUs still do xor reg,reg at least as fast as moving 0.

    You are correct. XOR reg,reg was such a common instruction on the x86, that essentially it became the special case CLR instruction. Essentially, if you see a CLR instruction on an x86 assembly printout, it is the XOR instruction in disguise. The x86 has no CLR instruction.

    Ok well that's changed now. Our more complex modern CPUs have special logic for clears, and doing a move to the register with 0 is faster. So it was a time limited trick, useful back when he started doing it, but no longer something worth trying.

    Essentially, all current "simple" CPU instructions execute with the same speed. However, the XOR instruction is still faster than the MOV instruction because of instruction bandwidth and cache effects. Most code today is limited by cache and bandwidth limits, like the need to load instructions into the instruction decode pipeline immediately after a jump instruction. The MOV reg, 0 immediate move instruction is a two-byte instruction, and the XOR reg, reg instruction is a one-byte instruction. As such, in real code, the XOR instruction is usually slightly faster, because it results in smaller code.

    Additionally, all of the modern x86 CPU implementations special case the XOR reg,reg instruction into a MOV reg, 0 immediate move instruction inside the instruction decode stage anyway. As such, no significant functional difference exists. The only case where a move instruction is quicker is when the condition codes are propagating a side-effect via the condition code registers. Thus, in theory:
    ADD AL, AH
    MOV CL, 0
    JC somewhere

    should execute quicker with a MOV instruction as opposed to a XOR instruction. However, in practice, this piece of code:
    XOR CL, 0
    ADD AL, AH
    JC somewhere

    executes with exactly the same speed, because the out-of-order execution units inside the x86 automatically optimize the code and make it equivalent. As such, you are best with the "short small" code, which means that the XOR reg, reg instruction is still the fastest way to do a register clear.

  9. rule of the code by Bork · · Score: 3, Informative

    Just write good clean code that works properly first. The only time you optimize is after it has been profiled to see if there are troublesome spots. The way CPUs run and how compilers are designed, there is very little need to do optimization. Unless you have taken some serious courses of how the current CPU’s work, you efforts will mostly result in bad code that gains you nothing in respect in speed. Your time is better spent on writing CORRECT code.

    The compilers are very intelligent in proper loop unrolling, rearranging branches, and moving instruction code around to keep the CPU pipeline full. They will also look for unnecessary/redundant instruction within a loop and move them to a better spot.

    One of the courses I took was programming for parallelism. For extra credit, the instructor assigned a 27K x 27K matrix multiply; the person with the best time got a few extra points. A lot of the class worked hard in trying to optimize their code to get better times, I got the best time by playing with the compiler flags.

  10. Re:Code in high-level by TheRaven64 · · Score: 4, Informative

    One of the biggest drawbacks of a language like C (and even more C++, and even more Java), is that they don't give you a whole lot of control of how stuff is arranged in memory

    I'd say this is more of a C/C++ problem than a Java problem. Or, rather, they are different problems. The problem with C and C++ is that they do give the programmer a whole lot of control about how things are arranged in memory. They don't, on the other hand, give the compiler a lot of freedom to rearrange things.

    Java, on the other hand, uses the Smalltalk memory model and so the compiler (and/or JVM) is free to rearrange things in memory as much as it wants to (whether it does, of course, is a matter for the compiler writer). For example, a Java compiler that notices that you are doing the same operation on three instance variables is free to put them next to each other aligned on a 128-bit boundary with some padding at the end so that you can easily use vector instructions on them, even if they were originally declared in different classes. A C compiler can not do this with structure fields.

    If you really care about alignment in C, you are free to use valloc() to align on a page boundary and then subdivide the memory yourself. Most of the time, however, it's not worth the effort.

    --
    I am TheRaven on Soylent News
  11. Re:Code in high-level by TheRaven64 · · Score: 3, Informative

    The calling convention is complicated, but it's nowhere near as different as IA32 calling conventions between platforms. Linux and FreeBSD, for example, use different rules for when to return a structure on the stack and when to return it in registers on IA32, but they use exactly the same conventions (the SysV ABI) on x86-64.

    --
    I am TheRaven on Soylent News
  12. Re:Code in high-level by TheRaven64 · · Score: 4, Informative
    The GCC manual tells you everything you need to know. First you declare a vector type, so if you want four shorts representing an RGBA colour value , you declare a type like this:

    typedef short colour_t __attribute__ ((vector_size (4 * sizeof(short))));

    This will give you a 64-bit vector type, so you can fit one in an MMX register, or two in an SSE or AltiVec register. You can then create these and do simple operations on them. For example, if you wanted to add two together, you could do this:

    colour_t a = {1,2,3,4};
    colour_t b = {1,2,3,4};
    colour_t c = a + b;

    In this case, the add is constant so it will be evaluated at compile time, but in the case where a and b have unknown values GCC will emit either four scalar add operations or one 64-bit vector add.

    You can also pass them as arguments to vector intrinsics, which are listed in the manual under target-specific builtins. These correspond directly to a single underlying vector instruction, so if you look in the assembly language reference for the target CPU then you will find a detailed explanation of what each one does.

    Rather than declare vector types directly, it's often a good idea to declare unions of vector and array types. This lets you use the same value as both an array and a vector.

    I wrote a longer explanation a while ago.

    --
    I am TheRaven on Soylent News
  13. Re:Premature optimization is evil... and stupid by Rockoon · · Score: 4, Informative

    GCC is a big offender, thats true.

    This is one of the reasons that GCC sucks compared to ICC and VC++.

    Let me give you the facts as they are today. In isolation, both the shift instructions and the multiply instructions have the same latency and throughput, and are also performed on the same execution units.

    If this was the entire story, then they would be equal. Buts its not the entire story.

    The shift instructions only modify some of the flags in the flags register. Essentially, the shift instructions must do a read/modify/write on the flags. The multiplication instructions, however, alter the entire flags register, so only perform a write.

    "But Rockoon.. they are the same latency anyways, right?" .. yes, in isolation. But that read/modify/write cycle on the flags register prevents a hell of a lot of out-of-order execution.

    Essentially, one of the inputs to the shift instruction is the flags register so all prior operations that modify the flags register must be completed first, and no instruction following the shift that also partially modify the flags register can be completed until that shift is completed.

    In some code, it wont make any discernible difference, but in other code it will make a big difference.

    As far as that GCC compiler output.. thats code is horrible, and not just because its AT&T syntax.

    There are two alternatives here for multiplying by 4 that should be in competition here, and neither uses a shift.

    One is a straight multiplication (MASM syntax, CDECL):

    main:
    mov edx, [esp + 4] ; 32-bit version, so +4 skips the return address
    imul eax, edx, 4
    ret

    The other is leveraging the LEA instruction (MASM syntax, CDECL):

    main:
    mov eax, [esp + 4] ; 32-bit version, so +4 skips the return address
    lea eax, [eax * 4]
    ret

    The alternative LEA version on some processors (P4..), in isolation, is slower .. but it has the advantage that it uses different execution units on those very same processors, so might pair better with other stuff in the pipeline, and it doesnt touch the flags register at all.

    GCC is great at folding constants and such, even calculates constant loops at compile time.. but its big-time-fail at code generation. GCC is one of the processors that one optimization expert struggled with because he was trying to turn a series of shifts and adds into a single far more efficient multiplication.. the compiler converted it back into a series of shifts and adds on him. Fucking fail.

    --
    "His name was James Damore."