[HN Gopher] I Ported SAP to a 1976 CPU. It Wasn't That Slow
       ___________________________________________________________________
        
       I Ported SAP to a 1976 CPU. It Wasn't That Slow
        
       Author : weinzierl
       Score  : 122 points
       Date   : 2025-07-07 05:39 UTC (2 days ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | U1F984 wrote:
       | 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 wrote:
         | > 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/Ca1hHC2EctY?t=827
        
           | sumtechguy wrote:
           | Basically 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 wrote:
         | Depends 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 wrote:
           | I'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 wrote:
         | You 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 - an
         | extremely nice resource on micro architectural impacts of
         | instructions
         | 
         | 2. 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/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 wrote:
           | ...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 wrote:
             | Not _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).
        
               | bayindirh wrote:
               | You are right, but with a good optimizing compiler and
               | out of order execution, your code will not work the way
               | you guess most of the time, even though it accomplishes
               | what you want.
               | 
               | On the other hand, while doing high performance compute,
               | the processor will try to act smart to keep everything
               | saturated. As a result, you still need to look at cache
               | trash ratio, IPC, retirement ratio, etc. to see whether
               | you are using the system at its peak performance, and
               | again CPU is doing its thing to keep the numbers high,
               | but that's not enough of course. You have to do your own
               | part and write good code.
               | 
               | In these cases where you share the machine (which can be
               | a cluster node or a mobile phone), maximizing this
               | performance is again beneficial since it allows smoother
               | operation both for your and other users' code in general.
               | Trying to saturate the system with your process is a
               | completely different thing, but you don't have to do that
               | to have nice and performant code.
               | 
               | GPU computation is nice, and you can do big things fast,
               | but it's not suitable for optimizing and offloading every
               | kind of task, and even if though the task is suitable for
               | the GPU, the scale of the computation still matters,
               | because a competent programmer can fit billions of
               | computations until a GPU starts running your kernel. The
               | overhead is just too big.
               | 
               | Knuth's full quote doesn't actually invalidate me,
               | because that's how I operate while writing code, designed
               | for high performance or not.
        
               | sitkack wrote:
               | And so the line continues
               | 
               | https://hn.algolia.com/?dateRange=all&page=0&prefix=false
               | &qu...
        
           | rft wrote:
           | A few more links for low level CPU benchmarking
           | 
           | 1 - https://www.uops.info/index.html similar content to
           | Anger's tables
           | 
           | 2 - https://reflexive.space/zen2-ibs/ how to capture per
           | micro op data on AMD >= Zen 1 CPUs
           | 
           | I agree on "it depends". And usually not only on your actual
           | code and data, but also how you arrange it over cache lines,
           | what other code on the same core/complex/system is doing to
           | your view of the cache and some other internal CPU features
           | like prefetchers or branch predictors.
        
         | yorwba wrote:
         | In 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 wrote:
           | probably 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 wrote:
         | The 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 wrote:
         | You are correct and I've even ran into a situation where build-
         | time evaluation was slower than runtime calculation, thanks to
         | code size.
        
         | mananaysiempre wrote:
         | > 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
         | 
         | [2] https://lemire.me/en/talk/perfsummit2020/
         | 
         | [3] http://0x80.pl/notesen/2008-05-24-sse-popcount.html
         | 
         | [4]
         | https://tia.mat.br/posts/2014/06/23/integer_to_string_conver...
        
         | Taniwha wrote:
         | I'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 wrote:
         | On 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.
        
         | bluGill wrote:
         | > 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 wrote:
           | > 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.
        
         | djmips wrote:
         | It's not true
        
       | peteforde wrote:
       | I have only one question: does the author know anything about
       | coding ABAP like it's a Z80? I wish that they'd addressed this.
        
         | ooisee wrote:
         | yes, it is addressed in another repo
         | https://github.com/oisee/zvdb
         | 
         | and another article about zvdb-abap from ~1.5 years ago.
        
           | peteforde wrote:
           | My comment was a reaction to the fact that you called out
           | your experience/wisdom on the topic no less than five
           | distinct times in a short (otherwise great!) post.
           | 
           | I would argue that by doing so, you made it the unintended
           | central theme of the post.
           | 
           | I'm not saying this to hurt your feelings. That you didn't
           | perceive the obvious sarcasm in my late-night comment
           | suggests that you might not want to be perceived as pompous
           | in your writing style.
           | 
           | My suggestion is that one explicit "I'm an expert" reminder
           | per post is a perfectly good number.
        
             | ooisee wrote:
             | oh!, now I got it =) will increase "Shy" parameter and
             | decrease "Braggart" parameters in my "Normal speech" to
             | "LinkedInese" translator +)
             | 
             | Thank you for feedback =)
             | 
             | (I was thinking that it is not MY wisdom i am talking, but
             | about "Z80" wisdom - all the code I have seen on demoscene
             | and e-zines.)
        
               | peteforde wrote:
               | I am (definitely) not telling you to act shy or not be
               | proud of your accomplishments.
               | 
               | Ultimately, it's up to you if you want to redeclare your
               | bona fides in almost every paragraph. It doesn't make for
               | great reading, but it's your call either way.
               | 
               | If anything, I'm encouraging you to let your narrative
               | and actions speak for your skills.
        
               | hughw wrote:
               | Yes, this reinforces that the article suffered from the
               | LLM rewrite. Your comments here prove your natural voice
               | is more compelling for this audience than the LLM "cat
               | post" style.
        
             | ooisee wrote:
             | Nice recursive joke!
        
       | bravesoul2 wrote:
       | It's amusing that the writing style is akin to a LinkedIn what
       | XYZ taught me about B2B sales.
        
         | ooisee wrote:
         | Guilty as charged--and totally intentional.
         | 
         | Turns out "I want a burger" beats "we have equivalent burger at
         | home" every time--even when the home solution is objectively
         | better.
         | 
         | So yes, this reads like "What my goldfish taught me about
         | microservices." But unlike those posts, this story has no moral
         | --just nerdy fun with enterprise software roasting.
         | 
         | Sometimes you gotta speak the language they actually read there
         | +)).
        
       | _notreallyme_ wrote:
       | Optimizing code on MMU-less processor versus MMU and even NUMA
       | capable processor is vastly different.
       | 
       | The fact that the author achieves only a 3 to 6 times speedup on
       | a processor running at a frequency 857 faster should have led to
       | the conclusion that old optimizations tricks are awfully slow on
       | modern architecture.
       | 
       | To be fair, execution pipeline optimization still works the same,
       | but not taking into account the different layers of cache, the
       | way the memory management works and even how and when actual RAM
       | is queried will only lead to suboptimal code.
        
         | ooisee wrote:
         | Seems like, You've got it backwards -- and that makes it so
         | much worse. ^_^
         | 
         | I ported from ABAP to Z80. Modern enterprise SAP system - 1976
         | processor. The Z80 version is almost as fast as the
         | "enterprise-grade" ABAP original. On my 7MHz ZX Spectrum clone,
         | it's neck-and-neck. On the Agon Light 2, it'll probably win.
         | Think about that: 45-year-old hardware competing with modern
         | SAP infrastructure on computational tasks. This isn't "old
         | tricks don't work on new hardware." This is "new software is so
         | bloated that Paleolithic hardware can keep up." (but even this
         | is nonsense - ABAP is not designed for this task =)
         | 
         | The story has no moral, it is just for fun.
        
           | kragen wrote:
           | Oh, that makes a lot more sense! I was puzzled as to how the
           | new hardware could be so slow, but an inefficient interpreter
           | easily explains it. I've seen over 1000x slowdowns from
           | assembly to bash, so it sounds like ABAP is close to bash.
        
           | theamk wrote:
           | That Z80 code is not the equivalent of the modern code
           | though, is it?
           | 
           | for example your modern code mentions 64KB lookup table.. no
           | way you can port this to Z80 which has 64KB of address space
           | total, shared for input, output, cache and code.
           | 
           | So what do those timings mean? Are those just a made up
           | numbers for the sake of narrative?
        
             | michalpleban wrote:
             | Input and output are in a separate address space on the
             | Z80. It's on the 6502 where they share space with code and
             | data.
        
         | guenthert wrote:
         | Are we intentionally ignoring that ABAP is byte code
         | interpreted?
        
       | orbifold wrote:
       | Writing style and length of paragraphs strongly suggest that this
       | is AI generated in full.
        
         | ooisee wrote:
         | Not yet in full, it is ~45% generated.
         | 
         | Two reasons: 1) English is not my native tongue, 2) I hate
         | LinkedIn article style -> let LLM convert my hadrcore-oldschool
         | style into something like "You won't believe what my
         | grandmother's cat taught me about ..."
        
           | AnimalMuppet wrote:
           | I would rather read a non-native speaker write something
           | hardcore-oldschool than read LLM-generated "You won't believe
           | what my grandmother's cat taught me about..." You may get
           | corrections about your English. You may even get complaints
           | (not everyone on HN is nice all the time). But my impression
           | is that you will get fewer complaints than you will from
           | something that "feels" LLM-generated. (Or maybe that's just
           | my personal taste.)
        
             | bee_rider wrote:
             | It is considered socially acceptable to just
             | straightforwardly complain about an LLM-written post, while
             | complaints about somebody's English are usually phrased as
             | constructive feedback. The latter takes more effort and is
             | not as effective as a launching point for a rant, so the
             | former might just be easier to detect based on lines-of-
             | text.
        
           | bee_rider wrote:
           | Some folks get very annoyed by the whiff of LLMs, and will
           | complain loudly. I guess people who aren't annoyed have no
           | reason to post about it, though.
           | 
           | Perhaps if you post your oldschool notes and their LLMified
           | version and see which rises to the top, haha.
        
           | layer8 wrote:
           | What's wrong with hardcore-oldschool style? Why on earth
           | would you want LinkedIn style?
        
         | edg5000 wrote:
         | It was fun to read though, it flows oddly but it works. Great
         | case study of appropriate AI use!
        
         | bapak wrote:
         | Absolutely right. Kinda sad that this format is frowned upon
         | now. I like data-rich articles without SEO fluff.
        
       | IceDane wrote:
       | Saying you ported SAP makes no sense. You ported a program you
       | wrote for the SAP platform.
       | 
       | Porting all of SAP would be equivalent to porting all known human
       | cancers from the human genome to a different newly discovered
       | alien genome, both in terms of scale as well as sheer evil.
        
         | mdaniel wrote:
         | It really makes me want to flag this submission because of how
         | grossly misleading such a title is, despite it being correctly
         | copied from the upstream source
        
       | mdaniel wrote:
       | Please don't forget to put a license in your repo. Based on the
       | zvdb linked to in the readme I'm guessing you prefer MIT but
       | explicit is always better than leaving the audience wondering
        
       | martinald wrote:
       | It's interesting how this stuff works. I started learning "tech"
       | as a millenial in the late 90s when the internet and web was
       | relatively new.
       | 
       | As such I picked up an awful lot of networking/windows/linux
       | "fundamentals" simply because you had to know it to fix anything
       | (truing - and failing - to get my crappy 28k winmodem working on
       | Linux probably taught me many "months" worth of fundamentals
       | alone!).
       | 
       | The other thing is when you are younger learning this stuff, most
       | of us had pretty hard financial constraints on what we were
       | doing. I couldn't persuade my parents to replace that winmodem
       | with a proper modem (which I would do without thinking now), so
       | you really had to make do with what you had.
        
         | dekhn wrote:
         | One of the best lessons I learned was during a hard financial
         | constraint.
         | 
         | Roughly around 1994 I had a new compute- a 486/66MHz with 4MB
         | of RAM. I got LINUX and installed it, and was able to run X
         | windows, g++, emacs, and xterm- but if I compiled while emacs
         | was running, the system would page like crazy (especially
         | obvious in those days when harddrives were very noisy).
         | 
         | I had to work really hard to convince myself to pay the $200
         | (as an undergraduate, I had many other things I would have
         | preferred to spend money on) to double the ram to 8MB, and then
         | another $200 to 16MB a year later, and finally a last $200 to
         | max out the RAM at 32MB.
         | 
         | Once the system had 32MB of RAM, it performed quite well, with
         | minimal paging, and it greatly increased my productivity. I
         | learned that while RAM can be expensive, making sure your
         | processor is not waiting for disk is worth it.
         | 
         | I probably also spent $1,000s of dollars on modem upgrades
         | (1200->2400, 2400->9600, 9600->19200, 19200->48000, 48000->56K
         | and then switching to DSL and later fiber). Each time was
         | "worth it" but it was expensive and so I really thought hard
         | abotu the upgrade and the value it brought me (a high level of
         | job opportunities in areas I find interesting).
        
           | shawn_w wrote:
           | >... if I compiled while emacs was running, the system would
           | page like crazy
           | 
           | The good old days when "eight megs and constantly swapping"
           | was a real issue. I kind of miss them. (But not the modem
           | speeds. Don't miss those at all.)
        
       | reidrac wrote:
       | The Z80 example in
       | https://github.com/oisee/zvdb-z80/blob/master/ZVDB-Z80-ABAP....
       | doesn't look correct.
       | 
       | That:                   ld ix, 0 ; accumulator
       | 
       | Is bugging me because 1. ix is not used in the function and 2.
       | operations on ix are limited (and slow), can't be used as
       | "accumulator". It is a 16-bit register to access memory as an
       | index (and as extra two 8-bit limited registers if you use
       | undocumented Z80 opcodes).
       | 
       | Besides, why use b register as counter in the loop and use dec
       | and jr when there's djnz for that?
       | 
       | I haven't checked anything else, but that has a bad smell.
        
         | veltas wrote:
         | Using IX instead of HL or B even isn't going to break the bank.
         | This code will still beat e.g. the output of SDCC on some
         | normal C code.
        
       | faefox wrote:
       | I'm sure this is interesting stuff but the obvious ChatGPT voice
       | only distracts from your own thoughts.
        
       ___________________________________________________________________
       (page generated 2025-07-09 23:01 UTC)