[dart2js] Add mmap-based read to dart2js for linux systems.

Based on Martin Kustermann and Slava Egorov's versions of memory mapped views.

This feature is disabled by default and even if the flag is enabled, it will fall back to the current strategy if the platform is not Linux or the new strategy fails.

Phase 1 stats from 50 runs:
---Before---
Max memory: 8786.766
Median memory: 8116.242
Average memory: 8327.404081632654
---After---=
Max memory: 7805.691
Median memory: 7137.203
Average memory: 7305.301122448978

Phase 2 stats from 50 runs:
---Before---
Max memory: 9273.359
Median memory: 8742.133
Average memory: 8868.929124999999
---After---
Max memory: 9409.672
Median memory: 7500.148
Average memory: 7650.154000000001

Phase 3b stats from 50 runs:
---Before---
Max memory: 8979.367
Median memory: 8705.215
Average memory: 8703.982755102037
---After---
Max memory: 7809.898
Median memory: 7455.609
Average memory: 7449.96102040816


Change-Id: I73d73cfb26218399367c72c886f247836b89925c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/269640
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
This commit is contained in:
Nate Biggs 2022-11-18 21:40:38 +00:00 committed by Commit Queue
parent b8d85b7ad3
commit bdb782ba8f
9 changed files with 272 additions and 41 deletions

View file

@ -114,6 +114,7 @@ class Flags {
static const String sources = '--sources';
static const String readData = '--read-data';
static const String writeData = '--write-data';
static const String memoryMappedFiles = '--memory-map-files';
static const String noClosedWorldInData = '--no-closed-world-in-data';
static const String writeClosedWorld = '--write-closed-world';
static const String readClosedWorld = '--read-closed-world';

View file

@ -17,6 +17,7 @@ import '../compiler_api.dart' as api;
import '../compiler_api_unmigrated.dart' as api_unmigrated;
import 'commandline_options.dart';
import 'common/ram_usage.dart';
import 'io/mapped_file.dart';
import 'options.dart' show CompilerOptions, FeatureOptions;
import 'source_file_provider.dart';
import 'util/command_line.dart';
@ -562,6 +563,7 @@ Future<api.CompilationResult> compile(List<String> argv,
setWriteModularAnalysis),
OptionHandler('${Flags.readData}|${Flags.readData}=.+', setReadData),
OptionHandler('${Flags.writeData}|${Flags.writeData}=.+', setWriteData),
OptionHandler(Flags.memoryMappedFiles, passThrough),
OptionHandler(Flags.noClosedWorldInData, ignoreOption),
OptionHandler('${Flags.readClosedWorld}|${Flags.readClosedWorld}=.+',
setReadClosedWorld),
@ -718,22 +720,7 @@ Future<api.CompilationResult> compile(List<String> argv,
print("Compiler invoked from: '$invoker'");
}
// TODO(johnniwinther): Measure time for reading files.
SourceFileProvider inputProvider;
if (bazelPaths != null) {
if (multiRoots != null) {
helpAndFail(
'The options --bazel-root and --multi-root cannot be supplied '
'together, please choose one or the other.');
}
inputProvider = BazelInputProvider(bazelPaths);
} else if (multiRoots != null) {
inputProvider = MultiRootInputProvider(multiRootScheme, multiRoots);
} else {
inputProvider = CompilerSourceFileProvider();
}
diagnosticHandler = FormattingDiagnosticHandler(inputProvider);
diagnosticHandler = FormattingDiagnosticHandler();
if (verbose != null) {
diagnosticHandler.verbose = verbose;
}
@ -932,6 +919,41 @@ Future<api.CompilationResult> compile(List<String> argv,
options.add('--source-map=${sourceMapOut}');
}
CompilerOptions compilerOptions = CompilerOptions.parse(options,
featureOptions: features,
librariesSpecificationUri: librariesSpecificationUri,
platformBinaries: platformBinaries,
onError: (String message) => fail(message),
onWarning: (String message) => print(message))
..entryUri = entryUri
..inputDillUri = inputDillUri
..packageConfig = packageConfig
..environment = environment
..kernelInitializedCompilerState = kernelInitializedCompilerState
..optimizationLevel = optimizationLevel;
// TODO(johnniwinther): Measure time for reading files.
SourceFileByteReader byteReader = compilerOptions.memoryMappedFiles
? const MemoryMapSourceFileByteReader()
: const MemoryCopySourceFileByteReader();
SourceFileProvider inputProvider;
if (bazelPaths != null) {
if (multiRoots != null) {
helpAndFail(
'The options --bazel-root and --multi-root cannot be supplied '
'together, please choose one or the other.');
}
inputProvider = BazelInputProvider(bazelPaths, byteReader);
} else if (multiRoots != null) {
inputProvider =
MultiRootInputProvider(multiRootScheme, multiRoots, byteReader);
} else {
inputProvider = CompilerSourceFileProvider(byteReader: byteReader);
}
diagnosticHandler.registerFileProvider(inputProvider);
RandomAccessFileOutputProvider outputProvider =
RandomAccessFileOutputProvider(out, sourceMapOut,
onInfo: diagnosticHandler.info, onFailure: fail);
@ -1087,18 +1109,6 @@ Future<api.CompilationResult> compile(List<String> argv,
return result;
}
CompilerOptions compilerOptions = CompilerOptions.parse(options,
featureOptions: features,
librariesSpecificationUri: librariesSpecificationUri,
platformBinaries: platformBinaries,
onError: (String message) => fail(message),
onWarning: (String message) => print(message))
..entryUri = entryUri
..inputDillUri = inputDillUri
..packageConfig = packageConfig
..environment = environment
..kernelInitializedCompilerState = kernelInitializedCompilerState
..optimizationLevel = optimizationLevel;
return compileFunc(
compilerOptions, inputProvider, diagnosticHandler, outputProvider)
.then(compilationDone);

View file

@ -0,0 +1,188 @@
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:ffi';
import 'dart:io';
import 'dart:typed_data';
import 'package:compiler/src/source_file_provider.dart';
import 'package:ffi/ffi.dart';
// int open(const char *path, int oflag, ...);
@FfiNative<Int32 Function(Pointer<Utf8>, Int32)>("open")
external int open(Pointer<Utf8> filename, int flags);
// int close(int fd);
@FfiNative<Int32 Function(Int32)>("close")
external int close(int fd);
// void* mmap(void* addr, size_t length,
// int prot, int flags,
// int fd, off_t offset)
@FfiNative<
Pointer<Uint8> Function(
Pointer<Uint8>, IntPtr, Int32, Int32, Int32, IntPtr)>("mmap")
external Pointer<Uint8> mmap(
Pointer<Uint8> address, int len, int prot, int flags, int fd, int offset);
// int munmap(void *addr, size_t length)
@FfiNative<IntPtr Function(Pointer<Uint8> address, IntPtr len)>("munmap")
external int munmap(Pointer<Uint8> address, int len);
final processSymbols = DynamicLibrary.process();
final munmapNative = processSymbols.lookup<Void>('munmap');
final closeNative = processSymbols.lookup<Void>('close');
final freeNative = processSymbols.lookup<Void>('free');
// int mprotect(void *addr, size_t len, int prot)
@FfiNative<Int32 Function(Pointer<Uint8>, IntPtr, Int32)>("mprotect")
external int mprotect(Pointer<Uint8> addr, int len, int prot);
// DART_EXPORT Dart_Handle
// Dart_NewExternalTypedDataWithFinalizer(Dart_TypedData_Type type,
// void* data,
// intptr_t length,
// void* peer,
// intptr_t external_allocation_size,
// Dart_HandleFinalizer callback)
typedef Dart_NewExternalTypedDataWithFinalizerNative = Handle Function(
Int32, Pointer<Void>, IntPtr, Pointer<Void>, IntPtr, Pointer<Void>);
typedef Dart_NewExternalTypedDataWithFinalizerDart = Object Function(
int, Pointer<Void>, int, Pointer<Void>, int, Pointer<Void>);
final Dart_NewExternalTypedDataWithFinalizer = processSymbols.lookupFunction<
Dart_NewExternalTypedDataWithFinalizerNative,
Dart_NewExternalTypedDataWithFinalizerDart>(
'Dart_NewExternalTypedDataWithFinalizer');
const int kPageSize = 4096;
const int kProtRead = 1;
const int kProtWrite = 2;
const int kProtExec = 4;
const int kMapPrivate = 2;
const int kMapAnon = 0x20;
const int kMapFailed = -1;
// We need to attach the finalizer which calls close() and
final finalizerAddress = () {
final finalizerStub = mmap(nullptr, kPageSize, kProtRead | kProtWrite,
kMapPrivate | kMapAnon, -1, 0);
finalizerStub.cast<Uint8>().asTypedList(kPageSize).setAll(0, <int>[
// Regenerate by running dart mmap.dart gen
// ASM_START
// #include <cstddef>
// #include <cstdint>
//
// struct PeerData {
// int (*close)(int);
// int (*munmap)(void*, size_t);
// int (*free)(void*);
// void* mapping;
// intptr_t size;
// intptr_t fd;
// };
//
// extern "C" void finalizer(void* callback_data, void* peer) {
// auto data = static_cast<PeerData*>(peer);
// data->munmap(data->mapping, data->size);
// data->close(data->fd);
// data->free(peer);
// }
//
0x55, 0x48, 0x89, 0xf5, 0x48, 0x8b, 0x76, 0x20, 0x48, 0x8b, 0x7d, 0x18, //
0xff, 0x55, 0x08, 0x8b, 0x7d, 0x28, 0xff, 0x55, 0x00, 0x48, 0x8b, 0x45, //
0x10, 0x48, 0x89, 0xef, 0x5d, 0xff, 0xe0, //
// ASM_END
]);
if (mprotect(finalizerStub, kPageSize, kProtRead | kProtExec) != 0) {
throw 'Failed to write executable code to the memory.';
}
return finalizerStub.cast<Void>();
}();
class PeerData extends Struct {
external Pointer<Void> close;
external Pointer<Void> munmap;
external Pointer<Void> free;
external Pointer<Uint8> mapping;
@IntPtr()
external int size;
@IntPtr()
external int fd;
}
Uint8List toExternalDataWithFinalizer(
Pointer<Uint8> memory, int size, int length, int fd) {
final peer = malloc.allocate<PeerData>(sizeOf<PeerData>());
peer.ref.close = closeNative;
peer.ref.munmap = munmapNative;
peer.ref.free = freeNative;
peer.ref.mapping = memory;
peer.ref.size = size;
peer.ref.fd = fd;
return Dart_NewExternalTypedDataWithFinalizer(
/*Dart_TypedData_kUint8*/ 2,
memory.cast(),
length,
peer.cast(),
size,
finalizerAddress,
) as Uint8List;
}
Uint8List viewOfFile(String filename, bool zeroTerminated) {
final cfilename = filename.toNativeUtf8();
final int fd = open(cfilename, 0);
malloc.free(cfilename);
if (fd == 0) throw 'failed to open';
try {
final length = File(filename).lengthSync();
int lengthRoundedUp = (length + kPageSize - 1) & ~(kPageSize - 1);
final result =
mmap(nullptr, lengthRoundedUp, kProtRead, kMapPrivate, fd, 0);
if (result.address == kMapFailed) throw 'failed to map';
try {
if (zeroTerminated) {
if (length == lengthRoundedUp) {
// In the rare case we need a zero-terminated list and the file size
// is exactly page-aligned we need to allocate a new list with extra
// room for the terminating 0.
return Uint8List(length + 1)
..setRange(
0,
length,
toExternalDataWithFinalizer(
result, lengthRoundedUp, length, fd));
}
return toExternalDataWithFinalizer(
result, lengthRoundedUp, length + 1, fd);
}
return toExternalDataWithFinalizer(result, lengthRoundedUp, length, fd);
} catch (_) {
munmap(result, lengthRoundedUp);
rethrow;
}
} catch (e) {
close(fd);
rethrow;
}
}
class MemoryMapSourceFileByteReader implements SourceFileByteReader {
const MemoryMapSourceFileByteReader();
@override
Uint8List getBytes(String filename, {bool zeroTerminated = true}) {
if (Platform.isLinux) {
try {
return viewOfFile(filename, zeroTerminated);
} catch (e) {
return readAll(filename, zeroTerminated: zeroTerminated);
}
} else {
return readAll(filename, zeroTerminated: zeroTerminated);
}
}
}

View file

@ -221,6 +221,9 @@ class CompilerOptions implements DiagnosticOptions {
bool get hasModularAnalysisInputs => modularAnalysisInputs != null;
/// Uses a memory mapped view of files for I/O.
bool memoryMappedFiles = false;
/// Location from which serialized inference data is read.
///
/// If this is set, the [entryUri] is expected to be a .dill file and the
@ -710,6 +713,7 @@ class CompilerOptions implements DiagnosticOptions {
_extractUriListOption(options, '${Flags.readModularAnalysis}')
..readDataUri = _extractUriOption(options, '${Flags.readData}=')
..writeDataUri = _extractUriOption(options, '${Flags.writeData}=')
..memoryMappedFiles = _hasOption(options, Flags.memoryMappedFiles)
..noClosedWorldInData = _hasOption(options, Flags.noClosedWorldInData)
..readClosedWorldUri =
_extractUriOption(options, '${Flags.readClosedWorld}=')

View file

@ -14,12 +14,19 @@ import '../compiler_api.dart' as api;
import 'colors.dart' as colors;
import 'io/source_file.dart';
abstract class SourceFileByteReader {
List<int> getBytes(String filename, {bool zeroTerminated = true});
}
abstract class SourceFileProvider implements api.CompilerInput {
bool isWindows = (Platform.operatingSystem == 'windows');
Uri cwd = Uri.base;
Map<Uri, SourceFile<List<int>>> utf8SourceFiles = {};
Map<Uri, api.Input<List<int>>> binarySourceFiles = {};
int dartCharactersRead = 0;
SourceFileByteReader byteReader;
SourceFileProvider(this.byteReader);
Future<api.Input<List<int>>> readBytesFromUri(
Uri resourceUri, api.InputKind inputKind) {
@ -85,7 +92,7 @@ abstract class SourceFileProvider implements api.CompilerInput {
assert(resourceUri.isScheme('file'));
List<int> source;
try {
source = readAll(resourceUri.toFilePath(),
source = byteReader.getBytes(resourceUri.toFilePath(),
zeroTerminated: inputKind == api.InputKind.UTF8);
} on FileSystemException catch (ex) {
String? message = ex.osError?.message;
@ -133,7 +140,15 @@ abstract class SourceFileProvider implements api.CompilerInput {
}
}
List<int> readAll(String filename, {bool zeroTerminated = true}) {
class MemoryCopySourceFileByteReader implements SourceFileByteReader {
const MemoryCopySourceFileByteReader();
@override
List<int> getBytes(String filename, {bool zeroTerminated = true}) {
return readAll(filename, zeroTerminated: zeroTerminated);
}
}
Uint8List readAll(String filename, {bool zeroTerminated = true}) {
RandomAccessFile file = File(filename).openSync();
int length = file.lengthSync();
int bufferLength = length;
@ -148,6 +163,11 @@ List<int> readAll(String filename, {bool zeroTerminated = true}) {
}
class CompilerSourceFileProvider extends SourceFileProvider {
CompilerSourceFileProvider(
{SourceFileByteReader byteReader =
const MemoryCopySourceFileByteReader()})
: super(byteReader);
@override
Future<api.Input<List<int>>> readFromUri(Uri uri,
{api.InputKind inputKind = api.InputKind.UTF8}) =>
@ -155,7 +175,7 @@ class CompilerSourceFileProvider extends SourceFileProvider {
}
class FormattingDiagnosticHandler implements api.CompilerDiagnostics {
final SourceFileProvider provider;
late final SourceFileProvider provider;
bool showWarnings = true;
bool showHints = true;
bool verbose = false;
@ -170,8 +190,11 @@ class FormattingDiagnosticHandler implements api.CompilerDiagnostics {
final int INFO =
api.Diagnostic.INFO.ordinal | api.Diagnostic.VERBOSE_INFO.ordinal;
FormattingDiagnosticHandler([SourceFileProvider? provider])
: this.provider = provider ?? CompilerSourceFileProvider();
FormattingDiagnosticHandler();
void registerFileProvider(SourceFileProvider provider) {
this.provider = provider;
}
void info(var message, [api.Diagnostic kind = api.Diagnostic.VERBOSE_INFO]) {
if (!verbose && kind == api.Diagnostic.VERBOSE_INFO) return;
@ -526,7 +549,7 @@ class _BinaryOutputSinkWrapper extends api.BinaryOutputSink {
class BazelInputProvider extends SourceFileProvider {
final List<Uri> dirs;
BazelInputProvider(List<String> searchPaths)
BazelInputProvider(List<String> searchPaths, super.byteReader)
: dirs = searchPaths.map(_resolve).toList();
static Uri _resolve(String path) => Uri.base.resolve(path);
@ -580,7 +603,7 @@ class MultiRootInputProvider extends SourceFileProvider {
final List<Uri> roots;
final String markerScheme;
MultiRootInputProvider(this.markerScheme, this.roots);
MultiRootInputProvider(this.markerScheme, this.roots, super.byteReader);
@override
Future<api.Input<List<int>>> readFromUri(Uri uri,

View file

@ -60,10 +60,13 @@ api.CompilerDiagnostics createCompilerDiagnostics(
api.CompilerDiagnostics handler = diagnostics;
if (showDiagnostics) {
if (diagnostics == null) {
handler = new FormattingDiagnosticHandler(provider)..verbose = verbose;
handler = new FormattingDiagnosticHandler()
..verbose = verbose
..registerFileProvider(provider);
} else {
var formattingHandler = new FormattingDiagnosticHandler(provider)
..verbose = verbose;
var formattingHandler = new FormattingDiagnosticHandler()
..verbose = verbose
..registerFileProvider(provider);
handler = new MultiDiagnostics([diagnostics, formattingHandler]);
}
} else if (diagnostics == null) {

View file

@ -14,12 +14,13 @@ import 'package:compiler/compiler_api.dart' as api;
import 'package:compiler/src/io/source_file.dart'
show Binary, StringSourceFile, Utf8BytesSourceFile;
import 'package:compiler/src/source_file_provider.dart' show SourceFileProvider;
import 'package:compiler/src/source_file_provider.dart'
show CompilerSourceFileProvider;
export 'package:compiler/src/source_file_provider.dart'
show SourceFileProvider, FormattingDiagnosticHandler;
class MemorySourceFileProvider extends SourceFileProvider {
class MemorySourceFileProvider extends CompilerSourceFileProvider {
Map<String, dynamic> memorySourceFiles;
/// MemorySourceFiles can contain maps of file names to string contents or

View file

@ -13,6 +13,7 @@ dependencies:
collection: any
crypto: any
dart2js_info: any
ffi: any
front_end: any
js_ast: any
js_runtime: any

View file

@ -69,7 +69,7 @@ application_snapshot("dart2js") {
# Specifying the platform explicitly elides running the CFE on the sdk
# sources.
"--platform-binaries=" + rebase_path("$root_out_dir/"),
rebase_path("$target_gen_dir/dart2js.dart"),
rebase_path("../../pkg/compiler/lib/src/util/memory_compiler.dart"),
]
}