-
Notifications
You must be signed in to change notification settings - Fork 569
Large file sizes #136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Use gzip compression. I'm routinely making 1.5-2 MB GopherJS output .js files as little as 200-250 KB by using it. It helps a lot to make the generated output more manageable. P.S. On an unrelated topic, what's the reason you're using a fork of the websocket bindings instead of the original? Just curious. |
I added the gzipped results to the table above. You're right, that helped considerably. 1.4mb to 69kb! I didn't realize gzip was so effective. IMO 69kb is still kind of a lot considering the source is 296 bytes, but that's much better than it was. |
For what it is worth, while gzip will help with the cost of downloading the file, the browser will still have to parse and process 1.4 MB of JavaScript. |
The source was less than 300 bytes, but it indirectly imported the dom package which is quite heavy. That adds a fixed size, so if the source were 600 bytes instead of 300 but with no other heavy dependencies, it'd be 300~ bytes more, not 1.4~ MB more. Maybe we should not import the entire dom package in websocket bindings, I think it's only used for EventTarget and some event types, hmm. /cc @nightexcessive
That is true. |
@shurcooL I was using a fork of websocket because I made some updates. Here's the pull request. There's a new file in the table above called @dominikh Do you think we should split up |
@liamcurry I have not looked into splitting up the packages yet, primarily because I am expecting circular dependencies. I'll see what can be done. |
Events have various methods that return |
GopherJS has some constant overhead, because it includes a chunk of code in the output that is required for most programs. This also applies to the normal Go compiler, e.g. your simple.go example compiles to a 617kb binary on my machine. But as I said, this is a constant overhead, a twice as big source file will not result in a twice as big output file. And yes, please use gzip compression because it is very effective on generated code. ;-) Is this still preventing your from using GopherJS in production? What would be your requirement for using it? |
Question: Does DCE also get applied to the standard go packages ? |
Yes, DCE is applied to the standard packages, else the output would be much bigger. The I could add a flag that would potentially break |
I generally advise against switches that create a two-class society of code; code that works when the switch is turned on, and code that doesn't. You'd have to be aware of the implementation of your dependencies and their dependencies to know if the switch could be safely used, and you'd have to check again after every update to one of these dependencies. Ultimatively, it'd make for a GopherJS-centric ecosystem, which is similar to what you got when people tried writing event-loop based code in Ruby: Libraries that used blocking calls and weren't suitable, and libraries designed specifically for event loops. I'd really hate to see a similar divide for GopherJS. As a counter proposal, would it be possible to detect if |
Forgive my ignorance, but could you give an example of The thing holding me back at the moment is the overhead required to interact with native Javascript APIs. @neelance you're certainly right about Go binaries having some overhead, so I'd expect the Javascript equivalent to have overhead too. However, I think the overhead for interacting with native APIs (like the DOM) should be minimal. I'm not totally aware of GopherJS's innards, so this could be a silly idea. In a perfect world, native Javascript libraries could be implemented as just interfaces. There would be a way to register libraries as "native" (perhaps as a build tag?), which would tell GopherJS to treat these libraries differently. The ASTs for these libraries would be rewritten (lowercased) at compile time instead of embedded. This would significantly reduce the amount of overhead. Here's a partial example for implementing the DOM: //gopherjs:native
package dom
// http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1950641247
type Node interface {}
// http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-745549614
type Element interface {
Node
}
// http://www.w3.org/TR/DOM-Level-3-Core/core.html#i-Document
type Document interface {
GetElementById(elementId string) Element
} So then any calls to |
The issue that Richard is referring to is the mere use of it. You can't determine at compile time what argument will be passed to |
http://play.golang.org/p/JabJabl5Ly would be an example. Run it with GopherJS and enter either Foo or Bar in the prompt. |
@liamcurry Also, if you looked at the implementation of my js/dom package, as well as the generated output, you'd see that it's not as trivial as just calling JavaScript functions. You need to do certain wrapping and conversions and, more importantly, all of the type information needs to be available at runtime, too, which is where most of the file size actually comes from. |
@liamcurry You probably know that you can do js.Global.Get("document").Call("getElementById") which will translate to simply $global.document.getElementById() But in that case you will stay in the JavaScript world, e.g. the returned object is a type ElementProvider interface {
GetElementById(elementId string) dom.Element
} and then assigns the |
@dominikh I just checked and it seems like only |
SGTM. I can't think of any other way to dynamically get to methods right now, and luckily there's no reflect.MakeInterface. |
@dominikh thanks for the example, that makes sense. @neelance just to echo @dominikh question, would it be possible to detect use of
Good point. What if we had an interface in type NativeObject interface {
IsNative()
} So your previous example would be: type ElementProvider interface {
js.NativeObject
GetElementById(elementId string) dom.Element
} Would that work? |
My problem ist not with marking the interfaces, but with code breaking quite unexpectedly if you don't mark it, e.g. because you don't know that you have to. It simply breaks the Go spec. The current solution contains the problem to |
In my opinion, I would prefer to have less magic, rather than more. There's already quite a bit to keep in mind, see https://github.com/gopherjs/gopherjs/wiki/JavaScript-Gotchas. The benefit should be very strong for it to outweigh the extra mental overhead. |
@liamcurry Output size has gotten smaller, especially for |
@neelance I added It's interesting that the gzipped file size got slightly larger even though the uncompressed file is so much smaller. I wonder why that is? |
FYI using Google's Closure Compiler with advanced optimizations I was able to get the minified file size down to 218kb (another 179% reduction), but the code no longer works. |
There are also other changes that might have increased the gzipped output size a bit. In my testing of the version before be37568 and after, the gzipped size reduced. If working code is not a requirement, then I can reduce the size to zero. ;-) |
Additional samples for gopherjs/gopherjs#136.
Here are additional samples I've gathered for my upcoming talk. I'm also including the file size of Go compiled binary output for reference.
|
@shurcooL Thanks for collecting those stats. I'm quite happy with the output size right now. Of course there are still some optimizations possible, but there are more pressing issues. |
Re: my previous comment: Those were (obviously) my own download times. I have pretty fast Internet here. Others are welcome to post their own times. Also: In earlier comments, some folks posted their results using the Google Closure Compiler, which does do DCE, as I understand it. Their results were, in my opinion, fairly modest: 5.7 mb -> 4.7 mb, about a 17% reduction. Possibly the actual GopherJS compiler, with a little more knowledge on its side, could do better, but it's something to keep in mind. If @shurcooL (et al) wants to implement it, I'm not gonna tell 'em not to, but I don't know if it's worth it in the long run. Something no one has mentioned is that all of this might well be moot, once the Wasm branch is released. Again, just something to keep in mind, when thinking about priorities. |
@theclapp thank you for your level-headed comments. My veering off-topic thought is if we can drive popularity (and whether GopherJS is popular is both related but also orthogonal to whether Go is popular), then the resources for everything we need to do will follow. I realize again that is more of a vaporware/speculative thought, but that is where my current focus is at the moment. I realize many/most of you are coming from the focus of deploying what is available now and prioritizing resources that are available now and that is of course rational. My investigation is from the perspective of whether (at least as a first short cut) target Go for transpiler from typeclasses and other improvements, or whether we need to bypass and target perhaps LLVM or roll our own. Iβm reasonably sure that we donβt want to limit our design efforts (the discussions with @keean, @sighoya et al) by trying for it to qualify as Go 2 or Go 3, because thereβs too much inertia already in design decisions that would hamper what we could design. Yet it is enticing to leverage all the work on goroutines and GC by Go, so I hope we can target it with a transpiler. Also the extant ecosystem libraries are enticing. Hence my interest in this thread. Again to reiterate my concurrence, I also donβt know what the prioritization of #186 should be. I agree it is more compelling when someone who is using the GopherJS reports an example issue that hinges on DCE. Was the reported 5 β 8 second delays in iterative developer workflow when combined with Webpack not impacted by DCE? |
I see DCE discussed, but not implemented or tested; perhaps I missed it. If DCE were implemented, I imagine it'd have some impact, though I'm not sure how much. The Google Closure Compiler discussed earlier in this issue removed about 17% of the JS in their test. @neelance mentioned in the issue you linked that GopherJS itself could do a better job than any external post-processor. So let's posit a 2x improvement, or 34%. That takes us to a 3-5 second delay (assuming said delay scales linearly with code size). Which, admittedly, is not nothing, and every little bit helps. So it's food for thought. As I understand it, GopherJS already does what DCE that it can, given that reflection exists. Issue #186 posits a Let's review: This issue is about large file sizes. We already have some DCE, and #186 presents a way to make it even more effective. jsgo.io gives us a way to separate the compiler output into package-level files, but that would make DCE problematic β or it would mean that only a given project could use those particular compiled package files, which defeats at least one of the primary goals of jsgo.io in the first place, to wit, reuse of compiled files between unrelated projects. As a more-or-less direct fallout of large file sizes, it can take Webpack a while to process and reload GopherJS code. It seems possible that jsgo.io's output could be leveraged so that webpack only reloads the package files that've changed. From my point of view, both hot-reloading and jsgo.io are PFM, so possibly someone could add yet more magic to get that to work. (Or, you know, possibly not.) If someone wants to try their hand at that, that should be a separate issue. Do we have any other ideas about reducing the file size, with the goals of 1) reducing network transit time, 2) reducing total load on the browser (compiling uncompressed code, etc), and 3) making it easier for packaging tools (e.g. webpack) to process compiled output more granularly? Here is one totally wild hair-brained thought which is quite possibly terrible if not literally insane, and which I've considered for exactly as long as it took me to write about it: Go has the idea of Another thought is to enhance the GopherJS compiled output so it's just, you know, smaller. Reading it, it has a lot of repetition. Possibly the (And again, I'm confident that GopherJS-wasm will come out eventually and make at least some of this discussion moot.) But anyway ... any other ideas? |
[Deleted as irrelevant to both the issue and GopherJS at large. ~~ LC] |
How so? Weβre still sending executable code over the wire. Also JavaScript has proper Also I had suggested that AFAIK in theory, DCE could even be beneficial for runtime memory paging, so Go should have it also. Although Iβve never confirmed this. That was just a thought off the top of my head. How are we going to debug the Wasm? Whatβs the interoperability FFI between language ecosystems? I was aware of @neelanceβs initiative for compiling to WASM. Seems to me that Wasm is still a few years away from being practical. And itβs an experimental design which ultimately may be discovered to be the incorrect design and could potentially die on the vine or require significant breaking changes. So I would be cautious about throwing all your eggs into that basket too fast. My current belief is that design-by-public-committee is a flawed methodology analogous to the flaws in democracy, so I am always skeptical of it. If the WASM compiler is going to implement the entire runtime (e.g. GC, exceptions, generators) then it may also be possible to achieve multithreading with shared memory on JavaScript engines. Also I still have some doubts about Wasm, because sacrifices in performance and features were made in order to make it secure. But I wonder whether security wouldnβt be better handled at the language layer. However the advantage of Wasm in theory is that it all experimentation with many different programming languages on the client, so seems to make it a winner. But if it turns out that there is one language that dominates the rest, then the security model will need to adapt so as to provide best performance to that language and no duplicate performance cost for security at both the low-level and the high-level (e.g. if the high-level language already protects against buffer overruns then donβt need the low-level also checking for memory out-of-bounds accesses). I think thereβs a reasonable chance that the latter outcome might end up being the reality. From my 3 years of discussions on programming language design, there really arenβt that many choices for doing a general purpose programming language correctly w.r.t. to certain features such as checking for overruns of arrays. But to be honest, I havenβt dug in deep enough yet on WebAssembly, and I may very well be incorrect on some of the details.
My thought w.r.t. βimpactβ is whether it was determined if the file sizes (e.g. due to parsing slowness?) was the like cause of the delays?
I wouldnβt characterize it as problematic, but rather emphasize DCE can be added to jsgo.io but at the cost of what you recapitulated. But I had already suggested that the goal of having multiple applications reuse the same library files on each userβs client is not very realistic (at this time in most scenarios) because GopherJS is not popular enough to make that happen. The user will likely only have one or zero applications that were compiled with GopherJS. And the DCE would be presumably speedup some developer workflows. And if ever GopherJS becomes ubiquitous, then the DCE is to be an optional compiler setting, so it can be turned off as the market changes. And always turned on during development. So the benefit of DCE will persist. Also the benefit of reusing libraries can be offset even if GopherJS is popular, by the fact that 3 copies of the same file which is 1/3 its size without DCE is break even, and that doesnβt even factor in all the files which do not get duplicated between applications. Would need basically that all applications use the same library files and that DCE is not very effective, which isnβt likely. IOW, it is dubious that the stated goal of jsgo.io will ever be advantageous if aggressive DCE is viable.
Yeah I would like to know if that would solve the developer workflow delays without needing to resort to aggressive DCE based on a heuristic. The splitting into files seems to be significant benefit of jsgo.io even with DCE added. Or both combined would in theory reduce the delays even further. But reading that thread #524, there seems to be an issue with initialization and separately loaded modules. I donβt know what will be required to overcome that.
Iβm not understanding what you are proposing? Do you mean hot reloading while the program is running? IMO, I think this is far outside the realm of what GopherJS should do. IMO, Go would need to have that feature.
Yeah see I had recently commented on that in a very old thread which I linked above. And thereβs other benefits (and tradeoffs) to switching to |
Yeah, fair enough. I guess I was just thinking that this particular issue with GopherJS would be moot. In that it'd then be an issue with Go-wasm.
@neelance has stated he'd like to see proof that it'd help, which I think is fair. Time is scarce. We're all free to try it out and submit a PR.
Could be.
Dunno. We'll see when it comes out, I think. :)
Yes, sort of. In the context of a web application. I don't know exactly how web applications achieve hot reloading, so I can't go into detail on what it would look like, exactly, in a Go application. I suspect that the web app would have to be GopherJS-aware, and the Go code would probably need helper code to facilitate the reloading. I suspect that figuring out how to do that would be really hard, and quite possibly beyond the scope of GopherJS.
Yeah. But then I thought per-package GopherJS files would never work, and then @dave went and implemented it. So I dunno. I just know, it won't be me. (Alas; I bet that'd be cool to know how to do. :) |
BTW (off topic I know) Iβm currently working on a project that will load new GopherJS packages dynamically at run-time... not sure if it would work in the general case but it seems to work fine for my specific use... this is only new packages mind - reloading a changed version of an existing package isnβt what Iβm trying to do. |
Just a reminder to consider @neelanceβs comments in #524 about dynamically loaded modules:
Maybe that topic needs to be discussed in #524? |
I wonder if the "shared library" mode ( Thinking about that leads me to a different, but related, idea. GopherJS is just Javascript (duh). I bet that right now you could safely load multiple GopherJS programs on the same web page, via multiple Cloak and Uncloak are very simple and look like this: // Cloak encapsulates a Go value within a JavaScript object. None of the fields
// or methods of the value will be exposed; it is therefore not intended that this
// *Object be used by Javascript code. Instead this function exists as a convenience
// mechanism for carrying state from Go to JavaScript and back again.
func Cloak(i interface{}) *js.Object {
return js.InternalObject(i)
}
// Uncloak is the inverse of Cloak.
func Uncloak(o *js.Object) interface{} {
return interface{}(unsafe.Pointer(o.Unsafe()))
} That's kind of an exciting idea. I wish I had time to experiment with it right now. :) Hey @dave, do you think that'd work? |
So when I was developing the jsgo playground, I played around with a higher performance "run" button. Instead of completely destroying the iframe with the code running, it just replaced the script tag containing the edited source code. It worked faster, but it broke most projects... They'd re-initialise and duplicate UI elements etc. I guess they also would have hanging goroutines and all sorts of bad stuff. |
Yeah I don't see any reason you couldn't have multiple |
Well, you know, pressing ... Maybe nothing. My thought was just that it might be a way to have GopherJS "modules" without having to change the language or even the compiler at all. And also hoping that maybe with jsgo.io splitting dependencies up, tools like webpack could run a bit faster, e.g. maybe they could mostly ignore packages from the Go standard library. (Which is a different issue than the one I @'ed you about, just to be clear.)
Yeah, the user Go code would probably have to be written with an eye towards re-initialization. I wouldn't assume you could just do it for everybody automatically. I assume webpack users have startup/shutdown/restart code in their regular Javascript, too, but I haven't checked. Full page reloads haven't annoyed me enough yet to try any of the more sophisticated solutions. |
On Aug/18/2016, @shurcooL said:
Here are some of those starting points:
|
If I could use a flag to make any use of My current cross compilation results in a JS file of over 4MB, which is supposed to be used by lots of downstream users as a dependency. This is a bit too much. |
For anyone still interested in this issue, I think #1183 is a promising solution. I don't think it's too difficult to implement either and I tried to spell out the key points. I (unfortunately) won't get to it at least until #1013 is done and we are caught up with the upstream Go versions. So I invite the community to give it a try. |
Here are a few simple examples and their generated file sizes:
(The huge jump in filesize in
websocket.go
is because of github.com/dominikh/go-js-dom)There are a lot of implementation details in the generated files that shouldn't be there. For example, I would assume that:
would compile to:
but instead it compiles to:
I'd love to start using gopherjs in production, but this is a show stopper. Any ideas on how we can fix this?
edit: added gzipped sizes
edit: added
websocket_fork.go
to results tableedit: added
websocket_2015-02-03.go
to results tableThe text was updated successfully, but these errors were encountered: