Open NoahStoryM opened 2 years ago
I'm confused by what you write at the beginning/in the title. Are you running into performance problems with using parameter/c
, or with typed/untyped interop using the Parameter
type, or both?
And if you can reproduce this without Typed Racket, just using contracts, that would be helpful for debugging.
I'm confused by what you write at the beginning/in the title. Are you running into performance problems with using parameter/c, or with typed/untyped interop using the Parameter type, or both?
The performance problem appeared after I added a type to a variable which is attached to parameter/c
:
#lang typed/racket
(module untyped racket/base
(provide (contract-out [current-bm (parameter/c (or/c #f (is-a?/c bitmap%)))]
[current-dc (parameter/c (or/c #f (is-a?/c bitmap-dc%)))]))
(define current-bm (make-parameter #f))
(define current-dc (make-parameter #f)))
(require/typed 'untyped
;; It causes the program to be very slow.
[current-bm (Parameter (Option (Instance Bitmap%)))]
[current-dc (Parameter (Option (Instance Bitmap-DC%)))])
#;(unsafe-require/typed 'untyped
;; It works fine.
[current-bm (Parameter (Option (Instance Bitmap%)))]
[current-dc (Parameter (Option (Instance Bitmap-DC%)))])
;;; Program
(: apply-painter ...)
...
And if you can reproduce this without Typed Racket, just using contracts, that would be helpful for debugging.
I think the problem seems to be due to parameter/c
, but I don't know exactly why.
Ah, so it's the combination of parameter/c
in the untyped code and the contract generated by Typed Racket that makes things slow?
If that's the case, then two uses of parameter/c
should produce the same slowdown.
One thing that jumps out at me here is that the types are very large with lots of checks that're going to be generated by them; so it might not be parameter/c specifically but just a large extra number of contract checks that're being generated by the bitmap and bitmap-dc contracts.
Where can I get typed/sicp-pict
so that I can run the code?
Also, the code provided in this issue doesn't actually run -- it has a bunch of missing requires, various syntax errors. Do you have the version of the code you ran?
If that's the case, then two uses of parameter/c should produce the same slowdown.
You're right, I tried to use cast
and it also made the program slow:
#lang typed/racket
(require typed/racket/draw typed/sicp-pict)
(let ([current-bm (cast current-bm (Parameter (Option (Instance Bitmap%))))]
[current-dc (cast current-dc (Parameter (Option (Instance Bitmap-DC%))))])
;;; Program ...
)
Where can I get typed/sicp-pict so that I can run the code?
I'm trying to add typed version to sicp-pict
: https://github.com/NoahStoryM/sicp/tree/typed
Ok, I don't think this has anything to do with parameter/c
. If I change (parameter/c ...)
in sicp/sicp-pict/main.rkt
to any/c
, the program is still slow. So the problem is just with the contract that Typed Racket generates for (Parameter (Option (Instance Bitmap%)))
etc.
Further investigation suggests that it's the current-dc
contract that is very slow. Having a contract on current-bm
doesn't impose much overhead (which is unsurprising, it isn't used much in the actual library).
Further investigation:
info
log level prints an almost-endless stream of collapsible-value-bailout: arrow: can't prove single-return-value
, suggesting that some contract internal things are happening in a loop.racket/private/contract/class-c-old.rkt
, and usually in the object/c-stronger
function. I tried making object/c-stronger
always produce #f
, but that didn't seem to have much impact.all-new-ctcs
etc without checking). That was a huge performance improvement, down to about 10x slower than the uncontracted version (@NoahStoryM's results were about 10000x slower). I think the next question is for @rfindler, which is why is that code running so often, and can it be made faster/skipped.
Hi @samth can you please share what you did to make the code run? Apparently you figured out something that I cannot.
I just used the branch referenced by @NoahStoryM above.
I see. Thanks.
Looks like the O(n^2) in that code finally bit us. This diff is an improvement:
diff --git a/racket/collects/racket/private/class-c-old.rkt b/racket/collects/racket/private/class-c-old.rkt
index d60fc12fe6..80cf301b55 100644
--- a/racket/collects/racket/private/class-c-old.rkt
+++ b/racket/collects/racket/private/class-c-old.rkt
@@ -1384,6 +1384,15 @@
(contract? y)
(contract-stronger? x y)))
+ (define (ormap-first-few f lst)
+ (let loop ([lst lst]
+ [n 5])
+ (cond
+ [(null? lst) #f]
+ [(zero? n) #f]
+ [else (or (f (car lst))
+ (loop (cdr lst) (- n 1)))])))
+
(define-values (reverse-without-redundant-ctcs
reverse-without-redundant-projs
dropped-something?)
@@ -1399,8 +1408,8 @@
(cons this-proj prior-projs)
dropped-something?)]
[else
- (if (and (ormap (λ (x) (stronger? x this-ctc)) prior-ctcs)
- (ormap (λ (x) (stronger? this-ctc x)) next-ctcs))
+ (if (and (ormap-first-few (λ (x) (stronger? x this-ctc)) prior-ctcs)
+ (ormap-first-few (λ (x) (stronger? this-ctc x)) next-ctcs))
(loop prior-ctcs prior-projs
(car next-ctcs) (cdr next-ctcs) (car next-projs) (cdr next-projs)
#t)
On second thought, I think the real problem is this definition
(define (case->-stronger? this that) #f)
:) I'll try looking into that to see if improving it fixes the problem.
The commit I pushed a few minutes ago seems to improve the performance of the example a good amount.
Just to understand the current situation, the O(n^2) behavior is still there, but now much less likely to be a problem because many fewer elements are in the list now. Is that right?
That's right. In fact, I think that if the list ever goes beyond a constant length for TR-generated contracts, then that's a bug somewhere -- I'm not 100% confident of that fact, but it feels reasonable to me that we cannot get very many non-unique contracts stacking up with TR and repeating the same ones'll get discarded.
There are ways to break this assumption if you can write your own contracts, however.
Ideas on what would be better?
That makes sense. Maybe I should put a log statement in there for when it gets over 5.
That sounds like a good idea! Here's one way to do it; I would have pushed it already but I'm unsure what's the best logger to log it to:
diff --git a/racket/collects/racket/private/class-c-old.rkt b/racket/collects/racket/private/class-c-old.rkt
index d60fc12fe6..929d7c4a69 100644
--- a/racket/collects/racket/private/class-c-old.rkt
+++ b/racket/collects/racket/private/class-c-old.rkt
@@ -1393,20 +1393,26 @@
[next-ctcs (cdr all-new-ctcs)]
[this-proj (car all-new-projs)]
[next-projs (cdr all-new-projs)]
- [dropped-something? #f])
+ [dropped-something? #f]
+ [n 0])
(cond
- [(null? next-ctcs) (values (cons this-ctc prior-ctcs)
- (cons this-proj prior-projs)
- dropped-something?)]
+ [(null? next-ctcs)
+ (when (n . > . 10)
+ (log-something ...))
+ (values (cons this-ctc prior-ctcs)
+ (cons this-proj prior-projs)
+ dropped-something?)]
[else
(if (and (ormap (λ (x) (stronger? x this-ctc)) prior-ctcs)
(ormap (λ (x) (stronger? this-ctc x)) next-ctcs))
(loop prior-ctcs prior-projs
(car next-ctcs) (cdr next-ctcs) (car next-projs) (cdr next-projs)
- #t)
+ #t
+ (+ n 1))
(loop (cons this-ctc prior-ctcs) (cons this-proj prior-projs)
(car next-ctcs) (cdr next-ctcs) (car next-projs) (cdr next-projs)
- dropped-something?))])))
+ dropped-something?
+ (+ n 1)))])))
(define unwrapped-class
(if (has-impersonator-prop:instanceof/c-unwrapped-class? val)
I see that there are places where the logger racket/contract is used for generic "something might be missing that matter for performance" log messages, so I followed that (even though, technically, this isn't in racket/contract).
I found this problem while testing
typed/sicp-pict
(https://github.com/sicp-lang/sicp/pull/38).In
sicp-pict/main.rkt
, I have added contracts tocurrent-bm
andcurrent-dc
:What version of Racket are you using?
v8.5 [cs]
What program did you run?
I have no idea how to simplify this test case, so I'll just post the code:
What should have happened?
If I add type to
current-dc
viaunsafe-require/typed/provide
intyped/sicp-pict/main.rkt
:the program runs fine:
What happened?
If I use
require/typed/provide
:the program runs very slowly: