Why is struct/contract so much faster than a guard?

76 views
Skip to first unread message

Christopher Lemmer Webber

unread,
Sep 2, 2020, 3:41:02 PM9/2/20
to racket...@googlegroups.com
I tested the following:

(struct foo (bar baz)
#:guard (struct-guard/c any/c list?))

and:

(struct/contract foo ([bar any/c]
[baz list?]))

With the first:

test> (time
(for ([i 1000000])
(foo 'yeah '(buddy))))
cpu time: 2601 real time: 2599 gc time: 7

With the second:

test> (time
(for ([i 1000000])
(foo 'yeah '(buddy))))
cpu time: 184 real time: 184 gc time: 13

Wow, what the heck? That's about a 10x difference. What?!?!?
Why would #:guard be so damn slow in comparison? You'd think they'd be
doing the same thing.

Unfortunately I can't use #:methods with struct/contract so I'm stuck
with the slow one if I want a contract on the struct?

Sam Tobin-Hochstadt

unread,
Sep 2, 2020, 4:52:42 PM9/2/20
to Christopher Lemmer Webber, Racket Users
The issue is that `struct-guard/c` is slow. If you just write a
function as a guard it's faster than `struct/contract`.

Sam
> --
> You received this message because you are subscribed to the Google Groups "Racket Users" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/873640arw7.fsf%40dustycloud.org.

Philip McGrath

unread,
Sep 2, 2020, 6:26:15 PM9/2/20
to Christopher Lemmer Webber, Racket Users
On Wed, Sep 2, 2020 at 3:41 PM Christopher Lemmer Webber <cwe...@dustycloud.org> wrote:
Unfortunately I can't use #:methods with struct/contract so I'm stuck
with the slow one if I want a contract on the struct?

For another option (though you may already know this), I'd advocate for using the `struct` sub-form of `contract-out` and drawing the module boundary as tightly as needed to make it a sensible boundary for trust, potentially by using submodules.

Since you mention `#:methods` in particular, you should be aware of some subtle corners that make it tricky (and potentially expensive at runtime) to protect  `racket/generic` methods comprehensively with contracts. (Here's a pointer to some discussions.) I think just working with struct-type properties can make sense when you don't really need most of the features `racket/generic` gives you.

-Philip

Christopher Lemmer Webber

unread,
Sep 2, 2020, 10:42:58 PM9/2/20
to Sam Tobin-Hochstadt, Racket Users
Ah, I didn't know one could just write a function for the guard... but
that makes a lot of sense!

Christopher Lemmer Webber

unread,
Sep 2, 2020, 10:43:32 PM9/2/20
to phi...@philipmcgrath.com, Racket Users
Philip McGrath writes:

> On Wed, Sep 2, 2020 at 3:41 PM Christopher Lemmer Webber <
> cwe...@dustycloud.org> wrote:
>
>> Unfortunately I can't use #:methods with struct/contract so I'm stuck
>> with the slow one if I want a contract on the struct?
>>
>
> For another option (though you may already know this), I'd advocate for
> using the `struct` sub-form of `contract-out` and drawing the module
> boundary as tightly as needed to make it a sensible boundary for trust,
> potentially by using submodules.

Yes... probably what I should do in the future.

> Since you mention `#:methods` in particular, you should be aware of some
> subtle corners that make it tricky (and potentially expensive at runtime)
> to protect `racket/generic` methods comprehensively with contracts. (Here's
> a pointer to some discussions.
> <https://github.com/racket/racket/issues/1710>) I think just working with
> struct-type properties can make sense when you don't really need most of
> the features `racket/generic` gives you.

:O

David Storrs

unread,
Sep 3, 2020, 11:30:17 AM9/3/20
to Christopher Lemmer Webber, Philip McGrath, Racket Users
For the record, a self-plug:

#lang racket

(require struct-plus-plus)

(struct foo-guard (bar baz)

  #:guard (struct-guard/c any/c list?))


(struct/contract foo-contract ([bar any/c]
                               [baz list?]))

(struct++ foo-spp  ([bar any/c]
                    [baz list?]))

(display "#:guard:         ")


(time
 (for ([i 1000000])
   (foo-guard 'yeah '(buddy))))

(display "struct/contract: ")

(time
 (for ([i 1000000])
   (foo-contract 'yeah '(buddy))))

(display "struct++:        ")
(time
 (for ([i 1000000])
   (foo-spp++ #:bar 'yeah #:baz '(buddy))))

#:guard:         cpu time: 3335 real time: 3361 gc time: 90
struct/contract: cpu time: 217 real time: 218 gc time: 3
struct++:        cpu time: 1255 real time: 1262 gc time: 23

The struct++ version is slower at creation but gives you more security guarantees later on in the form of functional setters, business rules, and wrapper functions to normalize data.  For example:


; This accepts a symbol or string and forces it to string.                                                                
(struct++ baz-spp ([foo (or/c symbol? string?) ~a]) #:transparent)
(display "Result: ") (println (baz-spp++ #:foo 'bob))
Result: (baz-spp "bob")

; Any struct++ struct will throw an exception if given invalid data                            
(struct++ bar-spp ([foo integer?]))
(display "Throws: ") (bar-spp++ #:foo 'bob)
Throws:
; bar-spp++: contract violation                                                                
;   expected: integer?                                                                        
;   given: 'bob                                                                                
;   in: the #:foo argument of                                                                  
;       (-> #:foo integer? bar-spp?)                                                          
;   contract from: (function bar-spp++)                                                        
;   blaming: /Users/dstorrs/bmtc_dev/app/test.rkt                                              
;    (assuming the contract is correct)                                                        
;   at: /Users/dstorrs/bmtc_dev/app/test.rkt                                                  
; Context:                                                                                    
;  "/Users/dstorrs/bmtc_dev/app/test.rkt":1:1 [running body]                                  


; Example of more heavily verified struct.  It will accept a string or                        
; symbol for name and force it to string.  It will round the age down                          
; so it shows only years. It will default the 'gender' field to the                            
; symbol 'unknown.  It will make a database connection and verify that                        
; the department ID is valid.  (In practice you would want to check                            
; this against an in-RAM table instead of going to disk.)  
;
(define (dbh) "mock function that should return a database handle")                                                            
(struct++ person ([name (or/c symbol? string?) ~a]
                  [age  positive? (compose inexact->exact floor)]
                  [(gender 'unknown) (or/c 'male 'female 'other 'unknown)]
                  [user-id exact-positive-integer?])
          (#:rule ("department-id exists in the database"
                   #:check (user-id)
                   [(not (null? (query-rows (dbh) ; get database handle                        
                                            "SELECT id FROM users WHERE id=$1"
                                            user-id)))]))
          #:transparent)
(display "Result: ")(println (person++ #:name 'bob #:age 18.5 #:user-id 2))

Result: (person "bob" 18 'unknown 2)

It also provides reflection, functional setters (dotted and dashed versions), transformation rules, and other things.  https://docs.racket-lang.org/struct-plus-plus/index.html





--
You received this message because you are subscribed to the Google Groups "Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.

Christopher Lemmer Webber

unread,
Sep 3, 2020, 6:58:50 PM9/3/20
to David Storrs, Philip McGrath, Racket Users
Cool! Thanks for sharing :)
Reply all
Reply to author
Forward
0 new messages