[racket] universe program using 100% cpu on Mac (also poor Raspberry Pi performance)

24 views
Skip to first unread message

Darren Cruse

unread,
Dec 28, 2014, 9:52:42 PM12/28/14
to us...@racket-lang.org
Hi been working thru the How to Design Programs book with a meetup here in St Louis.

Just finished writing a pong game and though the performance is acceptable on a normal laptop I was a little surprised how poorly it runs on a raspberry pi...

Not that I'm that worried about the pi, but it started me looking a little closer at performance and wondering if I've written things in a way that hurts performance.

The first thing I noticed is cpu usage was high even when nothing was happening in the game (e.g. when the pong game was just idling and there was no movement on the screen at all).

In fact I noticed on my MacBook the program sits on 100% cpu all the time!

But on my little ubuntu netbook (chromebook running ubuntu) the same program sits at about 30% cpu.

And on a Windows 8.1 laptop it's only taking 6% cpu.

These numbers are when running with command line racket not thru drracket btw.

One thing I can see is that on-draw is called for every on-tick on all three platforms btw.

And even in cases where the program is idling and on-tick has simply returned the world state it was given unmodified.  

Is that normal I wonder?  Part of me thought that since to-draw is a function of the world state, and the world state hasn't changed, that it would *not* call to-draw in that case.

(but it calls to-draw for every on-tick even on the Windows machine which is using only 6% cpu - so maybe I'm wrong to look to that as the problem)

Could it be on Windows the graphics drawing is GPU accelerated but on the Mac it's not?  i.e. maybe on the Mac all drawing done with the CPU alone?

Or could this be an actual bug in racket I wonder?

Lastly - and again it's not a big deal I'm just curious - do people think a program like this should be able to run with racket on the Pi?  Or is the Pi too underpowered for universe programs with racket?

Thanks,

Darren

Matthias Felleisen

unread,
Dec 29, 2014, 9:07:15 AM12/29/14
to Darren Cruse, us...@racket-lang.org
On Dec 28, 2014, at 9:51 PM, Darren Cruse wrote:

One thing I can see is that on-draw is called for every on-tick on all three platforms btw.

And even in cases where the program is idling and on-tick has simply returned the world state it was given unmodified.  

Is that normal I wonder?  Part of me thought that since to-draw is a function of the world state, and the world state hasn't changed, that it would *not* call to-draw in that case.

I experimented with this 'optimization' and, if I recall correctly, it didn't make much of a difference and got in the way of imperative world programs. So I took it out. Mea culpa, I should have commented on this experiment inside the code. 



(but it calls to-draw for every on-tick even on the Windows machine which is using only 6% cpu - so maybe I'm wrong to look to that as the problem)


My Mac-based experiments suggest that this call is not the cause of performance problems. 

;; --- 

Could you post the code somewhere so we can experiment with it? 

Thanks -- Matthias


Darren Cruse

unread,
Dec 29, 2014, 10:49:06 AM12/29/14
to Matthias Felleisen, us...@racket-lang.org
Re:
Could you post the code somewhere so we can experiment with it? 

Here it is it's all in one file:

Matthias Felleisen

unread,
Dec 29, 2014, 11:41:42 AM12/29/14
to Darren Cruse, us...@racket-lang.org

Hi Darren, thanks for the link to the repo. I cloned it, successfully played with and without sound on a mac book -- inside of drracket and from the command line-- and never observed a load over 60% for drracket and ditto for plain racket. That doesn't mean that your 100% problem doesn't exist, it's just that I can't reproduce it. 

A couple of comments on the code: 

 -- I'd place the main function at the top of the function section of the file 
      right below the constant definitions and data definitions 
[I modified 2e to bring this across but you might be reading the stable version.]

 -- I also run (main initial-state) out of the repl not the main buffer. 

 -- Your file is missing tests. 

 -- Some functions are also missing proper signatures and purpose statements. 

But I know "it works" see my homepage :-) 

-- Matthias

Darren Cruse

unread,
Dec 29, 2014, 1:45:43 PM12/29/14
to Matthias Felleisen, us...@racket-lang.org
Thanks Matthias and it will be quite fun to tell the others at my next meetup who code reviewed this for me! :)

I'll make the changes you suggested though (forgive me) I'll have to think about what constitutes useful tests for this.  Somehow I've never fully bought into TDD though I know I'm one of the last holdouts in the civilized world. :)  Can I get out of it saying I was just doing this for fun? :)

I'm most of all pleased that you didn't see something I'd fundamentally misunderstood, e.g. that would explain why the game performed poorly on the raspberry pi.

fwiw Racket is the first lispy language I've ever gotten serious about learning.  I'm one of those who'd been thrown off by the parens for too long.  I really like using it now that I'm over the initial learning curve.  I think my biggest wish would be it had a good story for doing smartphone/tablet apps, or that Whalesong was more of a going concern (not that I've tried it I wonder if this pong game would run under it without a ton of work?)

Thanks again for your time,

Darren

Matthias Felleisen

unread,
Dec 29, 2014, 9:02:09 PM12/29/14
to Darren Cruse, us...@racket-lang.org

TESTS: Say I want to eliminate a common pattern from your handle-key-down function. If it comes with some tests, four to be precise, a simple run -- without playing -- assures me of basic qualities. If you express tests like those below and you formulate them first, you get an idea of how to code the function. And -- for a large function -- a reader quickly gets the idea of how the function works from reading some tests. 

(check-expect (handle-key-down initial-state "w")  (set-left-moving initial-state UP-DIR))

(define (handle-key-down world a-key)
  (cond
    [(key=? a-key "w") (set-left-moving world UP-DIR)]
    [(key=? a-key "s") (set-left-moving world DOWN-DIR)]
    [(key=? a-key "up") (set-right-moving world UP-DIR)]
    [(key=? a-key "down") (set-right-moving world DOWN-DIR)]
    [else world]))

(define (set-left-moving world dir)
  (set-left-paddle world (set-paddle-moving (pong-world-left-paddle world) dir PADDLE-SPEED)))

(define (set-right-moving world dir)
  (set-right-paddle world (set-paddle-moving (pong-world-right-paddle world) dir PADDLE-SPEED)))


GAME PAD: I am happy to see that you used on-pad. Your game does give me an idea on how to improve the whole 'pad situation'. 

Darren Cruse

unread,
Dec 29, 2014, 11:30:17 PM12/29/14
to Matthias Felleisen, us...@racket-lang.org
Hi Matthias just saw your reply.

Regarding the tests I like what you said about "for a large function -- a reader quickly gets the idea of how the function works from reading some tests".

But if I'm honest the example that you gave is the kind of example that bothers me - in that it's *not* a large function.  The test you gave reminds me of the majority that I see - and it struck me recently I think what bothers me is that such tests are in effect a tautology

i.e. most of these tests like this, esp. since they are written by the same programmer writing the function under test, are literally just a restating of the exact same assumptions the programmer has made in writing the function.  So they are *literally* redundant.  Yet they must be maintained as the programmer maintains and modifies the code going forward.  So there is a cost to them, but to me I honestly don't see much if any benefit.

Which isn't to say that good tests can't be written if they are testing a large and complex function (as you said).  What I question is the common belief nowadays that they're always of value even in simple cases. 

Anyway hope you'll forgive my heresy.  I turned 50 this year maybe I just an old dog now too set in his ways.  I know I'm very much in the minority in this view.  No offense intended.

But more to the point - here's what I did while I should have been writing tests:


The biggest challenge - other than what looks like some problems with multiple/deeply nested overlay/place-image positioning, is that whalesong seems to not support "on-release", so for now this works best if you click and hold where it says "hit space to serve" and then you can use your mouse and play the game against yourself.

This was done using the soegaard/whalesong version of whalesong btw.

Darren



Sean Kanaley

unread,
Dec 30, 2014, 8:18:04 AM12/30/14
to Darren Cruse, Racket Users, Matthias Felleisen
Hi Darren,

I think there are three common kinds of unit tests --

1. Interaction window while developing the function, to ensure it seems to do what you think it does. It's probably simple yet covers the majority of cases. These might be copy and pasted into unit tests as a simple sanity check for basically no extra effort.

2. Corner case checks. The ones you have to go out of your way to think about. I'm sure there's differing opinions here, but these can be left out until a bug is revealed, saving you the time of effectively presenting a proof by exhaustion for every atom of code you write.

3. Redundant checks like you said, e.g. pressing left does in fact make it go left. My view on this is you don't actually write these as part of the initial development, because not only is the redundancy clear in short functions, but the test is going to be subject to the same lazy copy paste errors that the function is, possibly resulting in functioning but wrong unit tests (ouch). Where these are helpful is when you refactor. When your program is complete, but you decide to change the features, and as a result may want to modify the movement logic, these *formerly* redundant tests now serve as the sole specification for what your program must continue doing, regardless of how much you butcher the code. In a way it's like you saved a backup copy of the code, except in such a way that running it allows the compiler to find differences in the code for you. But you don't really need the trouble of a backup copy at the beginning of development (where you may end up deleting a function altogether!).

____________________
  Racket Users list:
  http://lists.racket-lang.org/users


Alexander McLin

unread,
Dec 30, 2014, 8:32:56 AM12/30/14
to Darren Cruse, us...@racket-lang.org, Matthias Felleisen
Hello Darren,

May I offer this point of view regarding tautological tests. They are useful for checking if your assumptions are consistent and they stay that way over multiple iterations of design modifications.

Personally I've found keeping a list of assumptions only in my head is a recipe for design errors. Fallible memory and poorly thought out logical consequences.

Several times I've found simple seeming functions had surprising edge cases I hadn't thought about until I actually wrote down simple obvious tests. So they are useful for making the brain juices flow while you are starting to get the shape of your program down pat.

Tests really serve two functions here, verification of correctness and whether the design makes sense. They are both for you and your end users. I think tautological tests are more for your benefit than the end users.

Tautological tests in this case have a useful purpose of  allowing you to try your design out in practice and deciding if it works in a general sense of practicality and effective interface regardless of whether it gives correct output.

Matthias Felleisen

unread,
Jan 1, 2015, 7:37:49 PM1/1/15
to Darren Cruse, us...@racket-lang.org

Darren, happy new year. 

the tests I added to your code base after the fact were miserable to argue for test-oriented program design. t was like presenting a paragraph with thesis T and the remainder of the paragraph supports thesis S. Ouch. 

I added them only to make sure your version and mine agreed on basic inputs. 

Others have responded and did say the right things about test. Let me offer an example from today's exercise (there's some other thread on the mailing list that deals with related code): 

;; Splits -> [Listof String]
(module+ test
  (check-equal? (replaces "a") (for/list ((s ALPHABET)) (string s)))
  (check-equal? (replaces "ab") 
                (append (for/list ((s ALPHABET)) (string-append (string s) "b"))   (for/list ((s ALPHABET)) (string-append "a" (string s))))))

(define (replaces s)
  (define l (string->list s))
  (define all-replaced
    (let loop ([l l])
      (cond
        [(empty? l) '()]
        [else (define one (first l))
              (define others (rest l))
              (append (for/list ((a ALPHABET)) (cons a others))  (for/list ((r (loop (rest l)))) (cons one r)))])))
  (map list->string all-replaced))'

-- when I write such tests before I code, I get the basic idea of what the function definition will look like when I am done. 

-- once I have such tests and I come up with a better way of implementing this function, say 

(define (replaces s)
  (define ss (splits s))
  (for*/list ([s (in-list ss)] [rht (in-value (cdr s))] #:when (not (string=? rht "")) [c ALPHABET])
    (string-append (car s) (string c) (substring rht 1))))

then my first line of defense are the unit tests I developed while I designed. As you can see, for my second implementation I moved away from the 'natural' implementation and used one that looks quite different from the test. 

W/o unit tests around, I feel less confident about such changes and over the years I have learned that every time I have a logical bug, I failed to follow the design recipe. Here is an admission: for the first few years, I'd say "the design recipe is for the kids. They will internalize all this stuff and then they won't need the recipe anymore." And yes, I discovered that once I followed the design recipe, I reduced the number of bugs in my coding. [I am sure there are stronger-minded people on the Racket team and the Racket user list who don't need this kind of reassurance and who don't make the kind of mistakes I made and still make. I am happy for them. But I am not one of them.]

I have five years on you and I could do it, so I am sure you can too. 

;; ---- 

As for your Whalesong example, it doesn't do well for me at all. Neither the arrows nor the game-pad responses are fast enough to play. 

You may wish to check out Emmanuel Schanzer's recent re-do of Whalesong. Perhaps it helps with the performance problems. He posted on this list about a month ago and he actually asked for test cases. My Hungry Henry was fun on his JS Racket platform. 

Let the design recipe be with you this year -- Matthias
Reply all
Reply to author
Forward
0 new messages