`ngcc` supports both synchronous and asynchronous execution. The default mode when using `ngcc` programmatically (which is how `@angular/cli` is using it) is synchronous. When running `ngcc` from the command line (i.e. via the `ivy-ngcc` script), it runs in async mode. Previously, the work would be executed in the same way in both modes. This commit improves the performance of `ngcc` in async mode by processing tasks in parallel on multiple processes. It uses the Node.js built-in [`cluster` module](https://nodejs.org/api/cluster.html) to launch a cluster of Node.js processes and take advantage of multi-core systems. Preliminary comparisons indicate a 1.8x to 2.6x speed improvement when processing the angular.io app (apparently depending on the OS, number of available cores, system load, etc.). Further investigation is needed to better understand these numbers and identify potential areas of improvement. Inspired by/Based on @alxhub's prototype: alxhub/angular@cb631bdb1 Original design doc: https://hackmd.io/uYG9CJrFQZ-6FtKqpnYJAA?view Jira issue: [FW-1460](https://angular-team.atlassian.net/browse/FW-1460) PR Close #32427
92 lines
3.2 KiB
TypeScript
92 lines
3.2 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
/// <reference types="node" />
|
|
|
|
import * as cluster from 'cluster';
|
|
|
|
import {ClusterExecutor} from '../../../src/execution/cluster/executor';
|
|
import {ClusterMaster} from '../../../src/execution/cluster/master';
|
|
import {ClusterWorker} from '../../../src/execution/cluster/worker';
|
|
import {PackageJsonUpdater} from '../../../src/writing/package_json_updater';
|
|
import {MockLogger} from '../../helpers/mock_logger';
|
|
import {mockProperty} from '../../helpers/spy_utils';
|
|
|
|
|
|
describe('ClusterExecutor', () => {
|
|
const runAsClusterMaster = mockProperty(cluster, 'isMaster');
|
|
let masterRunSpy: jasmine.Spy;
|
|
let workerRunSpy: jasmine.Spy;
|
|
let mockLogger: MockLogger;
|
|
let executor: ClusterExecutor;
|
|
|
|
beforeEach(() => {
|
|
masterRunSpy = spyOn(ClusterMaster.prototype, 'run');
|
|
workerRunSpy = spyOn(ClusterWorker.prototype, 'run');
|
|
|
|
mockLogger = new MockLogger();
|
|
executor = new ClusterExecutor(42, mockLogger, null as unknown as PackageJsonUpdater);
|
|
});
|
|
|
|
describe('execute()', () => {
|
|
describe('(on cluster master)', () => {
|
|
beforeEach(() => runAsClusterMaster(true));
|
|
|
|
it('should log debug info about the executor', () => {
|
|
const anyFn: () => any = () => undefined;
|
|
executor.execute(anyFn, anyFn);
|
|
|
|
expect(mockLogger.logs.debug).toEqual([
|
|
['Running ngcc on ClusterExecutor (using 42 worker processes).'],
|
|
]);
|
|
});
|
|
|
|
it('should delegate to `ClusterMaster#run()`', async() => {
|
|
masterRunSpy.and.returnValue('CusterMaster#run()');
|
|
const analyzeEntryPointsSpy = jasmine.createSpy('analyzeEntryPoints');
|
|
const createCompilerFnSpy = jasmine.createSpy('createCompilerFn');
|
|
|
|
expect(await executor.execute(analyzeEntryPointsSpy, createCompilerFnSpy))
|
|
.toBe('CusterMaster#run()' as any);
|
|
|
|
expect(masterRunSpy).toHaveBeenCalledWith();
|
|
expect(workerRunSpy).not.toHaveBeenCalled();
|
|
|
|
expect(analyzeEntryPointsSpy).toHaveBeenCalledWith();
|
|
expect(createCompilerFnSpy).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('(on cluster worker)', () => {
|
|
beforeEach(() => runAsClusterMaster(false));
|
|
|
|
it('should not log debug info about the executor', () => {
|
|
const anyFn: () => any = () => undefined;
|
|
executor.execute(anyFn, anyFn);
|
|
|
|
expect(mockLogger.logs.debug).toEqual([]);
|
|
});
|
|
|
|
it('should delegate to `ClusterWorker#run()`', async() => {
|
|
workerRunSpy.and.returnValue('CusterWorker#run()');
|
|
const analyzeEntryPointsSpy = jasmine.createSpy('analyzeEntryPoints');
|
|
const createCompilerFnSpy = jasmine.createSpy('createCompilerFn');
|
|
|
|
expect(await executor.execute(analyzeEntryPointsSpy, createCompilerFnSpy))
|
|
.toBe('CusterWorker#run()' as any);
|
|
|
|
expect(masterRunSpy).not.toHaveBeenCalledWith();
|
|
expect(workerRunSpy).toHaveBeenCalled();
|
|
|
|
expect(analyzeEntryPointsSpy).not.toHaveBeenCalled();
|
|
expect(createCompilerFnSpy).toHaveBeenCalledWith(jasmine.any(Function));
|
|
});
|
|
});
|
|
});
|
|
});
|