Skip to content

[Performance] Adding just one more method causes check time to jump from 2s to 100+s #31612

Closed
@AnyhowStep

Description

@AnyhowStep

TypeScript Version: 3.5.0-dev.20190523

Search Terms: method, generic, interface, slow check time, this

Code

Scroll to the bottom to see the repository link and reproduction steps.
For now, I'll post a rough example of what I am experiencing,


I first had something like this,

type Foo<U, Blah> = /* some complicated type */;
type Bar<V, Blah> = /* some complicated type */;
interface Interface1<T> { /* some complicated type */ }
type Baz<B> = /* some complicated type */;

interface Interface0<T> extends Interface1<T> {
    foo<U> (u : U) : (Baz<Foo<U, this>>);

    //A bunch of other methods.
}

The above compiles in 2s. Nothing suspicious.

Files:           130
Lines:         38454
Nodes:        171047
Identifiers:   56900
Symbols:       54146
Types:         18772
Memory used: 111991K
I/O read:      0.01s
I/O write:     0.03s
Parse time:    0.50s
Bind time:     0.27s
Check time:    1.35s
Emit time:     0.48s
Total time:    2.59s

Then, I added one more method,

type Foo<U, Blah> = /* some complicated type */;
type Bar<V, Blah> = /* some complicated type */;
interface Interface1<T> { /* some complicated type */ }
type Baz<B> = /* some complicated type */;

interface Interface0<T> extends Interface1<T> {
    foo<U> (u : U) : (Baz<Foo<U, this>>);

    //The new method
    bar<V> (v : V) : (Baz<Bar<V, this>>);

    //A bunch of other methods.
}

And then it took 102s!

Files:           130
Lines:         38452
Nodes:        171976
Identifiers:   57291
Symbols:      120444
Types:         61118
Memory used: 174232K
I/O read:      0.01s
I/O write:     0.03s
Parse time:    0.52s
Bind time:     0.34s
Check time:  101.11s
Emit time:     0.53s
Total time:  102.51s

This was very suspicious. I looked at the output .d.ts file but there was nothing out of place.
In fact, emit only took 0.53s


I assumed this new method must be the culprit.

However, I decided to comment out everything but the new method,

type Foo<U, Blah> = /* some complicated type */;
type Bar<V, Blah> = /* some complicated type */;
interface Interface1<T> { /* some complicated type */ }
type Baz<B> = /* some complicated type */;

interface Interface0<T> extends Interface1<T> {
    //commented out
    //foo<U> (u : U) : (Baz<Foo<U, this>>);

    //The new method
    bar<V> (v : V) : (Baz<Bar<V, this>>);

    /*
    //commented out
    //A bunch of other methods.
    */
}

This time, it only took 3s.

Files:           130
Lines:         38452
Nodes:        171950
Identifiers:   57280
Symbols:       88571
Types:         38360
Memory used: 126678K
I/O read:      0.01s
I/O write:     0.24s
Parse time:    0.47s
Bind time:     0.26s
Check time:    1.79s
Emit time:     0.71s
Total time:    3.23s

So, the problem wasn't this new method, it seemed.


I was so confused. This didn't make sense.
Then, I got to thinking, "What if it was the number of methods?"
Like, just add one more arbitrary method and see what happens.

Copy+paste foo<>() and rename it to foo2. Change nothing else,

interface Interface0<T> extends Interface1<T> {
    //No longer commented
    foo<U> (u : U) : (Baz<Foo<U, this>>);
    foo2<U> (u : U) : (Baz<Foo<U, this>>);

    //Notice bar<V>() is now deleted

    //No longer commented
    //A bunch of other methods.
}

The above now compiles in 47+s. What?

Files:           130
Lines:         38461
Nodes:        171976
Identifiers:   57291
Symbols:      118614
Types:         57314
Memory used: 139677K
I/O read:      0.01s
I/O write:     0.04s
Parse time:    0.51s
Bind time:     0.29s
Check time:   45.83s
Emit time:     0.56s
Total time:   47.20s

I looked at the generated .d.ts file and I didn't see any explosion of types.
It looked exactly the way I predicted it should and only took up 29 lines.

So, without foo2<>(), it takes 2s. With it, it takes 47s.
And foo2<>() is exactly the same as foo<>()


Copy+paste foo<>() (again) and rename it to foo3. Change nothing else,

interface Interface0<T> extends Interface1<T> {
    foo<U> (u : U) : (Baz<Foo<U, this>>);
    foo2<U> (u : U) : (Baz<Foo<U, this>>);
    foo3<U> (u : U) : (Baz<Foo<U, this>>);

    //A bunch of other methods.
}

I started this build at 22:20, 2019 May 27.
Well, it's been checking for 40 minutes and hasn't finished yet.

Expected behavior:

  • Adding copies of foo<>() without any changes shouldn't increase check time.
  • If bar<>() alone compiles in 3s, and x<>(), y<>(), z<>() compile in 2s, compiling all of these methods should not take 102s.

Actual behavior:

  • Check time is crazy long.
  • It's still checking as of this writing.
  • My laptop, a ThinkPad P52, is making scary exhaust fan noises.
  • My laptop is the hottest it has ever been, I think the CPU might melt.

Github Link:

https://github.com/AnyhowStep/type-mapping/tree/d09851dc437aa25b702a27fa7b6eb5060dd476c5/src

  1. Clone the repository
  2. npm install
  3. npm run build
  4. Grab coffee

The problem is specifically in src/fluent.ts

You'll see derive2<>(), derive3<>(). Uncommented, it'll take forever to check.
https://github.com/AnyhowStep/type-mapping/blob/d09851dc437aa25b702a27fa7b6eb5060dd476c5/src/fluent.ts#L64

You'll see rename<>(). Uncommented, it'll take 100s to check.
But if you comment everything else but rename<>(), it'll take 3s to check.
https://github.com/AnyhowStep/type-mapping/blob/d09851dc437aa25b702a27fa7b6eb5060dd476c5/src/fluent.ts#L86

Related Issues:

I know there are a bunch of performance related issues around here.
I've even opened a few myself in the past.
But I'm not even sure what is related to this.


As a side note, I usually implement my libraries with just a function composition API.
Then, when I want to make it more usable, I add a fluent API on top of it.

But it seems like, with TypeScript, whenever I start to build the fluent API wrapper, I get weird problems like reaching the max instantiation depth, or super-long check times.

I guess function composition > fluent?


I feel like this problem might have to do with the fact that it's a generic interface and the return type of the methods reference the this type.


This reminds me of TREE(3).

  • TREE(1) = 1
  • TREE(2) = 3
  • TREE(3) > googolplex

Except,

  • foo(1) = 2
  • foo(2) = 47
  • foo(3) > TREE(3) (Proof is left as an exercise to the reader)

Metadata

Metadata

Assignees

Labels

BugA bug in TypeScript

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions