6 ms·
From the article: Lookup tables are always faster than calculation - is that true? I'd think that while in the distant past maybe today due to memory being much
by U1F984 1y ago
From the article: Lookup tables are always faster than calculation - is that true? I'd think that while in the distant past maybe today due to memory being much slower than CPU the picture is different nowadays. If you're calculating a very expensive function over a small domain so the lookup fits in L1 Cache then I can see it would be faster, but you can do a lot of calculating in the time needed for a single main memory access.
- haiku2077 1y ago> Lookup tables are always faster than calculation - is that true? I know it's not always true on the Nintendo 64, because it shared a single bus between the RAM and "GPU": https://youtu.be/t_rzYnXEQlE?t=94 https://youtu.be/t_rzYnXEQlE?t=94, https://youtu.be/Ca1hHC2EctY?t=827 https://youtu.be/Ca1hHC2EctY?t=827
- sumtechguy 1y agoBasically if the thing you are doing can be precomputed. Then you can use a lookup table. Then at that point do you have the space for it? Is the time to get it out of memory less than the operation you are caching in the lookup table. Then it could be a good candidate for a lookup table. Many times that can be true. But not always. Even if that is all true you can end up hurting something else because your lookup table evicted something else important from the l1/l2 cache. So you also have to test it in context.
- bayindirh 1y agoDepends on the hardware and what you are making with that hardware. Some processors can do complicated things stupidly fast (e.g. when SIMD done right), and for some hardware platforms, a mundane operation can be very costly since they are designed for other things primarily. My favorite story is an embedded processor which I forgot its ISA. The gist was, there was a time budget, and doing a normal memory access would consume 90% of that budget alone. The trick was to use the obscure DMA engine to pump data into the processor caches asynchronously. This way, moving data was only ~4% of the same budget, and they have beaten their performance targets by a large margin.
- bobmcnamara 1y agoI've run into this on PowerQuicc network processors. It was so handy having the packets(or at least header) dropped straight into the cache
- ay 1y agoYou will need to first sit and ballpark, and then sit and benchmark, and discover your ballpark was probably wrong anyhow:-) Some (for me) useful pointers to that regard for both: 1. https://www.agner.org/optimize/instruction_tables.pdf https://www.agner.org/optimize/instruction_tables.pdf - an extremely nice resource on micro architectural impacts of instructions 2. https://llvm.org/docs/CommandGuide/llvm-mca.html https://llvm.org/docs/CommandGuide/llvm-mca.html - tooling from Intel that allows to see some of these in real machine code 3. https://www.intel.com/content/www/us/en/developer/articles/tool/performance-counter-monitor.html https://www.intel.com/content/www/us/en/developer/articles/t... - shows you whether the above is matching the reality (besides the CPU alone, more often than not your bottleneck is actually memory accesses; at least on the first access which wasn’t triggered by a hardware prefetcher or a hint to it. On Linux it would be staring at “perf top” results. So, the answer is as is very often - “it depends”.
- bayindirh 1y ago...and we always circle back to "premature optimization is the root of all evil", since processors are a wee bit more intelligent with our instructions than we thought. :)
- mananaysiempre 1y agoNot that intelligent. If you have two loads and one store per cycle, then that’s it. (Recall that we have SSDs with 14 GB/s sequential reads now, yet CPU clocks are below 6 GHz.) Most of the computational power of a high-performance CPU is in the vector part; still the CPU won’t try to exploit it if you don’t, and the compiler will try but outside of the simplest cases won’t succeed. (Most of the computational power of a high-performance computer is in the GPU, but I haven’t gotten that deep yet.) I don’t mean to say that inefficient solutions are unacceptable; they have their place. I do mean to say that, for example, for software running on end-users’ computers (including phones), the programmer is hardly entitled to judge the efficiency on the scale of the entire machine—the entire machine does not belong to them. > We should forget about small inefficiences, say 97% of the time; premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%. A good programmer will not be lulled into complacency by such reasoning, he will be wise to look carefully at the critical code; but only after that code has been identified. D. E. Knuth (1974), “Structured Programming with go to Statements”, ACM Comput. Surv. 6(4).
- yorwba 1y agoIn this case, the lookup table is used for popcount, and there's a comment in the Z80 assembly that says "One lookup vs eight bit tests." If the code could make use of a hardware popcount instruction, the lookup table would lose, but if that isn't available, a 256-byte lookup table could be faster. So it's less "lookup tables are always faster" and more "lookup tables can be faster, this is one such case."
- ooisee 1y agoprobably fastest popcount in z80 that does not use aligned table, would be shift A through flag, then conditional INC C, unrolled, still slower than ld l,a: ld b,(hl).
- whizzter 1y agoThe article does mention cache friendly access patterns in the same context. But yes, you're right. Back when I started with optimizations in the mid 90s memory _latencies_ were fairly minor compared to complex instructions so most things that wasn't additions (and multiplications on the Pentium) would be faster from a lookup table, over time memory latencies grew and grew as clock speeds and other improvements made the distance to the physical memory an actual factor and lookup tables less useful compared to recomputing things. Still today there are things that are expensive enough that can be fit in a lookup table that is small enough that it doesn't get evicted from cache during computation, but they're few.
- devnullbrain 1y agoYou are correct and I've even ran into a situation where build-time evaluation was slower than runtime calculation, thanks to code size.
- mananaysiempre 1y ago> Lookup tables are always faster than calculation - is that true? Maybe on the Z80. Contemporary RAM was quite fast compared to it, by our sad standards. A table lookup per byte will see you hit a pretty hard limit of about 1 cycle per byte on all x86 CPUs of the last decade. If you’re doing a state machine or a multistage table[1] where the next table index depends on both the next byte and the previous table value, you’ll be lucky to see half that. Outracing your SSD[2] you’re not, with this approach. If instead you can load a 64-bit chunk (or several!) at a time, you’ll have quite a bit of leeway to do some computation to it before you’re losing to the lookup table, especially considering you’ve got fast shifts and even multiplies (another difference from the Z80). And if you’re doing 128- or 256-bit vectors, you’ve got even more compute budget—but you’re also going to spend a good portion of it just shuffling the right bytes into the right positions. Ultimately, though, one of your best tools there is going to be ... an instruction that does 16 resp. 32 lookups in a 16-entry table at a time[3]. So no, if you want to be fast on longer pieces of data, in-memory tables are not your friend. On smaller data, with just a couple of lookups, they could be[4]. In any case, you need to be thinking about your code’s performance in detail for these things to matter—I can’t think of a situation where “prefer a lookup table” is a useful heuristic. “Consider a lookup table” (then measure), maybe. [1] https://www.unicode.org/versions/latest/ch05.pdf https://www.unicode.org/versions/latest/ch05.pdf [2] https://lemire.me/en/talk/perfsummit2020/ https://lemire.me/en/talk/perfsummit2020/ [3] http://0x80.pl/notesen/2008-05-24-sse-popcount.html http://0x80.pl/notesen/2008-05-24-sse-popcount.html [4] https://tia.mat.br/posts/2014/06/23/integer_to_string_conversion.html https://tia.mat.br/posts/2014/06/23/integer_to_string_conver...
- Taniwha 1y agoI'm a sometimes CPU architect and came here to argue just this - modern CPUs have far far slower memory access (in clocks) than z80 memory access. To be fair you can probably fit any z80 table you're using into modern L1 cache, but even so you're looking at multiple clocks rather than 1.
- flohofwoe 1y agoOn the Z80 any memory access had a fixed cost of 3 clock cycles (in reality the memory system could inject wait cycles, but that was an esoteric case). Together with the instruction fetch of 4 clock cycles the fastest instruction to load an 8-bit value from an address that's already in a 16-bit register (like LD A,(HL)) takes 7 clock cycles. The fastest instructions that didn't access memory (like adding two 8-bit registers) were 4 clock cycles, so there's really not much room to beat a memory access with computation. Today "it depends", I still use lookup tables in some places in my home computer emulators, but only after benchmarking showed that the table is actually slightly faster.
- deleted 1y ago[deleted]
- bluGill 1y ago> Lookup tables are always faster than calculation - is that true No. A simple counter example: a single ADD will be faster than a lookup table on nearly anything. However I doubt that is what is meant. For complex calculations there are a lot of it depends and tradeoffs. A lookup table will often force you to think about trade offs because the table takes up a lot more memory and so you need to decide what values are important. A lookup table is also prone to bugs - back in the 1990s someone noticed that the Intel Pentium processor didn't give the right results for division - turns out they didn't enter a few values into the table correctly - if you write a table you could have the same bug. Calculating sin() to as many decimal places as your highest precision floating point register allows will be slow, but that is likely what the sin built into your standard library does since you might be building a bridge that the person who wrote that sin function crosses latter. If you only need sin rounded to the nearest whole number a lookup table is probably faster. If you need sin to as precise as the computer can calculate that is a lot of RAM (x86 uses 80 bits internally for floating point numbers)
- dragontamer 1y ago> No. A simple counter example: a single ADD will be faster than a lookup table on nearly anything. Note that a round of AES is now one aesenc instruction on modern systems. You might be surprised how much better code is than memory lookups. Modern AMD Zen5 cores have 8 instruction pipelines but only 3 load/store pipelines. You have more AVX512 throughput on modern Zen5 cores (4x Vector pipelines) than L1 throughput. I'd go as far out to say that table lookups are the worst they've ever been in terms of compute speed. The reason modern encryption/hashing got so fast is that XChaCha and SHA3 are add/for/rotate based rather than lookup-based (sbox based like AES or DES). Tables are still appropriate for some operations, but really prefer calculations if at all possible. Doubly so if you are entering GPU code where you get another magnitude more compute without much memory bandwidth improvements.
- dragontamer 1y agoOh, if you need the best of both worlds, consider pshufb (4-bit lookup table), or if you have access to AVX512 you could use vpermi2b as an effective 7-bit lookup table. It's not quite a full memory lookup table but these instructions get a lookup-like behavior but using the vector units (128-bit or 512-bit registers).
- djmips 1y agoIt's not true