6 ms·
> performance and security from running JavaScript outside of a browser. I'm just now building a node app to filter point clouds, so lots of number crunching.
by mschuetz 8y ago
> performance and security from running JavaScript outside of a browser.
I'm just now building a node app to filter point clouds, so lots of number crunching. In two days I've got something in javascript that's faster than the C++ solution I've been working on for a week. Mostly because javascript/node makes it trivial to parallelize file IO while doing work in the main thread. This app reads 13 million points from 1400 files (~200mb), filters all points within a clip region, and writes the 12 million resulting points in a 300MB file, all in 1.6 seconds. (File reads were cached by OS and/or disk due to repeated testing, but file writes probably not)
My personal conclussion is that javascript can rival or even exceed the performance of C++, not because it's inherently faster, it's obviously not, but because it makes it much easier to write fast code. For the highest possible performance you'll defenitely want to use C++ but sometimes you'll have to spend multiple times the work to get there.
- chrisco255 8y agoRight, V8 works great at optimizing JS and it handles streams great. Productivity is one of the most important factors to think about when building software systems since human time is much more expensive than CPU cycles. That's why Node.js works great.
- maerF0x0 8y agoThis fails from the false dichotomy of speed of execution vs speed of development (which includes fixing bugs). Well written languages optimize to a certain weighted preference of the two and some languages deliver more of _both_ than others. For example; Typescript is fast to write. and Golang is reasonably quick to write, _and_ execute. Both should have ~15% less bugs than javascript, potentially making them faster to develop (where bugs matter).
- ninjakeyboard 8y agowhat is the 15% figure from? I'm suddenly working with a dynamically typed language (elixir) coming from scala and I do find more bugs. Would like some hard evidence to support that so just curious.
- maerF0x0 8y ago15% is from this paper as I recall it: http://ttendency.cs.ucl.ac.uk/projects/type_study/documents/type_study.pdf http://ttendency.cs.ucl.ac.uk/projects/type_study/documents/... https://blog.acolyer.org/2017/09/19/to-type-or-not-to-type-quantifying-detectable-bugs-in-javascript/ https://blog.acolyer.org/2017/09/19/to-type-or-not-to-type-q...
- ninjakeyboard 8y agothanks!
- pera 8y agoHave you tried to use Dialyzer or Dialyxir and typespecs?
- ninjakeyboard 8y agono. I probably should.
- chrisco255 8y agoOnce you've paid the upfront cost of learning Golang, I might agree with you. But then again, in particular when building a full stack app (and not when on a team that has back end and front end specialists), it's helpful to use the same context (JS/NPM) when devving. People are bad at context switching.
- maerF0x0 8y agoThat's why I mentioned TypeScript. It can run on both "sides", and has faster speed of development (as I defined it above, considering bugs) ...
- notdonspaulding 8y agoI've heard this argument several times now and it finally hit me what I dislike about it. If you aren't context switching between your backend code and your frontend code (even when both are JS), you're probably incurring technical debt in your architecture to be paid in even greater numbers of dev hours down the road. When you are writing an all-JS full-stack app, do you really feel like you're only working on a single app, as opposed to two different apps which happen to share the same repo?
- chrisco255 8y agoIt's not even likely they're in the same repo when working with Node (although possible). The context switch between apps is one cost you have to pay (surely your backend will differ from frontend in architecture). However, the costs of switching languages is higher. Golang / JS conventions are very different. It is possible to share some common libs front end / back end (lodash, validation logic, etc) and that helps too.
- dozzie 8y ago> The context switch between apps is one cost you have to pay [...]. However, the costs of switching languages is higher. Is it? I was working with a system where server is written in Erlang and client (and another server) is written in Python. No problems with switching back and forth.
- blattimwind 8y ago> (File reads were cached by OS and/or disk due to repeated testing, but file writes probably not) Unless you flush the pages manually, your dirty pages (written files) live on long after your process died. Depending on system and configuration, minutes or even hours can pass before they are flushed to disk.
- mschuetz 8y agoDo you know about best practices to benchmark with uncached data? It's something I've been wondering about for a long time and I've seen many attempts at benchmarking things without regard for disk caching. e.g. benchmarking in-memory data bases to out-of-core databases but because of caching due to repeated runs, the results were meritless since the out-of-core databases had their stuff in memory as well.
- zbjornson 8y agoTake a look at https://github.com/Feh/nocache https://github.com/Feh/nocache. It works effectively for reads. I don't know about writes.
- brlewis 8y agoDid you use typed arrays, or did the benefit of async I/O outweigh the cost of using double-precision floats for everything?
- mschuetz 8y agoI'm reading the values directly from the Buffer object that is returned from fs.readFile using buffer.readUInt32LE and buffer[index], then convert the values to doubles to do the hit-test and if it succeeds, it's immediately written to the output buffer. The nodejs Buffer object is a subclass of Uint8Array as far as I know. On writing, the double coordinate values are transformed back into a fixed-precision integer format and stored in the output Buffer object. I'm not generating an intermediate buffer since that does decrease performance a bit. It's directly from input buffer to output buffer. Output buffer is initially allocated with the same size as the input, and before sending it to the output stream it's cut to the actual size. One thing I've previously learned and which has shown to be still true is that writing individual bytes to a buffer is faster than writing integers. So originally I did this: outBuffer.writeInt32LE(ux, outOffset + 0); But this turned out to be significantly faster. (~20%) // do once let tmpBuffer = new ArrayBuffer(4); let tmpUint32 = new Uint32Array(tmpBuffer); let tmpUint8 = new Uint8Array(tmpBuffer); // do many times tmpUint32[0] = ux; outBuffer[outOffset + 0] = tmpUint8[0]; outBuffer[outOffset + 1] = tmpUint8[1]; outBuffer[outOffset + 2] = tmpUint8[2]; outBuffer[outOffset + 3] = tmpUint8[3];
- zbjornson 8y agoJust constructing a Uint32Array view of the buffer and accessing values by index way(!) faster than using the buffer.read* methods. Likewise, you don't need to copy bytes in your last code sample. You can copy uint32s.
- mschuetz 8y agoThe buffer contains not just integer but also byte, unsigned byte, short, etc. Due to this, the buffer length may not be a multiple of 4, and Uint32Array views can only be constructed on buffers with a length of a multiple of 4. Edit: Also, the stride from one record to the next can be anything, e.g. 15. An Uint32Array would need a stride of 4 or a multiple of 4 to be useful. Edit2: I could try to create 4 Uint32Arrays with byteOffsets 0 to 3, and with a view length of a multiple of 4, then use the one that works with the attribute I'm currently processing. Not sure if that's really going to be faster but who knows.
- ex3ndr 8y agoCan you share some sources? I tried to process simple CSV files in a very straight forward (but async) way and got reading 200mb CSV and just splitting it to columns (with simple split by comma) takes ~10 seconds. Also simplest HTTP request in express is handled in several ms and that's A LOT in my opinion.
- mschuetz 8y agoI don't really have any explanatory sources at hand, just the source code here: https://github.com/potree/PotreeServer/blob/redo/src/RegionsFilter.js https://github.com/potree/PotreeServer/blob/redo/src/Regions... Points of interest: Schedule files to be loaded. This will usually load around 1000 files: https://github.com/potree/PotreeServer/blob/redo/src/RegionsFilter.js#L283 https://github.com/potree/PotreeServer/blob/redo/src/Regions... Whenever a file is loaded, iterate through the points in it, do the filtering and write the results to the output file: https://github.com/potree/PotreeServer/blob/redo/src/RegionsFilter.js#L286 https://github.com/potree/PotreeServer/blob/redo/src/Regions... Wait until all files were loaded and processed: https://github.com/potree/PotreeServer/blob/redo/src/RegionsFilter.js#L399 https://github.com/potree/PotreeServer/blob/redo/src/Regions... Wait until the output was written to disk: https://github.com/potree/PotreeServer/blob/redo/src/RegionsFilter.js#L401 https://github.com/potree/PotreeServer/blob/redo/src/Regions... What happens is that ~1000 files will be loaded in the background, points are filtered in the main thread and even while some files are still being loaded, we already start writing the results to the output file. > I tried to process simple CSV files in a very straight forward (but async) way and got reading 200mb CSV and just splitting it to columns (with simple split by comma) takes ~10 seconds. CSV may be much bigger challenge since it's ASCII data. That always tends to take multiple times longer.