Skip to content

Scopes: memory requirement for translating stack traces #256

Description

@rakudrama

There have been discussions about the size of source maps (e.g. #41) but mostly concentrated on the file size. This issue is about the memory requirements for using sourcemaps.

Consider translating stack traces to the source language. The application server might do the translation, e.g. when the in-browser app logs a stack trace by sending back to the server. In practice, application servers see unwelcome spikes in memory usage when translating a burst of stack traces. In the server memory profiles I examined, 10-20% of the steady state memory was related to decoded source maps.

One reason for the high memory load  is that the entire source map must be decoded before any part of it can be used. However, translating a stack trace accesses the source map sparsely.

A stack trace typically has ten or so lines. These correspond to a similar number of functions in the original source. The ~10 stack trace lines reference a few generated files (3 is common in the crash logs I sampled). The generated files are large - I looked at a 9.8MB .js file that has a 15.8MB source map (12MB if proprietary extensions are stripped).

Digging into this sourcemap:

The sources section names ~4.6k files

The mappings section is 3.85MB which encodes 530k mapping records.
If the implementation naively represents the decoded source map using classes for each record type in the specification, you might expect one mapping record with its positions to take about 12 words (= 48 bytes when using, say, Java, with compressed pointers).
That would be about 25MB of heap - a 6.6x expansion over the encoded memory footprint.
Some clever implementations can pack this into arrays and reduce it to ~12MB, or a 3x expansion.

The names section has 180k names in 7.9MB. After parsing the JSON, the list and the individual strings it contains occupy about 10MB of heap.

During the stack frame translation we have resident:

  • The .js file, since it is scanned to find the beginning of the JavaScript function enclosing the stack frame positions (9.8MB)
  • The list of names (10MB)
  • The decoded mappings (12MB)

That is 31.8MB for only one file and there are typically two more in the stack trace.

Scopes

For the scopes proposal, the .js file is no longer needed, as the scopes field of the JSON encodes ranges 'covering' generated functions. Can the ranges, stripped of any bindings, be decoded into a data structure that occupies less memory than the source code?
This is not clear. How dart2js uses V3 mappings, ranges have a lot of overlap with the mappings. Ranges are more complicated, but potentially more sparse.

The original source scopes are also needed, since they are the definition, containing the name of the original source function, for the ranges we care about in order to translate the stack.

Stripping the source map while decoding

For stack trace translation:

  • Bindings are not needed.
  • Ranges that correspond to non-function scopes are not needed.
  • Scopes are of interest only for the name of the source function.

It would help memory if the unneeded parts could be stripped while decoding.

If the bindings are going to be unused, can they be stripped from the ranges without reference to the original scope's variables?
If yes, then decoding the scopes could be deferred until we know the few that are required.
Could there be some way to decouple the parsing of ranges so that it could precede parsing original scopes?
Could the scopes be encoded in a list of strings, like sourceContents

There is currently one 'index' space for scopes. If the definition was a pair (sourceIndex, scopeIndexWithinSource) then it would be unnecessary to decode the OriginalScopeTree for sources that are not named in the translated stack trace.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions