[HN Gopher] WebAssembly for the Java Geek
___________________________________________________________________
WebAssembly for the Java Geek
Author : ingve
Score : 145 points
Date : 2022-12-23 07:22 UTC (15 hours ago)
(HTM) web link (www.javaadvent.com)
(TXT) w3m dump (www.javaadvent.com)
| torginus wrote:
| Genuine question about GC'd languages on Wasm - how does the GC
| run?
|
| When running a GC language in its own runtime, usually there is
| at least 1 GC thread, that runs concurrently with the app code
| and pauses/preempts the main thread.
|
| Afaik, in WASM, your application only has CPU time when you are
| inside a WASM function and threading is incredibly hacky and may
| be disabled due to security reasons related to side-channel
| attacks (read up on `SharedArrayBuffer` and cross-origin site
| isolation).
| pjmlp wrote:
| It runs alongside the application, basically you're back to
| using RC as GC algorithm or only when memory needs demand a
| full GC.
| titzer wrote:
| Virgil compiles its tracing semi-space collector into the
| application. It's the same code as the native GC. The major
| difference is that the call stack is not visible, so the
| compiler must spill all live references on the stack into a
| shadow stack in memory. That last part was the trickiest and
| most inefficient part of porting.
| pjmlp wrote:
| Yeah, but it doesn't run in parallel, does it?
| bullen wrote:
| Reference Counting, ok!
| saurik wrote:
| Garbage collection isn't generally something that happens
| periodically as it isn't needed until you get a bunch of new
| allocations and classically did not happen on some background
| thread as that's both extremely difficult to pull off and
| requires threading support in the first place. The simpler way
| to think about garbage collection is you establish a heap, set
| a size, and when you run out of space in that heap you run a
| garbage collection pass _during the memory allocation that
| failed_. If you fail to free enough memory, you either kill the
| program or request a larger heap.
| kaba0 wrote:
| This is the way it is described in the GC Handbook as well.
| New(): ref <- allocate() if ref = null
| collect() ref <- allocate() if ref = null
| error "Out of memory" return ref
| gernb wrote:
| WASM is a virtual processor spec. There's no GC support in X64
| assembly nor ARM assembly and there's no GC support in Web
| Assembly because they are all assembly languages. You get a
| hunk of memory, you manage it however you want. If you want GC
| you you implement GC the same way the JVM implements GC on
| various processors.
| pulse7 wrote:
| Currently there is no official support for GC in WASM. So
| currently any compilation of GC'd language into WASM must
| provide it's own GC which will be part of the WASM binary.
| nilslice wrote:
| For anyone who want to experiment with WebAssembly in Java, we
| just shipped a Java Host SDK[0] for Extism[1] which makes it
| about as easy as possible to call wasm functions from your Java
| programs.
|
| 0: https://extism.org/docs/integrate-into-your-codebase/java-
| ho...
|
| 1: https://github.com/extism/extism
| stevefan1999 wrote:
| also take a look in .NET counterpart:
| https://dev.to/azure/exploring-net-webassembly-with-wasi-and...
| singularity2001 wrote:
| This seems a bit outdated. There was a recent blog post by
| intellij Raider that showed that you can just compile to wasm
| by adding the wasi nuget package, no extra steps required.
| stevefan1999 wrote:
| It's JetBrains Rider not Raider...Plus the blog post I posted
| also talked about the out of the box experience with just
| installing one Wasi.Sharp package to get WASI WebAssembly
| support out of the box for most scenarios...except it is
| still too big to put into Cloudflare workers because it
| contains the entire Mono runtime...
| evacchi wrote:
| huh, author here, I didn't expect this to be on HN please don't
| roast me too much :P
| ridruejo wrote:
| It is actually a pretty good article and I learnt quite a bit.
| Thanks for taking the time to write it. The only two things I
| would add are that, unlike Java, the industry has rallied
| unanimously behind it (in the 90s it was Java vs .NET and
| Applets vs ActiveX, etc.) and the big difference that Wasm
| comes pre-integrated in the browser, has a focus on security
| and is a proper W3C standard. Everything else that came before
| it felt like some after-the-fact bolt-on (Flash, Applets, etc.)
| and had big security issues and often got out of sync with
| browser releases
| rowls66 wrote:
| There was no .NET in the 90's. It was Java vs. ActiveX vs.
| Flash. I agree that Java support in browsers was never as
| good as it could have been. Sun never had direct control over
| a mainstream browser, and the companies that did each had
| other technologies that they preferred (Microsoft - ActiveX,
| Netscape - Javascript).
| [deleted]
| kaba0 wrote:
| I mean, Applets are just _really_ old, security itself wasn't
| considered all that "important" at the time. There is no real
| difference on a purely runtime level between wasm and the JVM
| to make one safer than the other. And regarding release
| strategy, that's just politics.
| usrusr wrote:
| Wasm could start out as a "function runner" box with no
| outside interface at all besides arguments and return
| value, with everything UI, storage or networking
| conveniently out of scope. Applet, in its day, had to be so
| much more than just the bytecode VM...
| kaba0 wrote:
| That were applets (used for a very different web than
| today). The JVM format/vm itself can also work as a
| simple "function runner", hell, without adding actual
| functionality you pretty much get this automatically from
| any runtime. Most brainfuck interpreters are trivially
| safe from deleting your hard drive, they simply don't
| have fs access.
| evacchi wrote:
| thanks! some of those consideration I made in the article I
| linked at the beginning of this post: "A History of
| WebAssembly"
| https://evacchi.github.io/posts/2022/11/23/a-history-of-
| weba...
| ridruejo wrote:
| I missed clicking on it, thanks for sharing!
| lifthrasiir wrote:
| Not sure why, but some links in the article seem to be missing,
| for example one to the draft GC spec for WebAssembly (which
| should probably link to https://github.com/WebAssembly/gc).
| evacchi wrote:
| you are right, I spotted a couple more links being broken.
| They should be fixed now. Thanks!
| sandGorgon wrote:
| Just fyi - there is an open bug to target wasm for Graal java
| compilation. https://github.com/oracle/graal/issues/3391
|
| to a very large extent, it is open till LLVM upstream is more
| functionally complete to support GC.
|
| wasm doesnt support GC to the way Java and Graal does. Which is
| why Graal is seriously impressive piece of tech. so it is not
| wasm vs java. Because that is really apples verus oranges.
|
| it is just a matter of time before java gets compiled to wasm.
| that's what makes me puzzled about this article. Maybe its really
| about rust or golang vs java.
| kaba0 wrote:
| While it is an informative article, I fail to be convinced that
| wasm is somehow superior (even for its current role).
|
| Validation is faster due to it being structured, but stackmaps
| make it fast for class files as well. It is lower level, but is
| that really a good thing? It is quite trivial to just assign a
| huge 'long' array to some jvm byte code for the same result, and
| while I will look at the linked GC proposal, because haven't been
| kept up-to-date on that, I think having a good GC is the single
| most important thing, as that is much harder to do than just
| providing a linear array. Which will in turn decimate the
| implementations (or at least mark a toy vs prod ready divide).
|
| Also, will that structured nature not make it a less than great
| compilation target? This means that certain, more niche languages
| will have to use ugly (and slow) hacks to get implemented. And it
| being lowish level it is much harder to optimize it well (in that
| you express the exact semantics).
| kodablah wrote:
| For many, language freedom/portability outweigh other concerns
| and this is where WASM has value. Running an algorithm written
| in Rust, Go, C, Zig, etc on the JVM with no JNI has value vs
| rewriting.
|
| > It is lower level, but is that really a good thing? It is
| quite trivial to just assign a huge 'long' array to some jvm
| byte code for the same result
|
| That's basically what the WASM implementations on the JVM
| currently do. But it is actually _higher_ level from a dev POV
| due to language choice (at a not-huge performance expense).
| bullen wrote:
| I'm considering building a Java compatible VM that only has
| static memory allocation to avoid GC.
|
| Why hasn't this been done by anyone yet?
|
| Also confused why this does not have a binary windows release
| yet: https://github.com/bytecodealliance/wasm-micro-runtime
|
| Edit: Epsilon is not the answer here you can stop mentioning
| that.
| beardyw wrote:
| Not sure how that would play out. Just not running the GC is an
| unsatisfactory answer. Otherwise what?
| bullen wrote:
| Basically you enforce static heap allocation. Meaning you
| cannot type new in a method only in the class definition. I
| don't even know if this can be enforced from the bytecode
| VM... just playing with it in my head.
| pron wrote:
| That would violate the Java specification, i.e. it's not
| Java. On the other hand, what you could do is only allow
| allocations to succeed in class initialisation (Java's
| "static") and throw an error if done outside it. Note,
| however, that even adding an element to a Map outside of
| initialisers would fail when using OpenJDK's standard
| library even if both key and value have been preallocated
| in initialisers, as it may allocate an internal node and/or
| a new hash array. So you may want to change some of the
| standard library to make it more useful with your
| restrictions.
|
| But you may want to ask yourself _why_ you want to do that.
| OpenJDK 's GCs have become really, _really_ good in both
| throughput and latency, and the main thing you pay in
| exchange is memory footprint.
| ako wrote:
| So the only place you really define what objects get
| created is in the class containing the main method? Seems
| fairly limited that you'd have to know upfront what your
| memory needs are? An app would have to allocate a large
| block of memory in the main class, and then handle memory
| management itself from this piece of memory?
| bullen wrote:
| Yes, you would have to overallocate and reuse.
|
| This is the way I code C now allready.
| ako wrote:
| That would remove a lot of the benefits of using Java,
| might as well use something like c. Garbage collection
| has been highly optimized in the jvm, and most would
| consider it a benefit of a jvm compared to managing
| memory yourself.
| the_only_law wrote:
| I've never understood it either. I've heard people doing
| similar things with Java in the low-latency trading area.
|
| I mean Java isn't the worst tool out there, but even C#
| outclasses it as a language. The biggest plus for Java is
| the massive, mature ecosystem which I imagine mostly
| evaporates when you're only using a self restrictive
| subset of the language itself.
| kaba0 wrote:
| It doesn't evaporate, more often than not only a small
| subset of these trading programs need that strict "no
| heap allocation" policy. The rest of the program is free
| to take advantage of the huge ecosystem.
| invalidname wrote:
| I have a bit of a different idea along somewhat different
| lines. Tweeted at you.
| jffhn wrote:
| >Why hasn't this been done by anyone yet?
|
| I used to do Java apps which didn't allocate memory in the long
| run, and so did some banks for high frequency trading, by
| reusing objects on the spot or using pools for objects with a
| life cycle. It imposes some programming style/patterns, in
| particular for APIs design, but it's perfectly doable, so I
| guess people prefer to just do that. In what context that
| wouldn't be enough? For proof?
| karussell wrote:
| Isn't this already possible with -XX:+UseEpsilonGC? (disables
| the GC) And then you manage memory manually in big (primitive)
| arrays.
| bzzzt wrote:
| Beat me to this answer ;) - I presume you can just allocate
| objects but you have to keep those allocations in check to
| prevent the JVM from terminating when going out of memory.
|
| Maybe object pooling (which helped performance in old JVM's
| in the 90s) will make a comeback? ;)
| bullen wrote:
| I'm going to try and prevent heap allocation in runtime.
| But since I'm going to use javac it's going to be ugly.
|
| Basically only static atomic arrays (int/float) in classes
| will be allowed, for cache and parallelism, and AoS up to
| 64 bytes encouraged to avoid parallel cache invalidation.
|
| And I'm even considering dropping float, and only have
| integer fixed point... but then I'll need to convert those
| in shaders as GPUs are hardcoded to float.
| garblegarble wrote:
| Not to be too negative here but... why? You'd be creating
| something syntax-compatible with Java, but where you
| wouldn't be able to use any existing Java code & unable
| to use most of the interesting features of Java (even
| string concatenation is handled by instantiating a new
| StringBuilder on your behalf by javac). Aren't you just
| reinventing a worse C at that point?
| bullen wrote:
| See my other 2 responses.
|
| On mobile here would reference on PC.
| usrusr wrote:
| What would be the point of that things relationship with
| java though? Is it somehow important that the bytecode
| run by a tiny vm that is apparently distributed alongside
| could also run on a proper JVM?
| bullen wrote:
| Javac, I don't want to write a compiler.
|
| The bytecode is only meant to run in my VM.
| lukeramsden wrote:
| Object pooling, mutable objects, flyweight encoding[0], and
| being allocation-free on the steady state are all alive and
| well in latency-sensitive areas like financial trading,
| plenty of which is written in Java
|
| [0] https://github.com/real-logic/simple-binary-
| encoding/wiki/De...
| imhoguy wrote:
| You can check Java Card to see how GC-less low memory
| programing was sorted out there.
| https://en.m.wikipedia.org/wiki/Java_Card
| bzzzt wrote:
| Because there's no need? There the 'epsilon' no-op GC in
| OpenJDK: https://openjdk.org/jeps/318
|
| If you keep your object allocation in check you can use this to
| guarantee no GC pauses.
| bullen wrote:
| Yes, but then you are downloading and running executables
| that contain the GC.
| bzzzt wrote:
| The Java "GC" also contains the memory allocator and
| handling for out of memory conditions, so if you want to
| use Java there needs to be 'something' there to call.
| wtetzner wrote:
| At that point, why even use Java?
| bullen wrote:
| See my other answers...
| marginalia_nu wrote:
| Why do you want to avoid GC?
| bullen wrote:
| See above.
| kasperni wrote:
| From another comment > Meaning you cannot type new in a method
| only in the class definition.
|
| How would you handle the ~ 1000 java.x classes that even a
| minimal HelloWorld programs uses. There are plenty of random
| allocation.
| bullen wrote:
| I'm not writing thus for old code, I'm writing it for new
| code. Specifically 3D action MMO scripting.
| pron wrote:
| > Why hasn't this been done by anyone yet?
|
| That depends what you mean by "this." The Java spec requires
| you to support `new A()` by returning a fresh object or
| throwing a VMError (like OutOfMemoryError). If by "this" you
| mean that allocations would succeed until some fixed amount of
| memory is exhausted, then, as others have pointed out, this has
| been done even in OpenJDK. If by "this" you mean that the
| allocation of some particular set of objects -- say, only those
| allocated during class initialisation -- would succeed
| regardless of memory consumption and all others would fail with
| an OutOfMemoryError, then I guess that it hasn't been done in
| that particular way because people haven't found it
| particularly useful as most Java programs would fail, but you
| can give it a try.
|
| RTSJ, the specification for hard-realtime Java [1][2], actually
| goes further than that and supports both "static" memory
| (ImmortalMemory) and arenas (ScopedMemory). So if that's what
| you mean, then it has been done.
|
| [1]: https://www.rtsj.org/specjavadoc/book_index.html
|
| [2]: https://www.aicas.com/download/rtsj/rtsj_76.pdf
| tryfinally wrote:
| Not many people are motivated to take optimization this far in
| the Java world.
|
| On the .NET side, the language is more suitable because you can
| define your own value types (coming soon in Java AFAIK), and
| there is a lot of people using C# for game dev which is a big
| use case for GC optimization.
|
| Unity has been integrating unmanaged allocators in their
| engine, which lets developers skip the GC much more easily
| (without having to manually preallocate and reuse memory, which
| isn't anyone's favorite workflow, and doesn't save you much
| compared to a fast native allocator). I've also seen a couple
| of roughly equivalent projects, eg. Smmalloc-CSharp,
| github.com/alaisi/nalloc.
| lmm wrote:
| > Why hasn't this been done by anyone yet?
|
| What would be the point? Java programs assume GC (or at least,
| they assume they can allocate memory and not worry about when
| it will be freed). If you don't have GC then you're not going
| to be compatible with extant Java programs, so what's the point
| in trying to be "Java compatible" at all?
| bullen wrote:
| Because 1) you don't want GC pauses 2) you don't want GC code
| to bloat the VM 3) You don't want memory leaks 4) You don't
| want people to not know what they are allocating 5) Static
| allocation is enough to make anything.
|
| 6) int arrays do not have cache misses (but you might need to
| pad them to avoid cache invalidation) 7) parallel atomic
| multicore works on int arrays out of the box!
| trimbo wrote:
| Why wouldn't you use C++?
| bullen wrote:
| 1) You cannot deploy C++ across different
| architectures/OSes without recompile.
|
| 2) VMs avoid crashes upon failures that cause a
| segmentation fault in native opcodes, with a VM you can
| keep the process from crashing AND get exact information
| where and how the problem occurred avoiding debug
| compiles with symbols and reproducing the error on your
| local computer.
|
| Right now I use this with my C++ code to get somewhere
| near the feedback I get from Java (but it requires you to
| compile with debug): http://move.rupy.se/file/stack.txt
|
| The question you really should ask is why are people
| using C++? Performance is only required in some parts of
| engines, to have a VM without GC on top should be default
| by now (50 years after C and 25 years after Java).
| kaba0 wrote:
| 1) Java has the very best GCs out of anything to the point
| that I very much question anyone claiming to suffer from GC
| pauses on most workloads. Do you really have an application
| which suffers from that or is that just the usual "GC bad"
| mantra? Your average C program may spend just as much time
| trying to malloc a given block in its fragmented heap than
| that.
|
| 2) Why exactly? 3) that's why you have a GC. But if I
| really want to understand what you mean (I assume you meant
| non-memory resources not being explicitly closed?), try-
| with-resources and cleaners solve the problem quite well.
|
| 4) Does it really matter if the last 2 decades were spent
| on optimizing allocation to the point where it is literally
| a pointer bump and like 3 basic, thread-local instructions?
| Deallocation can be amortized to be practically zero cost
| (moving GC), at RAM's expense. Where it really really
| matters though, you could always do ByteBuffers, the new
| MemorySegment's or just straight sun.misc.Unsafe pointer
| arithmetics. That string allocation will be escape analyzed
| and be stack allocated either way.
|
| I don't even know what do you mean by static allocation.
| You mean like in embedded, having fix sized arrays and
| exploding when the user enters a 32+1 letter text? I really
| don't miss that. 6) value classes are coming and solving
| the issue. Though it begs the question, what's the size of
| your average list? Also, how come it doesn't matter for all
| the linked lists used extensively in C? Also, see point 4.
| 7) I don't get what you mean here, do you mean not doing
| atomic instructions, because java don't have out-of-thin-
| air values? There are rare programs that can get away with
| that, but I think Java has quite a great toolkit for
| synchronization primitives to build everything (most
| concurrency books use it for a reason).
| bullen wrote:
| Yes, I mean having limits on everything! Every input has
| a max value. Players, messages count, message length,
| weapons they can hold etc. You can add more by
| recompiling, you should reuse them (for size but also to
| keep data contiguous) even if that is challenging under
| multicore utilization and you can add MUUUUUCH more than
| the CPU can handle to compute anyway so this is not an
| issue.
|
| The first and last bottleneck of computers is and will
| always be RAM. Both speed, memory size and energy. A
| 256GB RAM stick uses 80W!!!! Latency is increasing since
| DDR3 (2007) and we have had caches to accommodate for
| slow RAM since 386 (1985) (3 always seems to be the last
| version, HL3 confirmed? >.<):
|
| You need to cache align everything perfectly: 1) all data
| has to be in an array (or vector which is a managed array
| but i digress) 2) you need your types to be atomic so
| multiple cores can write to them at the same time without
| segmentation fault (int/float). 3) You need your
| groups/objects/structs to perfectly fill (padded) 64
| bytes. Because then multiple cores cannot invalidate each
| others cache unless they are writing to the same struct.
|
| So SoA vs. AoS never was an argument! AoS where
| structures are exactly 64 bytes is the only thing all
| programmers must do for eternity! This is the law of both
| X86 and ARM.
|
| So an array of float Mat4x4 is perfect and I suspect that
| is where the 64 bytes came from. But here is another
| struct just as an example: struct Node {
| int mesh, skin; Vec3 spot, pace; Quat
| look, spin; };
| kaba0 wrote:
| But things don't fit into 64 bits all the time, and then
| you get tearing. This is observable and now you have to
| pay for "proper" synchronization. Also, apple's m1's
| reason for speed is pretty much bigger cache, so I don't
| think it's a good choice to go down this road.
|
| Most applications have plenty of objects all around that
| are rarely used and are perfectly fine with being managed
| by the GC as is, and a tiny performance critical core
| where you _might_ have to care a tiny bit about what gets
| allocated. This segment can be optimized other ways as
| well, without hurting the maintainability, speed of
| progress etc of the rest of the codebase.
| bullen wrote:
| 64 bytes, 512 bits.
|
| Cachelines are 64 bytes on all modern hardware.
|
| They will probably never change this value ever.
|
| Everything fits into 64 bytes if you make the effort.
|
| And if it doesn't you have to use two Arrays of 64 byte
| Structures and pad the last.
|
| This is non negotiable and I'm completely baffled nobody
| has mentioned this yet.
|
| I call this law: Ao64bS (did I invent my first law?) :D
| kaba0 wrote:
| It may be a language barrier thingy, but then we are
| talking about different things. Also, that is
| architecture dependent.
| bullen wrote:
| Nope on all modern CPUs (X86 and ARM) this is 64 bytes
| and has been since a looong time...
| astrange wrote:
| Cache lines are 128 bytes on M1.
|
| But since it's AMP and not SMP, sharing work across cores
| doesn't necessarily work how you expect it to.
| bullen wrote:
| Can you ask the OS to give you a certain core type?
|
| 128 bytes is perfect 2 x 64! So even if the risk of cache
| invalidation goes up even if two cores are not writing to
| the exact same structure the alignment still works!
|
| Good job Apple!
| fisf wrote:
| There absolutely are modern systems that have e.g. 128
| byte cache lines (M1).
| sorokod wrote:
| I assume that by "you" you mean "I"
|
| From my perspective I am happy with the tradeoff of hanving
| gc pauses and not needing to manage memory manually.
| singularity2001 wrote:
| > Java programs assume GC
|
| only users of long running programs assume GC, the program
| itself doesnt care.
| marginalia_nu wrote:
| The programmer most likely assumes short-lived objects are
| "free" (as they effectively are in Java), and can be
| allocated in loops and so on without filling up memory and
| without incurring any real penalty to performance (via TLAB
| or possibly elision).
| grashalm wrote:
| Nobody did that because java is a safe language by design.
| Manual allocation would make it an unsafe language. For the
| vast majority of applications correctness wins over
| performance.
|
| Short lived objects are extremely cheap on the JVM with the
| right GC. Almost stack allocation cheap. So if you write code
| that avoids too many long lived objects there is almost no
| overhead to a GC.
|
| For long lived objects GC based compaction can give you even
| some performance advantage over manual allocation due to better
| memory locality. But that heavily depends on the application of
| course.
|
| In my experience people blame the GC way too early. Most often
| the application is just poorly written and some small tweaks
| can fix gc spikes.
| [deleted]
___________________________________________________________________
(page generated 2022-12-23 23:02 UTC)