Live data from Hacker News

{n} times faster than C

owen.cafe

1–10 of 249 posts

Re: {n} times faster than C

#3
There's an error in the pseudocode.

      cmp     ecx, 's'            #   if (c == 's')
      jne     loop                #     continue
      add     eax, 1              #   res++
      jmp     loop                #   continue
should be

      cmp     ecx, 's'            #   if (c != 's')
      jne     loop                #     continue
      add     eax, 1              #   res++
      jmp     loop                #   continue

Re: {n} times faster than C

#5
A while back, I wrote a UTF-8 decoder in Common Lisp, targeting SBCL (it already has one built in, this was an exercise). Pretty much all of the optimization win (after the obvious low-hanging fruit) was structuring the code so that the compiler would generate cmov* instructions rather than branches.

Re: {n} times faster than C

#6
Rearranging branches (and perhaps blocks too?) will definitely be done if you are building using FDO, because without FDO (or PGO) the compiler has no idea how likely each branch is to be taken. Cmov can also be enabled by FDO in some cases.

However, whether or not using cmov is effective compared to a regular test/jump is highly dependent on how predictable the branch is, with cmov typically performing better when the branch is very unpredictable. Since they got a 6x speedup with cmov, I assume that their test input (which isn't described in the post, and is also not in their GitHub repo) consists of random strings consisting almost entirely of s and p characters. There's nothing wrong with this, but it does make the post seem a little misleading to me, as their clever speedup is mostly about exploiting an unmentioned property of the data that is highly specific to their benchmark.

Re: {n} times faster than C

#8
post #5

A while back, I wrote a UTF-8 decoder in Common Lisp, targeting SBCL (it already has one built in, this was an exercise). Pretty much all of the optimization win (after the obvious low-hanging fruit) was structuring the code so that the compiler would generate cmov* instructions rather than branches.

What's some examples of the code changes that you made? And did you just do repeated disassemblies of the functions to see that it was using the correct instructions, or did you do some benchmarking to show your changes were actual improvements?

Re: {n} times faster than C

#9
This code screams for SIMD! If you can change the prototype to take an explicit length, you could easily read and process 16 bytes at a time (the compares will give you values you can just add and subtract directly). Heck, even calling strlen() at the function's start to get the explicit length would probably be worth it.

Re: {n} times faster than C

#10
I threw together a quick risc-v vectorized implementation:

    size_t run(char *str) {
            uint8_t *p = (uint8_t*)str;
            long end = 0;
            size_t res = 0, vl;
            while (1) {
             vl = __riscv_vsetvlmax_e8m8();
                    vuint8m8_t v = __riscv_vle8ff_v_u8m8(p, &vl, vl);
                    end = __riscv_vfirst_m_b1(__riscv_vmseq_vx_u8m8_b1(v, '\0', vl), vl);
                    if (end >= 0)
                            break;
                    res += __riscv_vcpop_m_b1(__riscv_vmseq_vx_u8m8_b1(v, 's', vl), vl);
                    res -= __riscv_vcpop_m_b1(__riscv_vmseq_vx_u8m8_b1(v, 'p', vl), vl);
                    p += vl;
            }
            vl = __riscv_vsetvl_e8m8(end);
            vuint8m8_t v = __riscv_vle8_v_u8m8(p, vl);
            res += __riscv_vcpop_m_b1(__riscv_vmseq_vx_u8m8_b1(v, 's', vl), vl);
            res -= __riscv_vcpop_m_b1(__riscv_vmseq_vx_u8m8_b1(v, 'p', vl), vl);
            return res;
    }

Here are the results from the above, the switch and the table c implementation, ran on my mangopi mq pro (C906, in order rv64gc with rvv 0.7.1, and a 128 bit vector length):

    switch: 0.19 Bytes/Cycle
    tbl:    0.17 Bytes/Cycle
    rvv:    1.57 Bytes/Cycle (dips down to 1.35 after ~30 KiB)
Edit: you can go up to 2/1.7 Bytes/Cycle, if you make sure the pointer is page aligned (and vl isn't larger than the page size), see comments
Post reply on HN