Closed
Description
TypeScript Version: 3.7.0-beta
Search Terms: spread out of memory
When strictNullChecks is set, repeatedly spreading properties doesn't scale:
// Slow
const props: Record<string, string> = {
...config.a !== undefined && {a: config.a.toString()},
...config.b !== undefined && {b: config.b.toString()},
...config.c !== undefined && {c: config.c.toString()},
// ...
}
// Fast
const props: Record<string, string> = {}
if (config.a !== undefined) props.a = config.a.toString()
if (config.b !== undefined) props.b = config.b.toString()
if (config.c !== undefined) props.c = config.c.toString()
// ...
In a real codebase, the issue will likely surface initially as slower and slower compilation times. Eventually, an out of memory error appears to occur randomly since it can be triggered by simply repeating an existing pattern a few more times. The VS Code experience in these scenarios is also poor as the TypeScript server hangs.
Code
interface Props {
readonly a?: string
readonly b?: string
readonly c?: string
readonly d?: string
readonly e?: string
readonly f?: string
readonly g?: string
readonly h?: string
readonly i?: string
readonly j?: string
readonly k?: string
readonly l?: string
readonly m?: string
readonly n?: string
readonly o?: string
readonly p?: string
readonly q?: string
readonly r?: string
readonly s?: string
readonly t?: string
readonly u?: string
readonly v?: string
readonly w?: string
readonly x?: string
readonly y?: string
readonly z?: string
}
function parseWithSpread(config: Record<string, number>): Props {
return {
...config.a !== undefined && {a: config.a.toString()},
...config.b !== undefined && {b: config.b.toString()},
...config.c !== undefined && {c: config.c.toString()},
...config.d !== undefined && {d: config.d.toString()},
...config.e !== undefined && {e: config.e.toString()},
...config.f !== undefined && {f: config.f.toString()},
...config.g !== undefined && {g: config.g.toString()},
...config.h !== undefined && {h: config.h.toString()},
...config.i !== undefined && {i: config.i.toString()},
...config.j !== undefined && {j: config.j.toString()},
...config.k !== undefined && {k: config.k.toString()},
...config.l !== undefined && {l: config.l.toString()},
...config.m !== undefined && {m: config.m.toString()},
...config.n !== undefined && {n: config.n.toString()},
...config.o !== undefined && {o: config.o.toString()},
...config.p !== undefined && {p: config.p.toString()},
...config.q !== undefined && {q: config.q.toString()},
...config.r !== undefined && {r: config.r.toString()},
...config.s !== undefined && {s: config.s.toString()},
...config.t !== undefined && {t: config.t.toString()},
...config.u !== undefined && {u: config.u.toString()},
...config.v !== undefined && {v: config.v.toString()},
...config.w !== undefined && {w: config.w.toString()},
...config.x !== undefined && {x: config.x.toString()},
...config.y !== undefined && {y: config.y.toString()},
...config.z !== undefined && {z: config.z.toString()}
}
}
parseWithSpread({a: 1, b: 2, z: 26})
Expected behavior: Compilation performance is on par with conditional syntax.
Actual behavior: An out of memory error occurs.
Related Issues:
Trace:
Local execution of `time tsc --strictNullChecks` (v3.7.0-beta) with the
uncommented code:
<--- Last few GCs --->
at[10896:0x2b128d0] 127908 ms: Mark-sweep 2043.7 (2057.8) -> 2040.9 (2058.1) MB, 752.5 / 0.0 ms (+ 227.7 ms in 67 steps since start of marking, biggest step 17.0 ms, walltime since start of marking 997 ms) (average mu = 0.092, current mu = 0.017) allocat[10896:0x2b128d0] 129003 ms: Mark-sweep 2043.7 (2058.1) -> 2041.6 (2057.6) MB, 917.0 / 0.0 ms (+ 167.1 ms in 43 steps since start of marking, biggest step 10.2 ms, walltime since start of marking 1095 ms) (average mu = 0.048, current mu = 0.011) alloca
<--- JS stacktrace --->
==== JS stack trace =========================================
0: ExitFrame [pc: 0x136cf99]
Security context: 0x1078847808a1 <JSObject>
1: createObjectType(aka createObjectType) [0x35be1a4076d9] [/home/stephen/dev/nature-elsewhere/node_modules/typescript/lib/tsc.js:~29235] [pc=0xeb30e605cfb](this=0x005fc70404a9 <undefined>,16,0x14f39511c269 <Symbol map = 0x234283fb2161>)
2: getSpreadType(aka getSpreadType) [0x174d4996a8d9] [/home/stephen/dev/nature-elsewhere/node_modules/typescript/lib/ts...
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
Writing Node.js report to file: report.20191019.123833.10896.0.001.json
Node.js report completed
1: 0x9d2fd0 node::Abort() [node]
2: 0x9d4166 node::OnFatalError(char const*, char const*) [node]
3: 0xb3281e v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [node]
4: 0xb32b99 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [node]
5: 0xcddeb5 [node]
6: 0xcde546 v8::internal::Heap::RecomputeLimits(v8::internal::GarbageCollector) [node]
7: 0xcea3da v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [node]
8: 0xceb2e5 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [node]
9: 0xcedcf8 v8::internal::Heap::AllocateRawWithRetryOrFail(int, v8::internal::AllocationType, v8::internal::AllocationAlignment) [node]
10: 0xcb4627 v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationType) [node]
11: 0xfea55b v8::internal::Runtime_AllocateInYoungGeneration(int, unsigned long*, v8::internal::Isolate*) [node]
12: 0x136cf99 [node]
Aborted (core dumped)
real 2m10.683s
user 2m50.317s
sys 0m1.371s