Skip to content

Performance #3

@nickdrozd

Description

@nickdrozd

Yo! This project is awesome. I don't know much about hardware or
low-level programming in general, so I'm excited to find out how the
whole thing works.

As I'm sure you're aware, performance is an issue at the moment. On my
crappy little Thinkpad, it takes several minutes to go from loading
Tetris to the second piece appearing during gameplay.

So I profiled for CPU time, running the interpreted code (commit
937e63c) from loading until the second Tetris piece appears. Here are
the results, with irrelevant stuff cut out:

- eboy-load-rom                                     51%
 - eboy-run                                         51%
  - while                                           50%
   - eboy-process-opcode                            25%
    - if                                            20%
     + funcall                                      18%
     + eboy-inc-pc                                   1%
    + eboy-mem-read-byte                             2%
   - eboy-lcd-cycle                                 17%
    - let                                           16%
     - if                                           14%
      - progn                                       13%
       - eboy-debug-update-fps                      13%
        - if                                        13%
         - progn                                    13%
          - eboy-write-display-unicode              13%
           - let                                     9%
            + let                                    9%
           + redisplay                               3%
          + eboy-read-keys                           0%
   + eboy-process-interrupts                         3%
   + eboy-timer-cycle                                2%
Automatic GC                                        48%

48% of CPU time spent in garbage collection, ouch! That means that too
much garbage is being created. Cutting down on that should have a
significant impact on performance.

So I profiled for memory.

- eboy-load-rom                                3,748,704,464  99%
 - eboy-run                                    3,748,351,560  99%
  - while                                      3,748,350,536  99%
   - eboy-process-opcode                       2,085,125,256  55%
    - if                                       1,834,769,803  48%
     - funcall                                 1,584,632,340  41%
      + #<lambda 0x129bd1ce6ac1ba49>             872,248,406  23%
      + #<lambda -0xb04ddad4bebffdd>             291,892,213   7%
      + #<lambda 0x1627d4710ba0e62d>             228,615,133   6%
      + #<lambda -0x4d5d1759851bda8>              38,206,101   1%
   - eboy-lcd-cycle                              900,930,468  23%
    - let                                        651,168,145  17%
     - if                                        651,072,911  17%
      - progn                                    651,072,911  17%
       - eboy-debug-update-fps                   651,072,911  17%
        - if                                     651,072,911  17%
         - progn                                 651,004,271  17%
          - eboy-write-display-unicode           650,744,042  17%
           - let                                 642,287,662  16%
            - let                                642,286,606  16%
             - while                             642,286,606  16%
              - let                              642,023,662  16%
               - let                             629,355,236  16%
                - while                          613,176,550  16%
                 - let                           576,671,503  15%
                  - setq                         576,636,407  15%
                   - nth                         540,956,279  14%
                    + eboy-get-color-xy          469,317,047  12%
   + eboy-timer-cycle                            259,579,949   6%
   + eboy-process-interrupts                     250,632,361   6%

Those numbers are for the interpreted code. Byte compiling makes a big
difference. Here is the memory profile for the byte compiled code
running all the way until the Tetris pieces reach the top of the
screen:

- eboy-load-rom                                  151,831,860  91%
 - eboy-run                                      151,489,484  90%
  - eboy-lcd-cycle                               151,485,348  90%
   - eboy-debug-update-fps                       151,485,348  90%
    - eboy-write-display-unicode                  45,211,847  27%
     + redisplay                                   3,058,555   1%
       mod                                            66,856   0%
       eboy-display-sprites                           65,472   0%
     + eboy-get-color-xy                                 512   0%
    + read-char                                       61,744   0%
  + eboy-process-opcode                                4,136   0%

And the CPU profile for the byte compiled code:

- eboy-load-rom                                     96%
 - eboy-run                                         92%
  - eboy-process-opcode                             35%
   + #<compiled 0x158338c1783d>                      8%
   + #<compiled 0x158337784dcd>                      6%
   + #<compiled 0x15833848ecc5>                      5%
  - eboy-lcd-cycle                                  32%
   - eboy-debug-update-fps                          31%
    - eboy-write-display-unicode                    29%
     + redisplay                                    15%
     - eboy-get-color-xy                            11%
      + eboy-get-color                               2%
        eboy-get-tile-id                             2%
  + eboy-process-interrupts                          1%
Automatic GC                                         3%

Based on this data, I have some ideas on how to improve some critical
sections:

  1. The CPU is currently vector filled with argument-less lambdas. This
    could be replaced with a big case switch statement. That would be
    uglier, but I think it would consume less memory.

  2. eboy-write-display-unicode contains this section:

    (dotimes (y 144)
      (setq line nil)
      (dotimes (x 160)
        (setq c (nth (eboy-get-color-xy (mod (+ x eboy-lcd-scrollx) 256) (mod (+ y eboy-lcd-scrolly) 256)) eboy-display-unicode-list))
        (setq line (cons c line))
        (setq line (cons c line))) ;; draw x twice, to get a more square sized display.
      (setq line (nreverse line))

Multiple conses are made in a very tight loop, and this consumes a lot
of memory. This would more efficient with vectors instead of lists.

I hope this is useful.

Activity

vreeze

vreeze commented on Mar 12, 2019

@vreeze
Owner

Hi nickdrozd,
Thanks for your detailed analysis write up!
It is useful indeed.

I'm planning to rewrite the eboy-write-display-unicode to use with-temp-buffer and buffer-string as was suggested in an earlier comment. This will remove the cons'ing and write directly to a buffer.

As for your first point. Earlier I had a big case switch, looking like this:

  (defun eboy-process-opcode (opcode flags)
    "Process OPCODE, cpu FLAGS state."
    (cl-case opcode
      (#x00 (incf eboy-clock-cycles 4))

      ;; LD nn,n
      (#x06 
          (setq eboy-rB (eboy-get-byte))
          (eboy-inc-pc 1)
          (incf eboy-clock-cycles 8))
      (#x0E 
          (setq eboy-rC (eboy-get-byte))
          (eboy-inc-pc 1)
          (incf eboy-clock-cycles 8))

and when I switched to the vector jump table with O(1) access, it was quite a speed improvement.
But maybe I could have done something in a more clever way in the switch case, since I read somewhere that it should be automatically be compiled into a jumptable.
Because the lamda functions are also causing a performance penalty, it is interesting to revisit this.

If you are welcome to investigate this if you like.

nickdrozd

nickdrozd commented on Apr 13, 2021

@nickdrozd
Author

I tried running Eboy under the new Emacs native compile branch. The speedup is substantial, with the reported FPS going from 0.2 to 1.1. From the user perspective, this means Tetris is almost playable! That doesn't sound like much, but it's a big step up from "Is this thing frozen?"

Here's a little bit of recent CPU profiling data:

       37800  95%            - eboy-load-rom
       36061  90%             - eboy-run
       15709  39%              - eboy-lcd-cycle
       15684  39%               - eboy-debug-update-fps
       15004  37%                - eboy-write-display-unicode
        4184  10%                 - eboy-get-color-xy
         822   2%                    eboy-get-tile-id
         485   1%                    eboy-get-color
         142   0%                   eboy-display-sprites
          16   0%                   eboy-get-color
       10567  26%              + eboy-process-opcode
           2   0%              + eboy-process-interrupts

@ebpa

vreeze

vreeze commented on Dec 11, 2021

@vreeze
Owner
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @nickdrozd@vreeze

        Issue actions