Hacker News new | past | comments | ask | show | jobs | submit | tjelen's comments login

Code testing is a big one for me. I'm currently using in-memory sqlite for tests and I'm often running into differences between sqlite and postgres (default values, JSON handling, etc). This could allow me to use the real thing without running a full Docker instance.


The innermost rendering loop in the JS seems to create and destruct an array instance for every single pixel iteration (see https://github.com/dmaynard/chaos-screen-saver/blob/master/s...). I guess that this could be potentially optimized away by the JIT, but it will make things slower or at least less predictable.


That's kind of an underrated aspect of these comparisons - while you absolutely can work around Javascript's weird performance cliffs and avoid putting pressure on the garbage collector, you have to fight the language at every turn because so many idiomatic JS patterns are inherently slow or flood the GC. You may find idiomatic JS easier to work with than something like Rust, but Rust is much easier to work with than the narrow and loosely defined subset of JS that you have to stick to for optimal performance. Taken to its limit you end up more or less writing asmjs by hand.


The rust code is 3x as long and a lot more complex too.

In theory, static typing would correct the biggest performance issues in JS (use monomorphic functions, don't mutate objects, and limit arrays of just one primitive/object type).

In practice, TypeScript allows and encourages you to create types that are horrendous for performance.

I'd love to see a comparison using AssemblyScript (basically stricter TS for WASM). I'd bet it's nearly the same speed as Rust while still being a third of the size.


The Rust version is longer mostly due to boilerplate for WASM<>JS interface, and awful vertically-exploded formatting (probably caused by rustfmt's dumb heuristics).

but the core loop in Rust is pretty straightforward. It could have been shortened and optimized further.

Also keep in mind that the larger the project, the harder it gets to keep JS in the performance sweet spot without tipping over any JIT heuristic, using GC, or accidentally causing a perf cliff, while the Rust has pretty stable and deterministic optimizations and keeps its memory management control at any scale.


https://blog.suborbital.dev/assemblyscript-vs-rust-for-your-...

This is a pretty good rundown of expected comparisons, but I doubt there will be any surprises here.


That's a really good point and I think that in this sense the comparison is really made between Rust and JS rather than between WASM and JS (as others have complained).


That's an interesting point.

It was my understanding that the V8 GC frankly was rarely used, and that they generally just let memory pile up quite a lot before it's used, in the hopes that it may never have to be run during application lifetime.


It depends on the application, a short-lived script may complete all of its work before the GC interrupts it, but something that runs continuously can't afford to generate much if any garbage in its main loop because it will inevitably pile up and eventually cause a huge stall when the GC decides that enough is enough. It's especially critical for animated or interactive applications like games, because with those the stall will manifest as the application freezing completely until the GC is finished.


Last I checked, destructuring created 4-5x as many bytecode instructions and a potential GC pause. I'd think this could be detected and optimized easily enough, but I guess there are bigger problems for the JIT devs to solve.

A quick profiling seemed to indicate that just a bit less than 10% of the JS time is being spent on the DOM rather than the calculations at hand. I wonder how much of that could be reclaimed simply by running the calculation in a web worker.

I suspect the bitwise AND operator every loop is another big performance issue. Normally, the JIT would leave the loop iterator as a 31-bit int, but because it is stored in a shared object, I suspect it must (f64 -> i31 -> AND -> f64) every time. A local variable that updates the object variable every 64ms and resets to zero would probably be faster.

The decPixel function should use a switch statement with values of 0, 1, 255, and default so it only needs to branch one time. This is probably a decent performance win too as around 15-20% of all time is spent here.

EDIT: I should praise the author for using a ternary instead of Math.max() as very few people know that the ternary is literally 100x faster. I wonder why this optimization was never made as it seems common enough.


I actually thought Math.max was faster in modern Chrome than a ternary.


You can try for yourself (this uses an if..else, but they compile to the same thing)

https://www.measurethat.net/Benchmarks/Show/6528/0/mathmin-v...


Are these Peter de Jong attractors?

I'm trying to work out how my interpretation of the calculations (in JS)[1] compare with the authors code, but trying to measure performance in CodePen is ... difficult to work out. My approach was to: 1. Run the CodePen with the inspector open; 2. Start recording performance; 3. Right click on the display panel and select 'Reload Frame'; 4: Stop recording performance after the images reappear.

... But when I look at the results nothing is making sense. Clearly my approach was wrong.

[1] - https://codepen.io/kaliedarik/pen/JjRJwNa


Why do you think the JIT would be able to optimize this? And how would it go about it? I know only about a few rough things and heuristics. I wouldn't expect or assume that this would be optimized.

It would probably have to recognize that the _usage_ of this function can be translated into a local mutation without allocating additional arrays. But from just looking at the function locally it isn't clear whether that is a safe assumption.


What I meant to say is that this definitely isn't a safe assumption and performance of this loop will be less predictable. That said, I wouldn't be surprised that such complex JITs as V8 or JSC can detect this scenario.


The actual code running comes from an unpublished module @davidsmaynard/attractor_iterator. It also returns a new array instance for that part. Most importantly, it uses a strange mix of global variables, class methods and properties, which I guess the author tried to optimize by trial & error.


It has been reported by a major Czech newspaper (it is originally a Czech project): https://domaci.hn.cz/c1-67066670-fiktivni-eshop-se-zbranemi-...


And all that is just for being able to write `response = await fetch(...)` instead of `fetch(...).then(...)`.


My fault. Didn't know that ES2015 actually supported `.then()`. Anyway, this was also an exercise for me to learn about babel (I use CRA all the time so have no idea what is going on behind the scenes) and this was a nice intro!



Isn't it already supported backend in LLVM? https://github.com/llvm/llvm-project/tree/master/llvm/lib/Ta...

I mean it is still under development but I think it is usable. Here's a quick intro I found some time ago: https://dassur.ma/things/c-to-webassembly/


And at the same time they recommend using UAs to detect agents incompatible with SameSite=None cookies (see https://www.chromium.org/updates/same-site/incompatible-clie...), including certain Chrome versions.


Yeah, I'd say that it's actually pretty good result. Too bad that the benchmark doesn't include any non-browser WASM runtimes, like Wasmtime or Wasmer.


So why not just write the original formula into the comment above the code? Maybe it's not the purest approach, but it sure would help parsing the code in this case.


Yep. If the code doesn't clearly show the original intent, document your intent in a comment next to the code so future you or others can double check. Esp in gfx engines where some blocks are just math translated one to one to code.


Why not one step further, and let the original formula be the code? Directly compilable Latex:

    \vec r = \frac {n_{1}}{n_{2}} \cdot (\vec v - dot(\vec v, \vec n) \cdot \vec N) - \sqrt{1 - \frac {n_{1}^2}{n_{2}^2} \cdot (1 - dot(\vec v, \vec n))} \cdot \vec N
(This is partly a joke and partly a serious suggestion, there are definitely people out there who would find that easier to write, especially if the IDE rendered it properly. I stole it by searching for ni_over_nt and finding http://viclw17.github.io/2018/08/05/raytracing-dielectric-ma... )


Julia (a language focused on science/math) does it partially. For example in the editor or REPL you can just type \sqrt [tab] \alpha [tab] and it will generate √α which is valid code. Most math symbols in unicode are supported and overloadable. And you can also write matrix like a table, such as

[1 2 3

4 5 6

7 8 9].

And you can transform Julia expressions to LaTeX:

https://github.com/korsbo/Latexify.jl

But even they didn't go as far as being able to parse full LaTeX expressions (though you technically could with reader macros plus editor support).


You could probably do it that way in Julia.


In my experience, it's better to create a "request context" at the beginning of handling a request and pass it down explicitly to subsequent async calls that handle the request. I think that this approach is similar to Golang's "context" package, for example.

As other commenters note, using CLS (via async hooks or domains) is often too much magic and can possibly cause leaks or at least it results in program logic that's hard to reason about and hard to test.


I disagree. Logging is a cross-cutting concern. It is basically the example used to define what a cross-cutting concern is.

I want my cross-cutting concerns to be as invisible as possible, I definitely do not want them to proliferate through all function calls I make in the app.

I do not believe domains or async hooks is too much magic, and they are pretty sound when used properly. As long as they are used responsibly and in a limited setting it is just the right amount of magic.


Cross-cutting concerns are an antipattern.

Logging should not happen inside libraries or submodules. Libraries should expose errors and exceptions either via throwing or message passing (e.g. dispatching error events). Logging should happen at the top-level of the application from a central place (logging logic should not be scattered all over the source code). The top level application logic should aggregate errors from children components/libraries and decide how to log them. This makes it easy to change the default logger and to customize various aspects of the logging.

When you scatter logging logic everywhere throughout your source code, you break the principle of separation of concerns which is probably the most important principle of software development. Even the term 'cross-cutting concern' implies a violation of separation of concerns (a violation of the cross-cutting kind to be exact). A concern cannot be both separate and cross-cutting.


>When you scatter logging logic everywhere throughout your source code, you break the principle of separation of concerns which is probably the most important principle of software development. Even the term 'cross-cutting concern' implies a violation of separation of concerns (a violation of the cross-cutting kind to be exact). A concern cannot be both separate and cross-cutting.

That's because logging is not a concern of your app -- it's a secondary need that's orthogonal (hence cross-cutting) to its actual concerns, and which is there regardless of the app.

Changing your whole app's structure just so that you collect things to log in a central place (as you propose), would be the real violation of concerns.

Not to mention this way you pass around information that might be useless for the purposes of the app (and is just needed for debugging), that it's enough that separate components know themselves internally.


>> Changing your whole app's structure just so that you collect things to log in a central place (as you propose), would be the real violation of concerns.

Maybe it depends on what kind of language/platform you use but if each component is able to dispatch events on itself instead of logging, then that does not require changing the whole app's structure.

The only concern of the top level application logic would be monitoring/reporting so logging fits naturally under that label.


When I write code, I don't want anything to be invisible. I should be able to look at a single file, and understand how it works without reading any others. Sure, there may be some imports at the top of the file, but clearly named external API's will infer what they do.


Yes, and when `const cls = require('cls-hooked')` is written at the top of the file it's pretty clear that the file is using CLS. Nothing invisible about it.


Yes. This is correct way to do logging. Pass an object with request, user, logger and other attributes around as you process it. This is basically "session"


This is the exact problem I solved with CabinJS; making this simple on both client and server. I've tried nearly every logging service and they continually get one thing or the other wrong (even after my attempts to fix them).

https://cabinjs.com


Hapi.js does this natively.


No, it does not do it to best standards and simplicity. Hapi itself is also incredibly convoluted and bloated.


I believe just the opposite.

Explicitly passing the context is robust but in my experience becomes neglected because of the extra work involved everywhere and so just doesn't happen. (The tracing equivalent of requiring a password so strong that people write it on sticky notes.)

My C++, Python, Ruby, C#, and Java runtimes provides thread local storage without cooperation from every function; I don't see why it's improper for my JS runtime to do the same.


It's single threaded. You can usually stick the context in the request object itself.


The issue becomes when you're calling some generic library that's unaware that it is running in a request handler. If you want to log from deeper in some logic, maybe you've passed some data or domain objects but not a request/context/session object. Do you pollute that logic with additional data passed in or does your library just create its own logger and call `log.info('intermediate result of some thing: XXX')` and let the CLS magic associate that with the request that generated the call?


And generally speaking, you want to exit the domain of request/response as early as you can, because it's your interface with the outside world, rather than something that should be pervasive throughout your system.


You still have to rely on async hooks since you won’t be able to pass the context through to a library that doesn’t account for such API.


Along the way you might have some generic data transformation functions that require no notion of a request. They're just pure functions that transform data and can be easily unit tested. Seems nuts to mock a request just to test something like that.


If they don't require a notion of a request then don't pass it to them?


Or you could just pass the function an optional tracing ID as an argument.


Or just keep logging / requests out of the pure functions, and leave it to the context around them. You can always have the pure functions return a `Result` or `Either` type, if you need information about a failure to be logged.


This golang pattern is the worst. Why not just built in thread resolution when the parent cancels?

The context pattern exists because go routines don't have parent child relationships.


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: