_content/blog: gopls scalability blog post
Add a new blog post describing some of the scalability work we've been doing for gopls. Change-Id: Id9a9a328b2e29499d635d36e96f753f9a4787978 Reviewed-on: https://go-review.googlesource.com/c/website/+/520995 Reviewed-by: Russ Cox <rsc@golang.org> Reviewed-by: Alan Donovan <adonovan@google.com> Auto-Submit: Robert Findley <rfindley@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Родитель
70cf2cb772
Коммит
45145578b7
|
@ -0,0 +1,241 @@
|
|||
---
|
||||
title: Scaling gopls for the growing Go ecosystem
|
||||
date: 2023-09-08
|
||||
by:
|
||||
- Robert Findley
|
||||
- Alan Donovan
|
||||
summary: As the Go ecosystem gets bigger, gopls must get smaller
|
||||
---
|
||||
|
||||
<style type="text/css" scoped>
|
||||
.chart {
|
||||
width: 100%;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.chart {
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Earlier this summer, the Go team released version [v0.12](https://go.dev/s/gopls-v0.12)
|
||||
of [gopls](https://pkg.go.dev/golang.org/x/tools/gopls),
|
||||
the [language server](https://microsoft.github.io/language-server-protocol/) for Go, featuring a rewrite of its core that allows
|
||||
it to scale to larger codebases.
|
||||
This is the culmination of a year-long effort,
|
||||
and we're excited to share our progress, as well as talk a little bit about
|
||||
the new architecture and what it means for the future of gopls.
|
||||
|
||||
Since the v0.12 release, we've fine-tuned the new design,
|
||||
focusing on making interactive queries (such as auto-completion or finding
|
||||
references) as fast as they were with v0.11,
|
||||
despite holding so much less state in memory.
|
||||
If you haven't already, we hope you'll try it out:
|
||||
|
||||
```
|
||||
$ go install golang.org/x/tools/gopls@latest
|
||||
```
|
||||
|
||||
We'd love to hear about your experience with it via this [brief survey](https://google.qualtrics.com/jfe/form/SV_4SnGxpcSKN33WZw?s=blog).
|
||||
|
||||
## Reductions in memory use and startup time {#results}
|
||||
|
||||
Before we dive into the details, let's look at the results!
|
||||
The chart below shows the change to startup time and memory usage for 28
|
||||
of the most popular Go repositories on GitHub.
|
||||
These measurements were taken after opening a randomly selected Go file
|
||||
and waiting for gopls to fully load its state,
|
||||
and since we assume that the initial indexing is amortized over many editing sessions,
|
||||
we take these measurements the _second_ time we open the file.
|
||||
|
||||
<div class="image">
|
||||
<img src="gopls-scalability/performance-improvements.svg" alt="Relative savings
|
||||
in memory and startup time" class="chart"/>
|
||||
</div>
|
||||
|
||||
Across these repos, the savings average around 75%,
|
||||
but memory reductions are non-linear:
|
||||
as projects get larger, so does the relative decrease in memory usage.
|
||||
We'll explain this in more detail below.
|
||||
|
||||
## Gopls and the evolving Go ecosystem {#background}
|
||||
|
||||
Gopls provides language-agnostic editors with IDE-like features such as auto-completion,
|
||||
formatting, cross-references, and refactoring.
|
||||
Since its beginnings in 2018, gopls has consolidated many disparate command-line
|
||||
tools such as [guru](https://pkg.go.dev/golang.org/x/tools/cmd/guru),
|
||||
[gorename](https://pkg.go.dev/golang.org/x/tools/cmd/gorename),
|
||||
and [goimports](https://pkg.go.dev/golang.org/x/tools/cmd/goimports) and
|
||||
has become the [default backend for the VS Code Go extension](https://go.dev/blog/gopls-vscode-go)
|
||||
as well as many other editors and LSP plugins.
|
||||
Perhaps you’ve been using gopls through your editor without even knowing
|
||||
it---that’s the goal!
|
||||
|
||||
Five years ago, gopls offered improved performance merely by maintaining a stateful session.
|
||||
Whereas older command-line tools had to start from scratch each time they executed,
|
||||
gopls could save intermediate results to significantly reduce latency.
|
||||
But all that state came with a cost, and over time we increasingly [heard from users](https://github.com/golang/go/issues?q=is%3Aissue+is%3Aclosed+in%3Atitle+gopls+memory)
|
||||
that gopls's high memory usage was barely tolerable.
|
||||
|
||||
Meanwhile, the Go ecosystem was growing, with more code being written in
|
||||
larger repositories.
|
||||
[Go workspaces](https://go.dev/blog/get-familiar-with-workspaces) allowed
|
||||
developers to work on multiple modules simultaneously,
|
||||
and [containerized development](https://code.visualstudio.com/docs/devcontainers/containers)
|
||||
put language servers in increasingly resource-constrained environments.
|
||||
Codebases were getting larger, and developer environments were getting smaller.
|
||||
We needed to change the way gopls scaled in order to keep up.
|
||||
|
||||
## Revisiting gopls's compiler origins {#origins}
|
||||
|
||||
In many ways, gopls resembles a compiler:
|
||||
it has to read, parse, type-check, and analyze Go source files,
|
||||
for which it uses many of the compiler [building blocks](https://github.com/golang/example/tree/master/gotypes#introduction)
|
||||
provided by the [Go standard library](https://pkg.go.dev/go) and [golang.org/x/tools](https://pkg.go.dev/golang.org/x/tools) module.
|
||||
These building blocks use the technique of "symbolic programming":
|
||||
in a running compiler there is a single object or "symbol" that stands for
|
||||
each function such as `fmt.Println`.
|
||||
Any reference to a function is represented as a pointer to its symbol.
|
||||
To test whether two references are talking about the same symbol,
|
||||
you don’t need to think about names.
|
||||
You just compare pointers. A pointer is much smaller than a string,
|
||||
and pointer comparison is very cheap, so symbols are an efficient way to
|
||||
represent a structure as complex as a program.
|
||||
|
||||
In order to respond quickly to requests, gopls v0.11 held all these symbols in memory,
|
||||
as though gopls was **compiling your entire program at once**.
|
||||
The result was a memory footprint that was proportional to and much larger
|
||||
than the source code being edited (for example,
|
||||
typed syntax trees are typically 30x larger than the source text!).
|
||||
<!-- deps(gopls) = 18.5MB source, 542MB RAM -->
|
||||
|
||||
## Separate compilation {#separate-compilation}
|
||||
|
||||
The designers of the first compilers in the 1950s quickly discovered the
|
||||
limits of monolithic compilation.
|
||||
Their solution was to break the program into units and compile each unit separately.
|
||||
Separate compilation makes it possible to build a program that does not fit in memory,
|
||||
by doing it in small pieces.
|
||||
In Go, the units are packages. Compilation of different packages cannot
|
||||
be completely separated:
|
||||
when compiling a package P, the compiler still needs information about what's
|
||||
provided by the packages that P imports.
|
||||
To arrange this, the Go build system compiles all of P's imported packages before P itself,
|
||||
and the Go compiler writes a compact summary of each package's exported API.
|
||||
The summaries of P's imported packages are provided as inputs to the compilation of P itself.
|
||||
|
||||
Gopls v0.12 brings separate compilation to gopls,
|
||||
reusing the same package summary format used by the compiler.
|
||||
The idea is simple, but there’s subtlety in the details.
|
||||
We rewrote each algorithm that previously inspected the data structure representing the entire program,
|
||||
so that it now works on one package at a time and saves per-package results to files,
|
||||
just like a compiler emitting object code.
|
||||
For example, finding all references to a function used to be as easy as
|
||||
searching the program data structure for all occurrences of a particular pointer value.
|
||||
Now, when gopls processes each package, it must construct and save an index
|
||||
that associates each identifier location in the source code with the name
|
||||
of the symbol to which it refers.
|
||||
At query time, gopls loads and searches these indexes.
|
||||
Other global queries, such as "find implementations",
|
||||
use similar techniques.
|
||||
|
||||
Like the `go build` command, gopls now uses a [file-based cache](https://cs.opensource.google/go/x/tools/+/master:gopls/internal/lsp/filecache/filecache.go;l=5;drc=6f567c8090cb88f13a71b19595bf88c6b27dbeed)
|
||||
store to record summaries of information computed from each package,
|
||||
including the type of each declaration, the index of cross-references,
|
||||
and the method set of each type.
|
||||
Since the cache is persisted across processes,
|
||||
you’ll notice that the second time you start gopls in your workspace,
|
||||
it becomes ready to serve much more quickly,
|
||||
and if you run two gopls instances, they work together synergistically.
|
||||
|
||||
<div class="image">
|
||||
<img src="gopls-scalability/separate-compilation.png" alt="separate compilation" class="chart"/>
|
||||
</div>
|
||||
|
||||
The result of this change is that gopls's memory use is proportional to
|
||||
the number of open packages and their direct imports.
|
||||
This is why we observe sublinear scaling in the chart above:
|
||||
as repositories get larger, the fraction of the project observed by any
|
||||
one open package gets smaller.
|
||||
|
||||
## Fine-grained invalidation {#invalidation}
|
||||
|
||||
When you make a change in one package, it's only necessary to recompile
|
||||
the packages that import that one,
|
||||
directly or indirectly.
|
||||
This idea is the basis of all incremental build systems since Make in the 1970s,
|
||||
and gopls has been using it since its inception.
|
||||
In effect, every keystroke in your LSP-enabled editor starts an incremental build!
|
||||
However, in a large project, indirect dependencies add up,
|
||||
making these incremental rebuilds too slow.
|
||||
It turns out that a lot of this work isn't strictly necessary,
|
||||
because most changes, such as adding a statement within an existing function,
|
||||
don't affect the import summaries.
|
||||
|
||||
If you make a small change in one file, we have to recompile its package,
|
||||
but if the change does not affect the import summary, we don't have to compile any other packages.
|
||||
The effect of the change is "pruned". A change that does affect the import
|
||||
summary requires recompiling the packages that directly import that package,
|
||||
but most such changes won't affect the import summaries of _those_ packages,
|
||||
in which case the effect is still pruned and avoids recompiling indirect importers.
|
||||
Thanks to this pruning, it is rare for a change in a low-level package to
|
||||
require recompiling _all_ the packages that indirectly depend on that package.
|
||||
Pruned incremental rebuilds make the amount of work proportional to the
|
||||
scope of each change.
|
||||
This is not a new idea: it was introduced by [Vesta](https://www.hpl.hp.com/techreports/Compaq-DEC/SRC-RR-177.pdf)
|
||||
and also used in [`go build`](https://go.dev/doc/go1.10#build).
|
||||
|
||||
The v0.12 release introduces a similar pruning technique to gopls,
|
||||
going one step further to implement a faster pruning heuristic based on syntactic analysis.
|
||||
By keeping a simplified graph of symbol references in memory,
|
||||
gopls can quickly determine whether a change in package `c` can possibly
|
||||
affect package `a` through a chain of references.
|
||||
|
||||
<div class="image">
|
||||
<img src="gopls-scalability/precise-pruning.png" alt="fine-grained invalidation" class="chart"/>
|
||||
</div>
|
||||
|
||||
In the example above, there's no chain of references from `a` to `c`,
|
||||
so a is not exposed to changes in c even though it indirectly depends on it.
|
||||
|
||||
## New possibilities {#new-possibilities}
|
||||
|
||||
While we're happy with the performance improvements we've achieved,
|
||||
we're also excited about several gopls features that are feasible now that
|
||||
gopls is no longer constrained by memory.
|
||||
|
||||
The first is robust static analysis. Previously,
|
||||
our static analysis driver had to operate on gopls's in-memory representation of packages,
|
||||
so it couldn't analyze dependencies:
|
||||
doing so would pull in too much additional code.
|
||||
With that requirement removed, we were able to include a new analysis driver
|
||||
in gopls v0.12 that analyzes all dependencies,
|
||||
resulting in greater precision.
|
||||
For example, gopls now reports diagnostics for `Printf` formatting mistakes
|
||||
even in your user-defined wrappers around `fmt.Printf`.
|
||||
Notably, `go vet` has provided this level of precision for years,
|
||||
but gopls was unable to do this in real time after each edit. Now it can.
|
||||
|
||||
The second is [simpler workspace configuration](https://go.dev/issue/57979)
|
||||
and [improved handling for build tags](https://go.dev/issue/29202).
|
||||
These two features both amount to gopls "doing the right thing" when you
|
||||
open any Go file on your machine,
|
||||
but both were infeasible without the optimization work because (for example)
|
||||
each build configuration multiplies the memory footprint!
|
||||
|
||||
## Try it out! {#try}
|
||||
|
||||
In addition to scalability and performance improvements,
|
||||
we've also fixed [numerous](https://github.com/golang/go/milestone/282?closed=1)
|
||||
[reported bugs](https://github.com/golang/go/milestone/318?closed=1) and
|
||||
many unreported ones that we discovered while improving test coverage during the transition.
|
||||
|
||||
To install the latest gopls:
|
||||
|
||||
```
|
||||
$ go install golang.org/x/tools/gopls@latest
|
||||
```
|
||||
|
||||
Please try it out and fill out the [survey](https://google.qualtrics.com/jfe/form/SV_4SnGxpcSKN33WZw?s=blog) ---
|
||||
and if you should encounter a bug,
|
||||
[report it](https://github.com/golang/go/issues/new/choose) and we will fix it.
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
После Ширина: | Высота: | Размер: 79 KiB |
|
@ -0,0 +1,467 @@
|
|||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "https://excalidraw.com",
|
||||
"elements": [
|
||||
{
|
||||
"type": "text",
|
||||
"version": 131,
|
||||
"versionNonce": 1812852806,
|
||||
"isDeleted": false,
|
||||
"id": "KLTqqKKdKPvv_uyDvIH56",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 708.5,
|
||||
"y": 226,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"width": 117.1875,
|
||||
"height": 192,
|
||||
"seed": 1764650250,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"roundness": null,
|
||||
"boundElements": [],
|
||||
"updated": 1694183553513,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 20,
|
||||
"fontFamily": 3,
|
||||
"text": "package a\n\nimport \"b\"\n\nfunc A() {\n b.B1()\n}\n",
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "package a\n\nimport \"b\"\n\nfunc A() {\n b.B1()\n}\n",
|
||||
"lineHeight": 1.2,
|
||||
"baseline": 187
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 382,
|
||||
"versionNonce": 198113875,
|
||||
"isDeleted": false,
|
||||
"id": "PScOU98GRKKPi_JFEXVOd",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 927.5,
|
||||
"y": 225.5,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"width": 152.34375,
|
||||
"height": 168,
|
||||
"seed": 1266662166,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"roundness": null,
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "dlPrM6ifKO8CYIQGgVSjT",
|
||||
"type": "arrow"
|
||||
}
|
||||
],
|
||||
"updated": 1692713327603,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 20,
|
||||
"fontFamily": 3,
|
||||
"text": "package b\n\nimport \"c\"\n\nfunc B1() {}\n\nfunc B2(C) {}",
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "package b\n\nimport \"c\"\n\nfunc B1() {}\n\nfunc B2(C) {}",
|
||||
"lineHeight": 1.2,
|
||||
"baseline": 163
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 245,
|
||||
"versionNonce": 410175581,
|
||||
"isDeleted": false,
|
||||
"id": "qdvLpJXYgRj1saqwYi-Ff",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 1152.2400512695312,
|
||||
"y": 227.5,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"width": 175.78125,
|
||||
"height": 72,
|
||||
"seed": 343723210,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"roundness": null,
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "dlPrM6ifKO8CYIQGgVSjT",
|
||||
"type": "arrow"
|
||||
}
|
||||
],
|
||||
"updated": 1692713327603,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 20,
|
||||
"fontFamily": 3,
|
||||
"text": "package c\n\ntype C struct{}",
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "package c\n\ntype C struct{}",
|
||||
"lineHeight": 1.2,
|
||||
"baseline": 67
|
||||
},
|
||||
{
|
||||
"type": "arrow",
|
||||
"version": 565,
|
||||
"versionNonce": 61253530,
|
||||
"isDeleted": false,
|
||||
"id": "Jo5-4OWytSqsWKyCCv0zs",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 774,
|
||||
"y": 373,
|
||||
"strokeColor": "#1971c2",
|
||||
"backgroundColor": "transparent",
|
||||
"width": 208.5,
|
||||
"height": 68.5,
|
||||
"seed": 662000458,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"roundness": {
|
||||
"type": 2
|
||||
},
|
||||
"boundElements": [],
|
||||
"updated": 1694183684530,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"startBinding": null,
|
||||
"endBinding": null,
|
||||
"lastCommittedPoint": null,
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
35.5,
|
||||
9
|
||||
],
|
||||
[
|
||||
168.5,
|
||||
-59.5
|
||||
],
|
||||
[
|
||||
208.5,
|
||||
-49
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "arrow",
|
||||
"version": 1142,
|
||||
"versionNonce": 1980630022,
|
||||
"isDeleted": false,
|
||||
"id": "dlPrM6ifKO8CYIQGgVSjT",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 1034.8165343534565,
|
||||
"y": 397.5,
|
||||
"strokeColor": "#1971c2",
|
||||
"backgroundColor": "transparent",
|
||||
"width": 176.6140607076302,
|
||||
"height": 106.6081098471862,
|
||||
"seed": 371482890,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"roundness": {
|
||||
"type": 2
|
||||
},
|
||||
"boundElements": [],
|
||||
"updated": 1694183691116,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"startBinding": {
|
||||
"elementId": "PScOU98GRKKPi_JFEXVOd",
|
||||
"focus": 0.5314594691269521,
|
||||
"gap": 4
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "qdvLpJXYgRj1saqwYi-Ff",
|
||||
"focus": -0.035506896849099974,
|
||||
"gap": 5
|
||||
},
|
||||
"lastCommittedPoint": null,
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
22.480751639833215,
|
||||
13.6081098471862
|
||||
],
|
||||
[
|
||||
91.90307013873098,
|
||||
-13
|
||||
],
|
||||
[
|
||||
154.18346564654348,
|
||||
-65
|
||||
],
|
||||
[
|
||||
176.6140607076302,
|
||||
-93
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 591,
|
||||
"versionNonce": 995219209,
|
||||
"isDeleted": false,
|
||||
"id": "0e_RgDukEA7a4s-hPUdUD",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "dashed",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 856,
|
||||
"y": 176,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"width": 272.5196838378906,
|
||||
"height": 25,
|
||||
"seed": 442549770,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"roundness": null,
|
||||
"boundElements": [],
|
||||
"updated": 1693423437323,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 20,
|
||||
"fontFamily": 1,
|
||||
"text": "Package a doesn't reach c.",
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "Package a doesn't reach c.",
|
||||
"lineHeight": 1.25,
|
||||
"baseline": 18
|
||||
},
|
||||
{
|
||||
"type": "arrow",
|
||||
"version": 260,
|
||||
"versionNonce": 116791514,
|
||||
"isDeleted": false,
|
||||
"id": "zUxFciaTErQ7nvkqFE4xZ",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "dotted",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 994.6198891769479,
|
||||
"y": 367.4258069066833,
|
||||
"strokeColor": "#1971c2",
|
||||
"backgroundColor": "transparent",
|
||||
"width": 0.17108894564739785,
|
||||
"height": 21.464261588495276,
|
||||
"seed": 390419155,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"roundness": {
|
||||
"type": 2
|
||||
},
|
||||
"boundElements": [],
|
||||
"updated": 1694183723060,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"startBinding": null,
|
||||
"endBinding": null,
|
||||
"lastCommittedPoint": null,
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": null,
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
0.17108894564739785,
|
||||
-21.464261588495276
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 245,
|
||||
"versionNonce": 838129114,
|
||||
"isDeleted": false,
|
||||
"id": "lwbMT4vlzQR_SD-RiLk_N",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 2,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 2,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 788.7196044921875,
|
||||
"y": 416,
|
||||
"strokeColor": "#1971c2",
|
||||
"backgroundColor": "transparent",
|
||||
"width": 148.08029174804688,
|
||||
"height": 20,
|
||||
"seed": 89416659,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"roundness": null,
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "J_bngpKx7t6uKubZgsPUc",
|
||||
"type": "arrow"
|
||||
}
|
||||
],
|
||||
"updated": 1694183688957,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 16,
|
||||
"fontFamily": 1,
|
||||
"text": "Note: no edge here",
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "Note: no edge here",
|
||||
"lineHeight": 1.25,
|
||||
"baseline": 14
|
||||
},
|
||||
{
|
||||
"type": "arrow",
|
||||
"version": 295,
|
||||
"versionNonce": 1976297030,
|
||||
"isDeleted": false,
|
||||
"id": "J_bngpKx7t6uKubZgsPUc",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 869.1993031227533,
|
||||
"y": 408.50000000000006,
|
||||
"strokeColor": "#1971c2",
|
||||
"backgroundColor": "transparent",
|
||||
"width": 109.02030136943415,
|
||||
"height": 51.00000000000006,
|
||||
"seed": 1824236701,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"roundness": {
|
||||
"type": 2
|
||||
},
|
||||
"boundElements": [],
|
||||
"updated": 1694183686932,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"startBinding": {
|
||||
"elementId": "lwbMT4vlzQR_SD-RiLk_N",
|
||||
"focus": -0.1187385614826156,
|
||||
"gap": 7.499999999999943
|
||||
},
|
||||
"endBinding": null,
|
||||
"lastCommittedPoint": null,
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "arrow",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
29.304081675061752,
|
||||
-31.38513730898279
|
||||
],
|
||||
[
|
||||
62.87164228631707,
|
||||
-47.22297253820352
|
||||
],
|
||||
[
|
||||
109.02030136943415,
|
||||
-51.00000000000006
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"version": 250,
|
||||
"versionNonce": 471171581,
|
||||
"isDeleted": false,
|
||||
"id": "N9ei1d7la2SGB-r_dfxam",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 851.7196044921875,
|
||||
"y": 203.5,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"width": 288,
|
||||
"height": 0.5,
|
||||
"seed": 953081427,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"roundness": {
|
||||
"type": 2
|
||||
},
|
||||
"boundElements": [],
|
||||
"updated": 1692713333701,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"startBinding": null,
|
||||
"endBinding": null,
|
||||
"lastCommittedPoint": null,
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": null,
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
288,
|
||||
-0.5
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
"gridSize": null,
|
||||
"viewBackgroundColor": "#ffffff"
|
||||
},
|
||||
"files": {}
|
||||
}
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 82 KiB |
|
@ -0,0 +1,253 @@
|
|||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "https://excalidraw.com",
|
||||
"elements": [
|
||||
{
|
||||
"type": "text",
|
||||
"version": 514,
|
||||
"versionNonce": 1931030537,
|
||||
"isDeleted": false,
|
||||
"id": "8vCu0eNhvFjFZlYF6qMKK",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "dashed",
|
||||
"roughness": 2,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 634.2309083984003,
|
||||
"y": 256.521234050657,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"width": 304.6875,
|
||||
"height": 216,
|
||||
"seed": 588319446,
|
||||
"groupIds": [
|
||||
"catUVsDqTBTKpq-jyjQOz"
|
||||
],
|
||||
"frameId": null,
|
||||
"roundness": null,
|
||||
"boundElements": [],
|
||||
"updated": 1693404698186,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 20,
|
||||
"fontFamily": 3,
|
||||
"text": "package foo\n\nimport \"fmt\"\n\ntype Bar struct{}\n\nfunc (Bar) Baz(s string) {\n fmt.Println(s)\n}",
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "package foo\n\nimport \"fmt\"\n\ntype Bar struct{}\n\nfunc (Bar) Baz(s string) {\n fmt.Println(s)\n}",
|
||||
"lineHeight": 1.2,
|
||||
"baseline": 211
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 466,
|
||||
"versionNonce": 584127881,
|
||||
"isDeleted": false,
|
||||
"id": "OIDpNpialGaYan-IDYsYp",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "dashed",
|
||||
"roughness": 2,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 607,
|
||||
"y": 211,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"width": 348.79070653208913,
|
||||
"height": 312.7659493429823,
|
||||
"seed": 160776214,
|
||||
"groupIds": [
|
||||
"catUVsDqTBTKpq-jyjQOz"
|
||||
],
|
||||
"frameId": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "YfmaTvV7nBEdCO5v-yiWr",
|
||||
"type": "arrow"
|
||||
}
|
||||
],
|
||||
"updated": 1693403971747,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 479,
|
||||
"versionNonce": 2124129159,
|
||||
"isDeleted": false,
|
||||
"id": "3OdPvUKZKpvbzhl_B5fid",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "dashed",
|
||||
"roughness": 2,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 1030.5,
|
||||
"y": 186.5,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"width": 342,
|
||||
"height": 341.4999999999999,
|
||||
"seed": 1665264074,
|
||||
"groupIds": [
|
||||
"3Jg1OxEHWdnyfhavtSLUi"
|
||||
],
|
||||
"frameId": null,
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "YfmaTvV7nBEdCO5v-yiWr",
|
||||
"type": "arrow"
|
||||
}
|
||||
],
|
||||
"updated": 1693403971747,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 518,
|
||||
"versionNonce": 1221397097,
|
||||
"isDeleted": false,
|
||||
"id": "eeZNpC4B2KUVuWE7MReMW",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "dashed",
|
||||
"roughness": 2,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 1051,
|
||||
"y": 207,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"width": 288.3397216796875,
|
||||
"height": 275,
|
||||
"seed": 1807097994,
|
||||
"groupIds": [
|
||||
"3Jg1OxEHWdnyfhavtSLUi"
|
||||
],
|
||||
"frameId": null,
|
||||
"roundness": null,
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "YfmaTvV7nBEdCO5v-yiWr",
|
||||
"type": "arrow"
|
||||
}
|
||||
],
|
||||
"updated": 1693423357961,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 20,
|
||||
"fontFamily": 1,
|
||||
"text": "Summary\n package foo\n import \"fmt\"\n type Bar struct{}\n func (Bar) Baz(s string)\n\nReferences:\n fmt.Println: line 8, column 6\n\nMethods:\n Baz(string): Bar",
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "Summary\n package foo\n import \"fmt\"\n type Bar struct{}\n func (Bar) Baz(s string)\n\nReferences:\n fmt.Println: line 8, column 6\n\nMethods:\n Baz(string): Bar",
|
||||
"lineHeight": 1.25,
|
||||
"baseline": 268
|
||||
},
|
||||
{
|
||||
"type": "arrow",
|
||||
"version": 1132,
|
||||
"versionNonce": 1317771943,
|
||||
"isDeleted": false,
|
||||
"id": "YfmaTvV7nBEdCO5v-yiWr",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 2,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 971.7907065320892,
|
||||
"y": 367.1869308196924,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"width": 44.20929346791081,
|
||||
"height": 1.1750564695992125,
|
||||
"seed": 1753119510,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"roundness": {
|
||||
"type": 2
|
||||
},
|
||||
"boundElements": [],
|
||||
"updated": 1693403971747,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"startBinding": {
|
||||
"elementId": "OIDpNpialGaYan-IDYsYp",
|
||||
"gap": 16,
|
||||
"focus": -0.03876047338293693
|
||||
},
|
||||
"endBinding": {
|
||||
"elementId": "3OdPvUKZKpvbzhl_B5fid",
|
||||
"gap": 14.5,
|
||||
"focus": -0.0915169218121322
|
||||
},
|
||||
"lastCommittedPoint": null,
|
||||
"startArrowhead": null,
|
||||
"endArrowhead": "triangle",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
44.20929346791081,
|
||||
1.1750564695992125
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 215,
|
||||
"versionNonce": 1703806793,
|
||||
"isDeleted": false,
|
||||
"id": "Ca7svLx55Vj_MCQV-LfdJ",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "dashed",
|
||||
"roughness": 2,
|
||||
"opacity": 100,
|
||||
"angle": 0.17623928971477731,
|
||||
"x": 1254.5398740640808,
|
||||
"y": 209.57474426703484,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"width": 99.139892578125,
|
||||
"height": 25,
|
||||
"seed": 1290026070,
|
||||
"groupIds": [],
|
||||
"frameId": null,
|
||||
"roundness": null,
|
||||
"boundElements": [],
|
||||
"updated": 1693403971747,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 20,
|
||||
"fontFamily": 1,
|
||||
"text": "File Cache",
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "File Cache",
|
||||
"lineHeight": 1.25,
|
||||
"baseline": 18
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
"gridSize": null,
|
||||
"viewBackgroundColor": "#ffffff"
|
||||
},
|
||||
"files": {}
|
||||
}
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 147 KiB |
Загрузка…
Ссылка в новой задаче