Skip to content

Commit 4f68339

Browse files
authored
Some of the changes to improve --build memory footprint (microsoft#2604)
1 parent eebe33a commit 4f68339

8 files changed

Lines changed: 55 additions & 73 deletions

File tree

internal/compiler/program.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1405,9 +1405,10 @@ func (p *Program) Emit(ctx context.Context, options EmitOptions) *EmitResult {
14051405
}
14061406
}
14071407

1408+
newLine := p.Options().NewLine.GetNewLineCharacter()
14081409
writerPool := &sync.Pool{
14091410
New: func() any {
1410-
return printer.NewTextWriter(p.Options().NewLine.GetNewLineCharacter())
1411+
return printer.NewTextWriter(newLine)
14111412
},
14121413
}
14131414
wg := core.NewWorkGroup(p.SingleThreaded())

internal/diagnostics/diagnostics_generated.go

Lines changed: 0 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/diagnostics/extraDiagnosticMessages.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,6 @@
3939
"category": "Message",
4040
"code": 100009
4141
},
42-
"all, unless --singleThreaded is passed.": {
43-
"category": "Message",
44-
"code": 100010
45-
},
4642
"Non-relative paths are not allowed. Did you forget a leading './'?": {
4743
"category": "Error",
4844
"code": 5090

internal/execute/build/buildtask.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,10 +200,6 @@ func (t *BuildTask) updateDownstream(orchestrator *Orchestrator, path tspath.Pat
200200
}
201201

202202
func (t *BuildTask) compileAndEmit(orchestrator *Orchestrator, path tspath.Path) {
203-
if orchestrator.buildSemaphore != nil {
204-
orchestrator.buildSemaphore <- struct{}{} // acquire slot
205-
defer func() { <-orchestrator.buildSemaphore }() // release slot
206-
}
207203
t.errors = nil
208204
if orchestrator.opts.Command.BuildOptions.Verbose.IsTrue() {
209205
t.result.reportStatus(ast.NewCompilerDiagnostic(diagnostics.Building_project_0, orchestrator.relativeFileName(t.config)))

internal/execute/build/host.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,15 @@ func (h *host) Trace(msg *diagnostics.Message, args ...any) {
5151
}
5252

5353
func (h *host) GetSourceFile(opts ast.SourceFileParseOptions) *ast.SourceFile {
54-
// Cache dts and json files as they will be reused
55-
return h.sourceFiles.loadOrStoreNewIf(opts, h.host.GetSourceFile, func(value *ast.SourceFile) bool {
56-
return value != nil && (tspath.IsDeclarationFileName(opts.FileName) || tspath.FileExtensionIs(opts.FileName, tspath.ExtensionJson))
57-
})
54+
if tspath.IsDeclarationFileName(opts.FileName) || tspath.FileExtensionIs(opts.FileName, tspath.ExtensionJson) {
55+
// Cache dts and json files as they will be reused
56+
return h.sourceFiles.loadOrStore(opts, h.host.GetSourceFile, false /* allowZero */)
57+
}
58+
return h.host.GetSourceFile(opts)
5859
}
5960

6061
func (h *host) GetResolvedProjectReference(fileName string, path tspath.Path) *tsoptions.ParsedCommandLine {
61-
return h.resolvedReferences.loadOrStoreNew(path, func(path tspath.Path) *tsoptions.ParsedCommandLine {
62+
return h.resolvedReferences.loadOrStore(path, func(path tspath.Path) *tsoptions.ParsedCommandLine {
6263
configStart := h.orchestrator.opts.Sys.Now()
6364
// Wrap command line options in "compilerOptions" key to match tsconfig.json structure
6465
var commandLineRaw *collections.OrderedMap[string, any]
@@ -71,7 +72,7 @@ func (h *host) GetResolvedProjectReference(fileName string, path tspath.Path) *t
7172
configTime := h.orchestrator.opts.Sys.Now().Sub(configStart)
7273
h.configTimes.Store(path, configTime)
7374
return commandLine
74-
})
75+
}, true /* allowZero */)
7576
}
7677

7778
func (h *host) ReadBuildInfo(config *tsoptions.ParsedCommandLine) *incremental.BuildInfo {

internal/execute/build/orchestrator.go

Lines changed: 41 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ type Orchestrator struct {
6161
tasks *collections.SyncMap[tspath.Path, *BuildTask]
6262
order []string
6363
errors []*ast.Diagnostic
64-
// Semaphore to limit concurrent builds
65-
buildSemaphore chan struct{}
6664

6765
errorSummaryReporter tsc.DiagnosticsReporter
6866
watchStatusReporter tsc.DiagnosticReporter
@@ -240,14 +238,9 @@ func (o *Orchestrator) Watch() {
240238
func (o *Orchestrator) updateWatch() {
241239
oldCache := o.host.mTimes
242240
o.host.mTimes = &collections.SyncMap[tspath.Path, time.Time]{}
243-
wg := core.NewWorkGroup(o.opts.Command.CompilerOptions.SingleThreaded.IsTrue())
244-
o.tasks.Range(func(path tspath.Path, task *BuildTask) bool {
245-
wg.Queue(func() {
246-
task.updateWatch(o, oldCache)
247-
})
248-
return true
241+
o.rangeTask(func(path tspath.Path, task *BuildTask) {
242+
task.updateWatch(o, oldCache)
249243
})
250-
wg.RunAndWait()
251244
}
252245

253246
func (o *Orchestrator) resetCaches() {
@@ -263,20 +256,14 @@ func (o *Orchestrator) DoCycle() {
263256
var needsConfigUpdate atomic.Bool
264257
var needsUpdate atomic.Bool
265258
mTimes := o.host.mTimes.Clone()
266-
wg := core.NewWorkGroup(o.opts.Command.CompilerOptions.SingleThreaded.IsTrue())
267-
o.tasks.Range(func(path tspath.Path, task *BuildTask) bool {
268-
wg.Queue(func() {
269-
if updateKind := task.hasUpdate(o, path); updateKind != updateKindNone {
270-
needsUpdate.Store(true)
271-
if updateKind == updateKindConfig {
272-
needsConfigUpdate.Store(true)
273-
}
259+
o.rangeTask(func(path tspath.Path, task *BuildTask) {
260+
if updateKind := task.hasUpdate(o, path); updateKind != updateKindNone {
261+
needsUpdate.Store(true)
262+
if updateKind == updateKindConfig {
263+
needsConfigUpdate.Store(true)
274264
}
275-
})
276-
// Watch for file changes
277-
return true
265+
}
278266
})
279-
wg.RunAndWait()
280267

281268
if !needsUpdate.Load() {
282269
o.host.mTimes = mTimes
@@ -307,11 +294,9 @@ func (o *Orchestrator) buildOrClean() tsc.CommandLineResult {
307294
var buildResult orchestratorResult
308295
if len(o.errors) == 0 {
309296
buildResult.statistics.Projects = len(o.Order())
310-
if o.opts.Command.CompilerOptions.SingleThreaded.IsTrue() {
311-
o.singleThreadedBuildOrClean(&buildResult)
312-
} else {
313-
o.multiThreadedBuildOrClean(&buildResult)
314-
}
297+
o.rangeTask(func(path tspath.Path, task *BuildTask) {
298+
o.buildOrCleanProject(task, path, &buildResult)
299+
})
315300
} else {
316301
// Circularity errors prevent any project from being built
317302
buildResult.result.Status = tsc.ExitStatusProjectReferenceCycle_OutputsSkipped
@@ -325,25 +310,40 @@ func (o *Orchestrator) buildOrClean() tsc.CommandLineResult {
325310
return buildResult.result
326311
}
327312

328-
func (o *Orchestrator) singleThreadedBuildOrClean(buildResult *orchestratorResult) {
329-
// Go in the order since only one project can be built at a time so that random order isnt picked by work group creating deadlock
330-
for _, config := range o.Order() {
313+
func (o *Orchestrator) rangeTask(f func(path tspath.Path, task *BuildTask)) {
314+
numRoutines := 4
315+
if o.opts.Command.CompilerOptions.SingleThreaded.IsTrue() {
316+
numRoutines = 1
317+
} else if builders := o.opts.Command.BuildOptions.Builders; builders != nil {
318+
numRoutines = *builders
319+
}
320+
321+
var currentTaskIndex atomic.Int64
322+
getNextTask := func() (tspath.Path, *BuildTask, bool) {
323+
index := int(currentTaskIndex.Add(1) - 1)
324+
if index >= len(o.order) {
325+
return "", nil, false
326+
}
327+
config := o.order[index]
331328
path := o.toPath(config)
332329
task := o.getTask(path)
333-
o.buildOrCleanProject(task, path, buildResult)
330+
return path, task, true
331+
}
332+
runTask := func() {
333+
for path, task, ok := getNextTask(); ok; path, task, ok = getNextTask() {
334+
f(path, task)
335+
}
334336
}
335-
}
336337

337-
func (o *Orchestrator) multiThreadedBuildOrClean(buildResult *orchestratorResult) {
338-
// Spin off the threads with waiting on upstream to build before actual project build
339-
wg := core.NewWorkGroup(false)
340-
o.tasks.Range(func(path tspath.Path, task *BuildTask) bool {
341-
wg.Queue(func() {
342-
o.buildOrCleanProject(task, path, buildResult)
343-
})
344-
return true
345-
})
346-
wg.RunAndWait()
338+
if numRoutines == 1 {
339+
runTask()
340+
} else {
341+
wg := core.NewWorkGroup(false)
342+
for range numRoutines {
343+
wg.Queue(runTask)
344+
}
345+
wg.RunAndWait()
346+
}
347347
}
348348

349349
func (o *Orchestrator) buildOrCleanProject(task *BuildTask, path tspath.Path, buildResult *orchestratorResult) {
@@ -398,9 +398,5 @@ func NewOrchestrator(opts Options) *Orchestrator {
398398
} else {
399399
orchestrator.errorSummaryReporter = tsc.CreateReportErrorSummary(opts.Sys, opts.Command.Locale(), opts.Command.CompilerOptions)
400400
}
401-
// If we want to build more than one project at a time, create a semaphore to limit concurrency
402-
if builders := opts.Command.BuildOptions.Builders; builders != nil {
403-
orchestrator.buildSemaphore = make(chan struct{}, *builders)
404-
}
405401
return orchestrator
406402
}

internal/execute/build/parseCache.go

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,23 @@ import (
66
"github.com/microsoft/typescript-go/internal/collections"
77
)
88

9-
type parseCacheEntry[V any] struct {
9+
type parseCacheEntry[V comparable] struct {
1010
value V
1111
mu sync.Mutex
1212
}
1313

14-
type parseCache[K comparable, V any] struct {
14+
type parseCache[K comparable, V comparable] struct {
1515
entries collections.SyncMap[K, *parseCacheEntry[V]]
1616
}
1717

18-
func (c *parseCache[K, V]) loadOrStoreNew(key K, parse func(K) V) V {
19-
return c.loadOrStoreNewIf(key, parse, func(value V) bool { return true })
20-
}
21-
22-
func (c *parseCache[K, V]) loadOrStoreNewIf(key K, parse func(K) V, canCacheValue func(V) bool) V {
18+
func (c *parseCache[K, V]) loadOrStore(key K, parse func(K) V, allowZero bool) V {
2319
newEntry := &parseCacheEntry[V]{}
2420
newEntry.mu.Lock()
2521
defer newEntry.mu.Unlock()
2622
if entry, loaded := c.entries.LoadOrStore(key, newEntry); loaded {
2723
entry.mu.Lock()
2824
defer entry.mu.Unlock()
29-
if canCacheValue(entry.value) {
25+
if allowZero || entry.value != *new(V) {
3026
return entry.value
3127
}
3228
newEntry = entry

internal/tsoptions/declsbuild.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ var OptionsForBuild = []*CommandLineOption{
5454
Kind: CommandLineOptionTypeNumber,
5555
Category: diagnostics.Command_line_Options,
5656
Description: diagnostics.Set_the_number_of_projects_to_build_concurrently,
57-
DefaultValueDescription: diagnostics.X_all_unless_singleThreaded_is_passed,
57+
DefaultValueDescription: diagnostics.X_4_unless_singleThreaded_is_passed,
5858
minValue: 1,
5959
},
6060
{

0 commit comments

Comments
 (0)