├── .gitignore ├── .jshintrc ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── dist ├── p-j-s-standalone.js ├── p-j-s-standalone.min.js ├── p-j-s.js └── p-j-s.min.js ├── docs ├── helpers │ ├── 3.1.3 │ │ └── par_seq_bench.xlsx │ └── benchmark_sample │ │ └── v8-artifacts │ │ ├── code-60612-1.asm │ │ ├── hydrogen-60612-1.cfg │ │ └── index.html ├── paper │ ├── .gitignore │ ├── comparison.png │ ├── context.png │ ├── llncs.cls │ ├── paper.pdf │ └── paper.tex ├── presentation │ ├── deck.key │ ├── deck.pdf │ ├── deck.pptx │ └── images │ │ ├── context.png │ │ ├── cpuscaling.jpg │ │ ├── no_shared_chart.png │ │ └── shared_chart.png ├── report │ ├── .gitignore │ ├── array_merging.tex │ ├── array_partitioning.tex │ ├── background.tex │ ├── code_sharing.tex │ ├── commands.tex │ ├── context.tex │ ├── function_caching.tex │ ├── function_transfer_time.tex │ ├── img │ │ └── naive_benchmark_ir_hydra.png │ ├── initialization.tex │ ├── inlining.tex │ ├── intro.tex │ ├── js_lang.tex │ ├── main.bib │ ├── main.pdf │ ├── main.tex │ ├── map_serial_vs_parallel.tex │ ├── micro_benchmarks.tex │ ├── modules.tex │ ├── overview.tex │ ├── serialization_transference.tex │ └── title.tex └── uba │ └── Proposal.pdf ├── examples ├── mapSample │ ├── README.md │ ├── index.html │ └── scripts │ │ ├── main.js │ │ └── require.js ├── sapiaPicture │ ├── default-pjs.html │ ├── default-pjs.js │ ├── default.css │ ├── defaultnoworker.html │ ├── defaultnoworker.js │ ├── p-j-s-standalone.js │ ├── p-j-s-standalone.min.js │ ├── p-j-s.js │ ├── p-j-s.min.js │ ├── package.json │ ├── pic-1024x668.jpg │ ├── pic-2448×3264.jpg │ ├── pic-300x300.jpg │ ├── pic.jpg │ ├── readme.md │ └── start.sh └── sharedSapiaPicture │ ├── default-pjs.html │ ├── default-pjs.js │ ├── default.css │ ├── defaultnoworker.html │ ├── defaultnoworker.js │ ├── p-j-s-standalone.js │ ├── p-j-s-standalone.min.js │ ├── p-j-s.js │ ├── p-j-s.min.js │ ├── package.json │ ├── pic-1024x668.jpg │ ├── pic-2448×3264.jpg │ ├── pic-300x300.jpg │ ├── pic.jpg │ ├── readme.md │ └── start.sh ├── gulpfile.js ├── karma-src.conf.js ├── package.json ├── spikes ├── 1 │ ├── PoC │ │ ├── both.js │ │ ├── main-both.html │ │ ├── main-series.html │ │ ├── main-test.html │ │ ├── series.js │ │ └── test.js │ └── map │ │ ├── map01-main.html │ │ ├── map01-usecase.js │ │ ├── map02-main.html │ │ ├── map02-workers.js │ │ ├── map03-allworkers.js │ │ ├── map03-main.html │ │ ├── map04-joinresult.js │ │ ├── map04-main.html │ │ ├── map05-main.html │ │ ├── map05-mapping.js │ │ ├── map06-blobmapper.js │ │ ├── map06-main.html │ │ ├── map07-main.html │ │ ├── map07-typed.js │ │ ├── map08-alltypes.js │ │ ├── map08-main.html │ │ ├── map09-lib.js │ │ ├── map09-main.html │ │ └── map09-main.js ├── 1.2 │ ├── README.md │ ├── longerSharedTransferrableVsCloning.js │ ├── longerTransferrableVsCloning.js │ ├── longerTransferrableVsCloning.png │ ├── transferableSharedVsNotShared.js │ ├── transferableSharedVsNotShared.png │ ├── transferrableVsCloning.js │ └── transferrableVsCloning.png ├── 2.1.1 │ ├── newVsPreExistent.js │ └── newVsPreExistent.png ├── 2.2.1 │ ├── README.md │ ├── notes.txt │ ├── test-functionSerialization │ │ ├── funcSerialization.js │ │ └── funcSerialization.png │ ├── test-howToSendCode │ │ ├── longSerialization.js │ │ ├── longSerialization.png │ │ ├── shortSerialization.js │ │ └── shortSerialization.png │ ├── test-howToSendCodeWithData │ │ ├── encoding.js │ │ └── encoding.png │ └── test-syncEncodingAPI │ │ ├── encodingAPI.js │ │ └── encodingAPI.png ├── 2.3.1 │ ├── README.md │ ├── copyComparison.js │ ├── copyComparison.png │ ├── splitComparison.js │ └── splitComparison.png ├── 2.4.1 │ ├── README.md │ ├── merge.js │ ├── merge.png │ ├── sharedMerge.js │ └── sharedMerge.png ├── 3.1.3 │ ├── comparison │ │ ├── chart.png │ │ ├── chartShared.png │ │ ├── index.html │ │ ├── jspBenchmark.js │ │ ├── jspBenchmarkShared.js │ │ ├── jspBenckmarkResultTable.txt │ │ ├── pjs_map_seq_vs_par_jsperf.xlsx │ │ ├── pjs_map_ser_vs_par_no_jsperf.xlsx │ │ └── setup.js │ ├── functionCache │ │ ├── functionCache.js │ │ └── functionCache.png │ ├── irhydra-noworkers │ │ ├── code-90768-1.asm │ │ ├── hydrogen-90768-1.cfg │ │ ├── inlined-clamp.png │ │ ├── inlined-colorDistance.png │ │ ├── inlined-noise.png │ │ └── inlined-processSepia.png │ ├── irhydra-pjs-noinlined │ │ ├── code-96600-5.asm │ │ ├── hydrogen-96600-5.cfg │ │ ├── ww-clamp.png │ │ ├── ww-colorDistance.png │ │ ├── ww-mapper.png │ │ └── ww-noise.png │ └── pjsMapInlining │ │ ├── pjsMapInlining.js │ │ └── pjsMapInlining.png ├── 3.2.3 │ └── comparison │ │ └── jspBenchmark.js ├── 3.3.3 │ └── comparison │ │ └── jspBenchmark.js ├── 6.1 │ ├── cloningComparison │ │ ├── jspBenchmark.js │ │ ├── jspBenchmarkLong.js │ │ ├── result.png │ │ └── resultLong.png │ ├── context.png │ ├── mapComparison │ │ ├── consoleError.png │ │ ├── error.js │ │ ├── jspBenchmark.js │ │ └── jspError.png │ └── sendingAdditionalsSeqVsPar │ │ ├── jspBenchmark-all-browsers.png │ │ ├── jspBenchmark.js │ │ └── jspBenchmark.png ├── 6.2 │ ├── inlinedVsGlobalInlined │ │ ├── jspBenchmark.js │ │ └── result.png │ ├── inlinedVsGlobalNotInlined │ │ ├── jspBenchmark.js │ │ └── result.png │ ├── jspBenchmark.js │ ├── noInlinedVsGlobalNotInlinedWithContext │ │ ├── jspBenchmark.js │ │ └── result.png │ ├── noInlinedVsGlobalWithGlobalContext │ │ ├── jspBenchmark.js │ │ └── result.png │ ├── readme.md │ └── result.png └── shared │ ├── 2.2.1 │ ├── jspBenchmark.longSendCode.js │ └── jspBenchmarklongSendCode.png │ ├── 3.1.3 │ ├── jspBenchmark.js │ └── results.xlsx │ └── paper-bechmarks │ ├── section-4.1 │ ├── jspBenchmark-long.js │ └── jspBenchmark-short.js │ ├── section-5.1 │ └── jspBenchmark.js │ ├── section-5.2.1 │ ├── jspBenchmark-long.js │ └── jspBenchmark-short.js │ └── section-5.2.2 │ └── jspBenchmark.js ├── src ├── chain.js ├── chain_context.js ├── context.js ├── context_update_packager.js ├── errors.js ├── index.js ├── job_packager.js ├── operation_names.js ├── operation_packager.js ├── result_collector.js ├── typed_array_merger.js ├── typed_array_partitioner.js ├── utils.js ├── worker.js ├── worker_core.js ├── workers.js └── wrapped_typed_array.js └── test ├── chain_context.test.js ├── chaining.test.js ├── chrome_version_helper.js ├── context.tests.js ├── errors.tests.js ├── filter.tests.js ├── firefox_helper.js ├── global_context.tests.js ├── index.tests.js ├── job_packager.tests.js ├── local_context.test.js ├── map.tests.js ├── multiple_chaining.test.js ├── operation_context_functions.tests.js ├── operation_packager.test.js ├── promises.test.js ├── queueing_workers_jobs.test.js ├── reduce.tests.js ├── result_collector.tests.js ├── shared_array.test.js ├── supported_array_types_helper.js ├── typed_array_merger.tests.js ├── typed_array_partitioner.tests.js └── worker_core.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | lcov.info 3 | *.seed 4 | *.log 5 | *.csv 6 | *.dat 7 | *.out 8 | *.pid 9 | *.gz 10 | 11 | *.sublime-workspace 12 | 13 | pids 14 | logs 15 | results 16 | build 17 | .grunt 18 | 19 | node_modules 20 | 21 | .DS_Store -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": "nofunc", 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "unused": true, 11 | "boss": true, 12 | "eqnull": true, 13 | "node": true, 14 | "expr": true, 15 | "browserify": true, 16 | "mocha": true, 17 | "globals": { 18 | "expect": true, 19 | "Promise": true 20 | } 21 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | before_install: 5 | - export CHROME_BIN=chromium-browser 6 | - export DISPLAY=:99.0 7 | - sh -e /etc/init.d/xvfb start -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | It's great that you are reading this document! 4 | 5 | ## Issue tracker 6 | * If you see an issue tagged **bug** feel free to fix it and send a pull request. Make sure the build is passing for our PR. 7 | * If you see an issue tagged **ideas** and you would like to take it on, please add a comment to start the discussion. 8 | * If you would like to see a new feature implemented, create a new issue with title "[RFC] {description}". That way we know that is a feature request and can review and tag accordingly. 9 | * If you find a bug, feel free to file an issue and we will review it and tag it accordingly. 10 | 11 | ## Discussing features 12 | 13 | We would like the design for all features to get socialized via github issues. Please do this before you implement something so that we can all get on the same page of the feature and not waste your time, which we really appreciate. 14 | 15 | ## FAQ 16 | 17 | ### What's the difference between a feature and a bug? 18 | _Any bug fix which is a significant code contribution (say more than 10 lines of code), which introduces new APIs or which introduces breaking changes will be treated in the same way as we treat a [feature](#feature)._ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 pjsteam 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # p-j-s ![build status](https://travis-ci.org/pjsteam/pjs.svg?branch=dev) 2 | 3 | A library to parallelize `map`, `filter` and `reduce` operations on typed arrays through the use of Web Workers. 4 | 5 | ## Installing 6 | ``` 7 | npm i p-j-s 8 | ``` 9 | 10 | ## Usage 11 | It's as simple as: 12 | ```js 13 | var pjs = require('p-j-s'); 14 | 15 | pjs.init({ maxWorkers: 4 }); // initialize the library 16 | 17 | pjs(new Uint32Array([1,2,3,4])) 18 | .filter(function(e){ 19 | return e % 2 === 0; 20 | }).map(function(e){ 21 | return e * 2; 22 | }).seq().then(function (result){ 23 | // result is [4,8] a new Uint32Array 24 | 25 | // if we are not using the library any more cleanup once we are done 26 | pjs.terminate(); 27 | }); 28 | ``` 29 | 30 | ## Operations 31 | 32 | ### `map(mapper)` 33 | The `map` operation invokes the `mapper` function for each element of the wrapped `TypedArray`. It produces a new array of the same type where each element is the result of the `mapper` function. 34 | 35 | ### `filter(predicate)` 36 | The `filter` operation invokes the `predicate` function for each element of the wrapped `TypedArray`. It produces a new array of the same type which only includes the original elements for which the `predicate` function returns `true` (or a _truthy_ value). 37 | 38 | ### `reduce(reducer, seed[, identityReducer], identity)` 39 | The `reduce` operation invokes the `reducer` function for each element of the wrapped `TypedArray` passing the value of the previous invocation as the first parameter (`current`) and the element as the second one. 40 | 41 | The reduction is first performed in the Web Workers using `identity` as the intial value for `current`. The results from the Web Workers are collected and a new reduction is performed on them using `seed` and `identityReducer` function. 42 | 43 | ## Documentation 44 | You can find out more by checking out the [complete API](https://github.com/pjsteam/pjs/wiki/API) and the How Tos in our [wiki](https://github.com/pjsteam/pjs/wiki). 45 | 46 | ## Acknowledgements 47 | * Using [this great seed](https://github.com/mgonto/gulp-browserify-library-seed) project from [@mgonto](https://twitter.com/mgonto). 48 | * [@mraleph](https://twitter.com/mraleph) for [IR Hydra](https://github.com/mraleph/irhydra) and the help he provided to use it. 49 | -------------------------------------------------------------------------------- /docs/helpers/3.1.3/par_seq_bench.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/docs/helpers/3.1.3/par_seq_bench.xlsx -------------------------------------------------------------------------------- /docs/helpers/benchmark_sample/v8-artifacts/code-60612-1.asm: -------------------------------------------------------------------------------- 1 | --- FUNCTION SOURCE (between) id{0,0} --- 2 | (a, b, c){ 3 | return a <= b && b <= c; 4 | } 5 | 6 | --- END --- 7 | --- FUNCTION SOURCE () id{1,0} --- 8 | 9 | function between(a, b, c){ 10 | return a <= b && b <= c; 11 | } 12 | var a = Math.round(Math.random()*100); 13 | var b = Math.round(Math.random()*100) + a; 14 | var c = Math.round(Math.random()*100) + b; 15 | var d; 16 | //end setup 17 | //start benchmark 18 | console.time('between'); 19 | for (var i = 0; i < 1000000; i++){ 20 | d = between(a, b, c); 21 | } 22 | 23 | console.timeEnd('between'); 24 | 25 | --- END --- 26 | --- FUNCTION SOURCE (between) id{1,1} --- 27 | (a, b, c){ 28 | return a <= b && b <= c; 29 | } 30 | 31 | --- END --- 32 | INLINE (between) id{1,1} AS 1 AT <0:309> 33 | -------------------------------------------------------------------------------- /docs/helpers/benchmark_sample/v8-artifacts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | -------------------------------------------------------------------------------- /docs/paper/.gitignore: -------------------------------------------------------------------------------- 1 | *.aux 2 | *.fdb_latexmk 3 | *.log 4 | *.pdf 5 | *.synctex.gz 6 | *.bbl 7 | *.blg 8 | *.DS_Store -------------------------------------------------------------------------------- /docs/paper/comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/docs/paper/comparison.png -------------------------------------------------------------------------------- /docs/paper/context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/docs/paper/context.png -------------------------------------------------------------------------------- /docs/paper/paper.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/docs/paper/paper.pdf -------------------------------------------------------------------------------- /docs/presentation/deck.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/docs/presentation/deck.key -------------------------------------------------------------------------------- /docs/presentation/deck.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/docs/presentation/deck.pdf -------------------------------------------------------------------------------- /docs/presentation/deck.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/docs/presentation/deck.pptx -------------------------------------------------------------------------------- /docs/presentation/images/context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/docs/presentation/images/context.png -------------------------------------------------------------------------------- /docs/presentation/images/cpuscaling.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/docs/presentation/images/cpuscaling.jpg -------------------------------------------------------------------------------- /docs/presentation/images/no_shared_chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/docs/presentation/images/no_shared_chart.png -------------------------------------------------------------------------------- /docs/presentation/images/shared_chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/docs/presentation/images/shared_chart.png -------------------------------------------------------------------------------- /docs/report/.gitignore: -------------------------------------------------------------------------------- 1 | *.aux 2 | *.fdb_latexmk 3 | *.log 4 | *.pdf 5 | *.synctex.gz 6 | *.bbl 7 | *.blg -------------------------------------------------------------------------------- /docs/report/array_merging.tex: -------------------------------------------------------------------------------- 1 | \section{Array merging} 2 | 3 | Once all the workers have executed the \tfunction{} provided to them on each element of their portion of the original array, they are to notify the UI thread and provide the resulting \tabuffer{} or \tsabuffer{}. 4 | 5 | At this point the UI thread must consolidate the results from the Web Workers into a new \ttarray{} or \tstarray{} that represents the result for the entire operation.\footnote{The case for chains with \code{reduce} as the last step similar and simpler. Thus, it is not worth analyzing.} 6 | 7 | For the \tabuffer{} case, this means copying all the elements of each partial result into a larger \ttarray{} while maintaining the original order. 8 | 9 | For the \tsabuffer{} case there are two possible situations: 10 | \begin{enumerate} 11 | \item If all steps in the chain perform the \code{map} transformation, then no changes are required. 12 | \item If there is at least one \code{filter} step, the elements need to be placed at the beginning of the resulting \tstarray{} in order to avoid \textit{holes}. 13 | \end{enumerate} 14 | 15 | The latter is similar to the \tabuffer{} case, but instead of copying from one array to another, the source and target are the same. 16 | 17 | \subsection{\ttarray{} merge benchmark} 18 | Unlike the case for \ttarray{} splitting (Section~\ref{subsec:copying-typed-arrays}) there aren't that many native alternatives to create a \ttarray{} from smaller ones. 19 | 20 | The benchmark presented only has one alternative that uses a method with a native implementation: 21 | \bmcode{http://jsperf.com/typedarray-merge/2}{../../spikes/2.4.1/merge.js} 22 | 23 | The results for the benchmark are displayed in Figure~\ref{fig:typed-array-merge}. 24 | \img{Comparing \ttarray{} merge approaches}{../../spikes/2.4.1/merge}{fig:typed-array-merge} 25 | 26 | \subsection{\tstarray{} merge benchmark} 27 | 28 | The benchmark modifies the \ttarray{} merge benchmark to support instances of the \tstarray{} type. The implementation does not include the \dataview{} alternative as it is not supported by \tstarray{}s: 29 | \bmcode{http://jsperf.com/sharedtypedarray-merge}{../../spikes/2.4.1/sharedMerge.js} 30 | 31 | The results for the benchmark are displayed in Figure~\ref{fig:shared-typed-array-merge}. 32 | \img{Comparing \tstarray{} merge approaches}{../../spikes/2.4.1/sharedMerge}{fig:shared-typed-array-merge} 33 | 34 | \subsection{Conclusion} 35 | The benchmark shows that the best alternative to merge small arrays into a larger one is using \code{Set} method of either \ttarray{} or \tstarray{}. 36 | 37 | For illustration purposes, a naive implementation (without optimizations or error checking) would be similar to the following one: 38 | \begin{lstlisting}[caption=Simple \ttarray{} merge function] 39 | function merge(arrays){ 40 | var first = arrays[0]; 41 | var total = arrays.reduce(function(c,a) { return c + a.length; }, 0); 42 | var result = new first.constructor(total); 43 | var start = 0; 44 | 45 | arrays.forEach(function(a){ 46 | result.set(array, start); 47 | start += array.length; 48 | }); 49 | 50 | return result; 51 | } 52 | \end{lstlisting} 53 | 54 | \pagebreak -------------------------------------------------------------------------------- /docs/report/array_partitioning.tex: -------------------------------------------------------------------------------- 1 | \section{Array partitioning} 2 | 3 | As explained in Section~\ref{sec:serialization-and-transference} of this document, transferring \ttarray{} objects to and from Web Workers requires the use of \code{Transferable}s to achieve acceptable performance. Since our library will receive \ttarray{} objects to be partitioned and handed over to multiple workers, it is important that it spends as little time as possible partitioning the arrays.\footnote{This does not apply to \tstarray{}s as those can be transferred without being partitioned.} 4 | 5 | If the library is configured to use \(N\) workers then the original data in the \ttarray{} will copied into \(N\) smaller \ttarray{}s. For ideal performance it would be nice to be able to pass a \textit{pointer} into the array and an amount of elements to process, but because \ttarray{}s do not support shared memory, and element ownership is tranferred to the Web Workers, the elements must be copied to the sub-arrays. 6 | 7 | \subsection{Copying \ttarray{}s} 8 | \label{subsec:copying-typed-arrays} 9 | Sections 22.2 and 24.1 of the ECMAScript 6 draft \cite{es6} specify the API for \ttarray{} and \tabuffer{} objects respectively. Some of the exposed operations provide a way to copy the contents of an \tabuffer{} into another \tabuffer{}, so they are to be considered as viable alternatives for the copy operation. Other alternatives to consider are: 10 | \begin{itemize} 11 | \item Implementing a non-native function that copies the array elements one by one. 12 | \item Using a \code{Blob} to store the contents of a \ttarray{} and reading the \code{Blob}'s contents into a new \ttarray{}. 13 | \end{itemize} 14 | 15 | \subsubsection{\ttarray{} copy benchmark} 16 | A benchmark was created based on the aforementioned alternatives: 17 | \bmcode{http://jsperf.com/arraybuffer-copy/4}{../../spikes/2.3.1/copyComparison.js} 18 | 19 | The results for it are displayed in Figure~\ref{fig:typed-array-copy}. 20 | \img{Comparing \ttarray{} copy approaches}{../../spikes/2.3.1/copyComparison}{fig:typed-array-copy} 21 | 22 | Based on those results we can discard the \code{Blob} approach for partitioning \ttarray{}s. 23 | 24 | \note{Alternatives \textit{Buffer subarray} and \textit{Constructor} do the same thing in the case of copying an entire \ttarray{}. However, \textit{Constructor} is not a viable alternative for copying part of an array} 25 | 26 | \subsubsection{\ttarray{} partition benchmark} 27 | Based on the result from the previous benchmark we created a new benchmark to understand which alternative is better to partition an array: 28 | \bmcode{http://jsperf.com/arraybuffer-split/3}{../../spikes/2.3.1/splitComparison.js} 29 | 30 | The results for it are displayed in Figure~\ref{fig:typed-array-split}. 31 | \img{Comparing \ttarray{} partition approaches}{../../spikes/2.3.1/splitComparison}{fig:typed-array-split} 32 | 33 | \subsubsection{Conclusion} 34 | The benchmark shows that there are three very similar alternatives to partition a \ttarray{}. We decided to use \textit{Buffer subarray}. The implementation of the slice function could look something like this: 35 | \begin{lstlisting}[caption=Possible approach to copy a slice of a \ttarray{}] 36 | function typedArraySlice(array, from, to) { 37 | return new array.constructor(array.subarray(from, to)); 38 | } 39 | \end{lstlisting} 40 | 41 | \pagebreak -------------------------------------------------------------------------------- /docs/report/background.tex: -------------------------------------------------------------------------------- 1 | \section{Background}\label{sec:background} 2 | All modern versions of major browsers (Internet Explorer, Mozilla Firefox, Google Chrome, Safari and Opera) allow the execution of JavaScript code in a single threaded event loop runtime \cite{event-loop}. User actions add elements to the event loop queue and these are processed sequentially. All process input/output is asynchronous to prevent the UI thread from blocking, thus keeping it responsive. 3 | While such a runtime is a good fit for most web applications, JavaScript and the browser are now also being used for some CPU intensive tasks, such as games, which commonly require physics calculations, image manipulation (in 2D) and graphics generation (in 3D). Despite the large improvement in JavaScript's performance, obtained through optimizing compilers such as Google Chrome's V8 \cite{v8} and Mozilla Firefox's SpiderMonkey \cite{spider-monkey}, it is important to be able to take advantage of multiple cores in modern devices to achieve even greater performance.\\ 4 | In 2010, Web Workers were made part of the web standard. Web Workers allow the creation of ``thread like'' constructs in a browser environment, but they don't allow shared memory and instead communicate via message passing. The message passing overhead is very big for common objects and Web Workers ``have a high start-up performance cost'' \cite{w3c-ww-startup}, so they were commonly considered for long running tasks and operating on generally the same set of data during a single execution, making them unfit for a thread pool model \cite{thread-pool}.\\ 5 | However, a spec is being developed for version 7 of the EcmaScript standard that changes this, by introducing shared memory through \tsabuffer{}s \cite{sab}. In essence, the proposal allows the same memory to be shared across multiple Web Workers, and also aims to provide the necessary atomic/lock constructs to deal with shared memory. In the particular case of embarrassingly parallel computations (such as map, filter and reduce operations on an array), it is simple to take advantage of the speed benefits of shared memory without incurring in any overhead due to synchronization. 6 | 7 | We developed our library to work with instances of the already standarized \tabuffer{} type using the Google Chrome broswer. Additionally, we support the experimental \tsabuffer{}, which at the time of writing is only available in Firefox Nightly\footnote{Current version is 41.0a1}. 8 | 9 | \pagebreak -------------------------------------------------------------------------------- /docs/report/commands.tex: -------------------------------------------------------------------------------- 1 | \newcommand{\bmcode}[2] { 2 | \lstinputlisting[caption=Benchmark at #1, escapechar=]{#2} 3 | } 4 | 5 | \newcommand{\img}[3] { 6 | \begin{center} 7 | \includegraphics[width=1\textwidth]{#2} 8 | \captionof{figure}{#1}\label{#3} 9 | \end{center} 10 | } 11 | 12 | \newcommand\todo[1]{\textcolor{red}{#1}} 13 | 14 | \newcommand{\note}[1]{ 15 | \fbox{ 16 | \parbox{0.9\textwidth}{ 17 | \textbf{Note:} #1 18 | } 19 | } 20 | } 21 | 22 | \newcommand{\code}[1]{\texttt{#1}} 23 | \newcommand{\tstring}[0]{\code{String}} 24 | \newcommand{\tabuffer}[0]{\code{ArrayBuffer}} 25 | \newcommand{\tfunction}[0]{\code{Function}} 26 | \newcommand{\ttarray}[0]{\code{TypedArray}} 27 | \newcommand{\tsabuffer}[0]{\code{SharedArrayBuffer}} 28 | \newcommand{\tstarray}[0]{\code{SharedTypedArray}} 29 | \newcommand{\dataview}[0]{\code{DataView}} -------------------------------------------------------------------------------- /docs/report/function_caching.tex: -------------------------------------------------------------------------------- 1 | \section{Function caching}\label{sec:func-caching} 2 | 3 | Before comparing the sequential implementation of \code{map} against the parallel one, we believed that the cost of dynamically instantiating a \tfunction{} from a \tstring{} in the Web Workers could be avoided by introducing a cache for cases where the Web Worker has already seen the code for a particular function. 4 | 5 | We decided to introduce a simple key/value map acting as a cache where the keys are \tstring{}s with the function's code and the values are the \tfunction{} objects. 6 | 7 | To verify its performance we created a benchmark that runs both with and without the function cache: 8 | \bmcode{http://jsperf.com/p-j-s-with-vs-without-function-cache/4}{../../spikes/3.1.3/functionCache/functionCache.js} 9 | 10 | The results for the benchmark are displayed in Figure~\ref{fig:function-cache-result}. 11 | \img{Comparing p-j-s \code{map} with and without a function cache}{../../spikes/3.1.3/functionCache/functionCache}{fig:function-cache-result} 12 | 13 | \subsection{Conclusion} 14 | As the benchmark shows, there is almost no difference in versions 40 and 41 of Google Chrome between the implementations with and without the cache. To determine whether introducing the cache was worth it or not we considered the result from Firefox Nightly, which shows a big increase in performance (near 25\%). That is why we decided to make the \tfunction{} cache a part of our implementation. 15 | 16 | \pagebreak -------------------------------------------------------------------------------- /docs/report/function_transfer_time.tex: -------------------------------------------------------------------------------- 1 | \section{Decreasing function transfer time}\label{sec:function-transfer-time} 2 | 3 | Each time \code{seq} is invoked on a chain, all callback functions related to \code{map}, \code{filter} and \code{reduce} steps are serialized and sent to the Web Workers. 4 | 5 | Each Web Worker has a cache to avoid using different instances of the same function (as explained in Section~\ref{sec:func-caching}). Nevertheless, if the same function is going to be used multiple times in a program as the callback for steps, it might make sense to send it to the Web Workers once and then simply make a reference to it. 6 | 7 | This can be achieved by taking advantage of the global context object (introduced in Section~\ref{sec:context}). 8 | 9 | The first thing that needs to be done is to call \code{pjs.updateContext} by providing a \tfunction{} as a property of the context object. An example is shown in the following code snippet: 10 | \begin{lstlisting}[caption=Sending function in global context] 11 | var promise = pjs.updateContext({ 12 | add: function (x) { return x + 2; }, 13 | }); 14 | \end{lstlisting} 15 | 16 | Once the global context has been updated, the \tstring{} representation of the function's key (\code{'add'}) can be used as a callback parameter instead of using a \tfunction{} instance. An example is shown in the following code snippet: 17 | \begin{lstlisting}[caption=Passing a function name from the global context instead of a \tfunction{} as a callback] 18 | var promise = pjs.updateContext({ 19 | add: function (x) { return x + 2; }, 20 | }).then(function(){ 21 | pjs(new Uint8Array([1,2,3,4])).map('add').seq() 22 | .then(function(result){ 23 | // result is [ 3, 4, 5, 6 ] 24 | }); 25 | }); 26 | \end{lstlisting} 27 | 28 | \subsection{Benchmark} 29 | We created a benchmark to analyze the improvement obtained by just sending a \tstring{} instead of a \tfunction{} instance. 30 | 31 | The following listing shows the code for the benchmark: 32 | \bmcode{http://jsperf.com/global-ctx-inlined-func/3}{../../spikes/6.2/inlinedVsGlobalInlined/jspBenchmark.js} 33 | 34 | The results for the benchmark are displayed in Figure~\ref{fig:func-key-vs-instance}. 35 | \img{Comparing sending function key in global context against \tfunction{} instance}{../../spikes/6.2/inlinedVsGlobalInlined/result}{fig:func-key-vs-instance} 36 | 37 | \subsection{Conclusion} 38 | While the performance improvement obtained is not remarkable, it is still noticeable and does not require a lot of effort from a developer's standpoint. It is worth pointing out that the improvement shown in Figure~\ref{fig:func-key-vs-instance} will vary if the difference in character length between the key and the function's code does. 39 | 40 | \pagebreak -------------------------------------------------------------------------------- /docs/report/img/naive_benchmark_ir_hydra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/docs/report/img/naive_benchmark_ir_hydra.png -------------------------------------------------------------------------------- /docs/report/initialization.tex: -------------------------------------------------------------------------------- 1 | \section{Initialization}\label{sec:initialization} 2 | 3 | As explained in the Web Workers standard \cite{w3c-ww-startup} \textit{``Generally, workers are expected to be long-lived, have a high start-up performance cost, and a high per-instance memory cost."}. 4 | 5 | There are two factors to consider from the above sentence regarding the library's implementation: 6 | 7 | \begin{description} 8 | \item[First] The high start-up time. 9 | \item[Second] The high per-instance memory cost. 10 | \end{description} 11 | 12 | \subsection{High start-up time} 13 | This factor raises the issue of when workers should be instantiated. On relation to this, we performed a benchmark to get an idea of the difference in time taken to process messages. One scenario considers cold starting a Web Worker, and the other one having a Web Worker already available. 14 | 15 | \subsubsection{Cold start benchmark} 16 | The following is the code for the benchmark: 17 | \bmcode{http://jsperf.com/worker-cold-start/6}{../../spikes/2.1.1/newVsPreExistent.js} 18 | 19 | The results for it are displayed in Figure~\ref{fig:new-vs-pre}. 20 | \img{Evaluating the inefficiency of cold starting a Web Worker}{../../spikes/2.1.1/newVsPreExistent}{fig:new-vs-pre} 21 | 22 | \subsubsection{Conclusion} 23 | The previous benchmark shows a huge difference between the time required to process a message on a pre-existent Web Worker compared to the one necessary to start a Web Worker and process a message. For that reason, we decided to create a pool of Web Workers when the library is initialized and dispatch work to them when it arrives. Since operations are asynchronous, if an operation is executing when another one is added for processing, the latter will be added to a queue and processed when the former completes. 24 | 25 | \subsection{High per-instance memory cost} 26 | The second factor makes it important for users to be able to control/limit the amount of web workers that the library instantiates. For that reason the library allows users to specify a maximum amount of workers to be instantiated, and also provide through its API a way to terminate all workers in existence. 27 | 28 | \subsection{Maximum amount of workers} 29 | Since the work being done is CPU intensive (i.e.: the workers' goal is not to perform I/O operations), the maximum amount of workers to be created should not be greater than the amount of cores in the machine \(W_{cores}\), otherwise interleaving would start to affect performance. Additionally, considering the necessity of users to control how many workers are created, an upper bound can be specified \(W_{upper bound}\)\footnote{If the executing browser does not have an API to detect the amount of cores in a device \(W_{cores}=W_{upper bound}\). If no \(W_{upper bound}\) is provided, \(W_{cores}=1\).}. If no upper bound is specified, the user upper bound for workers is assumed to be infinite (because the user is not providing a restriction). To summarize, the number of workers to be instantiated is: 30 | \[W = min(W_{cores}, W_{upper bound})\] 31 | 32 | \pagebreak -------------------------------------------------------------------------------- /docs/report/intro.tex: -------------------------------------------------------------------------------- 1 | \section{Introduction} 2 | In 1965 Gordon Moore proposed the popularly known ``Moore's law" \cite{moore65}, which predicts that the number of transistors in integrated circuits will double every two years. 3 | As a consequence, programs that use a single processor/core will automatically become faster without the need to modify them with that purpose in mind. 4 | 5 | In the year 2010 it was predicted that this tendency would be slowly reaching its end, mostly due to reasons related to heat dissipation. This is the reason why newer computers have a greater number of processors instead of processors with more computing power. 6 | 7 | To be able to maximize the performance of these computers, it is paramount that systems professionals can create programs that process data in parallel, through different mechanisms, such as threads or processes executing simultaneously in different processors. In the case of applications executed on a single device (e.g. browsers and web pages, applications for mobile devices, desktop apps, server applications) a lot of languages and platforms provide a simple way for programmers to abstract the complexity and coordination required for this type of processing. For example, the .NET platform has the Task Parallel Library (TPL) and Parallel LINQ (PLINQ) \cite{ms-par} libraries that use algorithmic skeletons modelled with high order functions so developers can perform complex operations through a simple API. 8 | 9 | On a different note, one of the tendencies that is beginning to take off is the use of EcmaScript \cite{es-web} (also known as Javascript) to create applications, such as video games, that were previously only considered viable in a native environment. This has been possible thanks to components like asm.js \cite{asm-web} and WebGL \cite{webgl-web} together with the constant evolution of browsers and JavaScript runtime engines. 10 | In this context, one of the plans of the committee that develops the language is to provide an API to simplify the processing of data in parallel for version 7 (ES7) of the language, with the goal of keeping up with native applications. Initiatives such as ParallelJS \cite{par-js} and River trail \cite{rivertrail} or the possibility of taking advantage of SIMD instructions \cite{js-simd} (single instruction multiple data) are some of the options to implement this. 11 | 12 | The goal of this paper is to provide an alternative for parallel code execution in EcmaScript, through a library that exposes high order functions to model algorithmic skeletons such as map, filter and reduce. This library will take advantage of Web Workers \cite{w3c-ww} (the mechanism proposed by browsers for parallelism at the time of writing) to allow the simultaneous execution of code in multiple processors. 13 | 14 | \pagebreak -------------------------------------------------------------------------------- /docs/report/js_lang.tex: -------------------------------------------------------------------------------- 1 | \usepackage{listings} 2 | \usepackage{color} 3 | \definecolor{lightgray}{rgb}{.9,.9,.9} 4 | \definecolor{darkgray}{rgb}{.4,.4,.4} 5 | \definecolor{purple}{rgb}{0.65, 0.12, 0.82} 6 | 7 | \lstdefinelanguage{JavaScript}{ 8 | keywords={break, case, catch, continue, debugger, default, delete, do, else, finally, for, function, if, in, instanceof, new, return, switch, this, throw, try, typeof, var, void, while, with}, 9 | keywordstyle=\color{blue}\bfseries, 10 | ndkeywords={class, export, boolean, throw, implements, import, this}, 11 | ndkeywordstyle=\color{darkgray}\bfseries, 12 | identifierstyle=\color{black}, 13 | sensitive=false, 14 | comment=[l]{//}, 15 | morecomment=[s]{/*}{*/}, 16 | commentstyle=\color{purple}\ttfamily, 17 | stringstyle=\color{red}\ttfamily, 18 | morestring=[b]', 19 | morestring=[b]" 20 | } 21 | 22 | \lstset{ 23 | language=JavaScript, 24 | backgroundcolor=\color{lightgray}, 25 | extendedchars=true, 26 | basicstyle=\footnotesize\ttfamily, 27 | showstringspaces=false, 28 | showspaces=false, 29 | numbers=left, 30 | numberstyle=\footnotesize, 31 | numbersep=9pt, 32 | tabsize=2, 33 | breaklines=true, 34 | showtabs=false, 35 | captionpos=b 36 | } -------------------------------------------------------------------------------- /docs/report/main.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/docs/report/main.pdf -------------------------------------------------------------------------------- /docs/report/main.tex: -------------------------------------------------------------------------------- 1 | \documentclass[12pt]{article} 2 | \usepackage{upquote} 3 | \usepackage{lingmacros} 4 | \usepackage{tree-dvips} 5 | \usepackage{cite} 6 | \usepackage{graphicx} 7 | \usepackage{amsmath} 8 | \usepackage{caption} 9 | \input{js_lang.tex} 10 | \input{commands.tex} 11 | \begin{document} 12 | \graphicspath{{./img/}} 13 | \input{title.tex} 14 | 15 | \input{intro.tex} 16 | 17 | \input{background.tex} 18 | 19 | \input{overview.tex} 20 | 21 | \input{micro_benchmarks.tex} 22 | 23 | \input{initialization.tex} 24 | 25 | \input{serialization_transference.tex} 26 | 27 | \input{code_sharing.tex} 28 | 29 | \input{context.tex} 30 | 31 | \input{array_partitioning.tex} 32 | 33 | \input{array_merging.tex} 34 | 35 | \input{function_transfer_time.tex} 36 | 37 | \input{inlining.tex} 38 | 39 | \input{function_caching.tex} 40 | 41 | \input{map_serial_vs_parallel.tex} 42 | 43 | \input{modules.tex} 44 | 45 | \bibliography{main.bib}{} 46 | \bibliographystyle{IEEEtran} 47 | \end{document} -------------------------------------------------------------------------------- /docs/report/map_serial_vs_parallel.tex: -------------------------------------------------------------------------------- 1 | \section{\code{map}: Sequential vs parallel} 2 | 3 | Since as explained in Section~\ref{sec:serialization-and-transference} the cost of transferring objects is not zero, it is important to compare the performance of our parallel implementation against a serial one. 4 | 5 | In this regard, the more complex the computation to be performed on each array element, the better the parallel implementation will perform. That is because, in the parallel case, the source \ttarray{} must be copied to sub-arrays in order to be transferred. On the contrary, the serial implementation operates on elements during that time. To observe the effects of this difference, we also perform the same benchmark using \tstarray{}s. 6 | 7 | \subsection{Algorithm selection} 8 | For this benchmark we decided to use an algorithm that applies a sepia tone effect to pictures. This meets the criteria of both being useful and having some calls to \code{Math.random} which make it more computationally complex. 9 | 10 | \subsection{TypedArray Benchmark} 11 | The benchmark code is introduced in the following code listing: 12 | \bmcode{http://jsperf.com/pjs-map-vs-serial/8}{../../spikes/3.1.3/comparison/jspBenchmark.js} 13 | 14 | \note{Unlike other benchmarks in which we were comparing different ways to achieve the same result, this benchmark compares a single way with a varying amount of elements. For that reason, the original charts are not displayed.} 15 | 16 | The results for the benchmark are displayed in Figure~\ref{fig:map-par-vs-seq}. 17 | \img{Relative performance of parallel implementation with regards to sequential one using \tabuffer{}s}{../../spikes/3.1.3/comparison/chart}{fig:map-par-vs-seq} 18 | 19 | \subsection{SharedTypedArray Benchmark} 20 | The benchmark code is introduced in the following code listing: 21 | \bmcode{http://jsperf.com/pjs-map-vs-serial/9}{../../spikes/3.1.3/comparison/jspBenchmarkShared.js} 22 | 23 | The results for the benchmark are displayed in Figure~\ref{fig:map-par-vs-seq-shared}. 24 | \img{Relative performance of parallel implementation with regards to sequential one using \tsabuffer{}s}{../../spikes/3.1.3/comparison/chartShared}{fig:map-par-vs-seq-shared} 25 | 26 | \subsection{Conclusion} 27 | A couple of interesting findings result from the experiments. An expected one is that the amount of operations per second that can be performed with a serial implementation is almost inversely proportional to the amount of items to transform (with a ratio of 1). On the other hand, this is not the case for the parallel approach. 28 | 29 | An acceptable speedup of approximately \textbf{2.4x} can be achieved with a relatively large amount of items (\(10E6\)) and performing a computationally expensive operation (generating pseudo-random numbers). Larger speedups might be possible by applying optimizations to the library and there might also be some potential improvements for operations with \tsabuffer{}s as they are likely not fully optimized, being that they are only an experimental feature in Firefox Nightly. 30 | 31 | The speedup obtained without shared memory is relative small (approximately \textbf{1.2x}) when compared to that obtained with shared memory. That can be explained by the overhead of copying array elements, to work around the lack of shared memory, when communicating with the Web Workers. 32 | 33 | \pagebreak -------------------------------------------------------------------------------- /docs/report/modules.tex: -------------------------------------------------------------------------------- 1 | \section{Modules system} 2 | 3 | \note{At the time of writing, neither Google Chrome nor Mozilla Firefox support the ES6 module specification so that is not a viable choice.} 4 | 5 | The two most known module formats/API in JavaScript are: 6 | \begin{description} 7 | \item[CommonJS] Recognized for being used in node.js~\cite{nodejs}. 8 | \item[AMD~\cite{amd}] The most known implementation being require.js~\cite{requirejs}. 9 | \end{description} 10 | 11 | CommonJS has many similar aspects with the ES6 module specification, although it does not provide a way to load modules asynchronously. AMD is very different from the ES6 module specification but does support asynchronous module loading which is useful for browser applications. 12 | 13 | Instead of making the choice for users, our library will support both systems in a way that makes it easy for users to consume either of them. 14 | 15 | \subsection{CommonJS support} 16 | All that needs to be done to use the library is shown in the following code snippet: 17 | \begin{lstlisting}[language=HTML, caption=CommonJS usage] 18 | 19 | 20 | 26 | \end{lstlisting} 27 | 28 | \subsection{AMD support} 29 | This example shows how to use the library with require.js. It assumes a folder structure like the one in the following listing: 30 | \begin{lstlisting}[language=bash, caption=AMD folder structure] 31 | . 32 | |__ index.html 33 | |__ scripts 34 | |__ main.js 35 | |__ require.js 36 | \end{lstlisting} 37 | 38 | The file \textbf{main.js} must have the following contents: 39 | \begin{lstlisting}[caption=main.js AMD support] 40 | require(["https://rawgit.com/pjsteam/pjs/v1.0.0-beta/dist/p-j-s-standalone.min.js"], function(pjs) { 41 | pjs.init(); 42 | 43 | /* use below */ 44 | }); 45 | \end{lstlisting} 46 | 47 | \subsection{Conclusion} 48 | As the previous examples show, using our library with either module system is really simple and is done in a way that should feel familiar for users of those systems. 49 | 50 | \pagebreak -------------------------------------------------------------------------------- /docs/report/overview.tex: -------------------------------------------------------------------------------- 1 | \section{Library Overview}\label{sec:overview} 2 | JavaScript developers are familiar with the concept of map, filter and reduce skeletons as language constructs. In JavaScript, \code{Array}s have \code{map}, \code{filter}, and \code{reduce} functions and version 6 of the EcmaScript standard adds those same methods to \ttarray{}s \cite{es6}. For example, to multiply all the elements of an \code{Array} by two: 3 | \begin{lstlisting}[caption=Example creating a new array with the values of the original one multiplied by two] 4 | var newValues = [1,2,3,4].map(function(e){ 5 | return e * 2; 6 | }); 7 | 8 | // newValues is a new array [2,4,6,8] 9 | \end{lstlisting} 10 | 11 | Our library (p-j-s) only supports \tstarray{}s and \ttarray{}s due to performance reasons discussed in Section~\ref{sec:serialization-and-transference}. It aims to provide a similar interface while not changing the native object's prototypes and dealing with the asynchronous nature of the operation. Given a \tstarray{}, the following code would do the same as the previous example but parallelizing the work: 12 | \begin{lstlisting}[caption=Example creating a new array with the values of the original one multiplied by two using p-j-s] 13 | var pjs = require('p-j-s'); 14 | pjs.init({ maxWorkers: 4 }); 15 | var xs = new SharedUint8Array(4); 16 | xs.set([1,2,3,4]); 17 | pjs(xs).map(function(e){ 18 | return e * 2; 19 | }).seq().then(function(newValues){ 20 | // newValues is a new SharedUint8Array [2,4,6,8] 21 | }); 22 | \end{lstlisting} 23 | 24 | The call to \code{pjs.init} initializes the library and the amount of workers to be used for processing (a worker pool is created with them).\footnote{More details in Section~\ref{sec:initialization}.} 25 | 26 | The API provided by p-j-s \cite{pjs-api} is very similar to the synchronous one that is provided by the language. The asynchronous nature of the operation is reflected by the fact that the call to \code{seq} returns a \code{Promise} \cite{promise} (alternatively a callback function could be passed as a parameter to it). 27 | 28 | It is possible to chain multiple operations before calling \code{seq} to avoid passing data back and forth between the Web Workers and the UI thread. Before \code{seq} is invoked, an operation is a chain of one or more steps. From the same main chain, multiple chains could be created: 29 | 30 | \begin{lstlisting}[caption=Example creating multiple chains from the same base chain] 31 | var xs = new SharedUint8Array(4); 32 | xs.set([1,2,3,4]); 33 | 34 | var chain = pjs(xs).map(function(e){ 35 | return e * 3; 36 | }); 37 | 38 | var evenChain = chain.filter(function(e){ 39 | return e % 2 === 0; 40 | }); 41 | 42 | var sumChain = chain.reduce(function(c, v){ 43 | return c + v; 44 | }, 0, 0); 45 | 46 | evenChain.seq().then(function(evens){ 47 | // evens is [6, 12] 48 | sumChain.seq().then(function(sum){ 49 | // sum is [3,6,9,12] 50 | }); 51 | }); 52 | \end{lstlisting} 53 | 54 | \pagebreak -------------------------------------------------------------------------------- /docs/report/title.tex: -------------------------------------------------------------------------------- 1 | \begin{titlepage} 2 | \begin{center} 3 | 4 | \textsc{\LARGE University of Buenos Aires}\\[1.5cm] 5 | 6 | \textsc{\Large Professional Assignment}\\[0.5cm] 7 | 8 | % Title 9 | { \huge \bfseries Using algorithmic skeletons in EcmaScript to parallelize computation in browsers through Web Workers \\[0.4cm] } 10 | 11 | % Author and supervisor 12 | \noindent 13 | 14 | \emph{Authors:}\\ 15 | Damian \textsc{Schenkelman}\\ 16 | Matias \textsc{Servetto}\\[0.5cm] 17 | 18 | \emph{Tutor:}\\ 19 | Prof.~Rosa \textsc{Wachenchauzer} 20 | 21 | \vfill 22 | 23 | % Bottom of the page 24 | {\large \today} 25 | 26 | \end{center} 27 | \end{titlepage} -------------------------------------------------------------------------------- /docs/uba/Proposal.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/docs/uba/Proposal.pdf -------------------------------------------------------------------------------- /examples/mapSample/README.md: -------------------------------------------------------------------------------- 1 | To run this test you should 2 | 3 | 1. Open terminal 4 | 2. Go to project root folder. 5 | 3. Run `npm run build` command. 6 | 7 | This will generate dist/ folder with `p-j-s.min.js` script which is used by mapSample's index.html file. -------------------------------------------------------------------------------- /examples/mapSample/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Map Sample 5 | 6 | 7 | 13 |
14 |
15 |
16 |

17 |

18 |

19 |

20 | 21 | -------------------------------------------------------------------------------- /examples/mapSample/scripts/main.js: -------------------------------------------------------------------------------- 1 | require(["https://rawgit.com/pjsteam/pjs/v0.2.1/dist/p-j-s-standalone.min.js"], function(pjs) { 2 | pjs.init(); 3 | function mapper (e) { 4 | return e + Math.floor(Math.random() * 10000) 5 | + Math.floor(Math.random() * 10000) 6 | + Math.floor(Math.random() * 10000) 7 | + Math.floor(Math.random() * 10000); 8 | }; 9 | 10 | function generateElements(count) { 11 | var xs = new Uint32Array(count); 12 | for (var i = 0; i < count; i++) { 13 | xs[i] = i; 14 | } 15 | return xs; 16 | }; 17 | 18 | function arrayToString(array, max) { 19 | max = max || array.length; 20 | var result = '['; 21 | for (var i = 0; i < max && i < array.length; i++) { 22 | if (0 === i) { 23 | result += array[i]; 24 | } else { 25 | result += ' , ' + array[i]; 26 | } 27 | } 28 | return result + ']'; 29 | }; 30 | 31 | var xs; 32 | 33 | function generate () { 34 | var count = document.getElementById("select").value; 35 | xs = generateElements(count); 36 | document.getElementById("xs").innerHTML = 'xs = ' + arrayToString(xs, 150); 37 | document.getElementById("mapper").innerHTML = 'mapper = ' + mapper.toString(); 38 | } 39 | 40 | function pjsmap () { 41 | initIfNeeded(); 42 | 43 | console.time('pjs'); 44 | var s = new Date(); 45 | pjs(xs).map(mapper, function (ys) { 46 | console.timeEnd('pjs'); 47 | finish(ys, s, 'pjs(xs).map'); 48 | }); 49 | }; 50 | 51 | function formap () { 52 | initIfNeeded(); 53 | 54 | console.time('for'); 55 | var s = new Date(); 56 | var l = xs.length; 57 | var ys = new Uint32Array(l); 58 | for (var i = 0; i < l; i++){ 59 | ys[i] = mapper(xs[i]); 60 | } 61 | console.timeEnd('for'); 62 | finish(ys, s, 'for map'); 63 | }; 64 | 65 | function initIfNeeded () { 66 | if (!xs) { 67 | generate(); 68 | } 69 | }; 70 | 71 | function finish(ys, s, m) { 72 | var e = new Date(); 73 | document.getElementById("ys").innerHTML = 'ys = ' + arrayToString(ys, 150); 74 | document.getElementById("time").innerHTML = m + ' time = ' + (e - s) + ' ms'; 75 | }; 76 | 77 | document.getElementById("generate").onclick = generate; 78 | document.getElementById("pjsmap").onclick = pjsmap; 79 | document.getElementById("formap").onclick = formap; 80 | }); -------------------------------------------------------------------------------- /examples/sapiaPicture/default-pjs.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | PJS Sample 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 |
14 | 15 |
16 |
17 | 18 |
19 |
Hit run
20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/sapiaPicture/default-pjs.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var run; 4 | 5 | (function () { 6 | var source = document.getElementById("source"); 7 | var runButton = document.getElementById("runButton"); 8 | var pjs = require('p-j-s'); 9 | pjs.init({ maxWorkers: 4 }); 10 | pjs.updateContext({ 11 | f:function(pixel){ 12 | // return pixel; 13 | var r = pixel & 0xFF; 14 | var g = (pixel & 0xFF00) >> 8; 15 | var b = (pixel & 0xFF0000) >> 16; 16 | var noiser = Math.random() * 0.5 + 0.5; 17 | var noiseg = Math.random() * 0.5 + 0.5; 18 | var noiseb = Math.random() * 0.5 + 0.5; 19 | 20 | var new_r = Math.max(Math.min(255, noiser * ((r * 0.393) + (g * 0.769) + (b * 0.189)) + (1 - noiser) * r), 0); 21 | var new_g = Math.max(Math.min(255, noiseg * ((r * 0.349) + (g * 0.686) + (b * 0.168)) + (1 - noiseg) * g), 0); 22 | var new_b = Math.max(Math.min(255, noiseb * ((r * 0.272) + (g * 0.534) + (b * 0.131)) + (1 - noiseb) * b), 0); 23 | 24 | return (pixel & 0xFF000000) + (new_b << 16) + (new_g << 8) + (new_r & 0xFF); 25 | } 26 | }).then(function(){ 27 | run = function(){ 28 | log.innerHTML = "Processing..."; 29 | runButton.style.visibility = "hidden"; 30 | var canvas = document.getElementById("target"); 31 | canvas.width = source.clientWidth; 32 | canvas.height = source.clientHeight; 33 | 34 | if (!canvas.getContext) { 35 | log.innerHTML = "Canvas not supported. Please install a HTML5 compatible browser."; 36 | return; 37 | } 38 | 39 | var tempContext = canvas.getContext("2d"); 40 | 41 | tempContext.drawImage(source, 0, 0, canvas.width, canvas.height); 42 | var canvasData = tempContext.getImageData(0, 0, canvas.width, canvas.height); 43 | var temp = new Uint8ClampedArray(canvasData.data.length); 44 | temp.set(canvasData.data); 45 | var copyData = new Uint32Array(temp.buffer); 46 | var start = new Date(); 47 | pjs(copyData).map('f').seq(function(err, result) { 48 | var diff = new Date() - start; 49 | canvasData.data.set(new Uint8ClampedArray(result.buffer)); 50 | tempContext.putImageData(canvasData, 0, 0); 51 | log.innerHTML = "Process done in " + diff + " ms"; 52 | runButton.style.visibility = "visible"; 53 | }); 54 | }; 55 | source.src = "pic.jpg"; 56 | }); 57 | })(); 58 | -------------------------------------------------------------------------------- /examples/sapiaPicture/default.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | width: 100%; 4 | margin: 0; 5 | padding: 0; 6 | } 7 | body { 8 | min-height: 100%; 9 | } 10 | 11 | #root { 12 | height: 100%; 13 | width: 100%; 14 | } 15 | 16 | #sourceDiv { 17 | position: absolute; 18 | left: 10px; 19 | top: 50px; 20 | } 21 | 22 | #targetDiv { 23 | position: absolute; 24 | right: 10px; 25 | top: 50px; 26 | } 27 | 28 | #source { 29 | } 30 | 31 | #target { 32 | } 33 | 34 | 35 | #log { 36 | position: absolute; 37 | width: 100%; 38 | text-shadow: 2px 2px 1px black; 39 | font-size: 28px; 40 | color: white; 41 | text-align: center; 42 | margin-top: 300px; 43 | } 44 | -------------------------------------------------------------------------------- /examples/sapiaPicture/defaultnoworker.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Serial Sample 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 |
14 | 15 |
16 |
17 | 18 |
19 |
Hit run
20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /examples/sapiaPicture/defaultnoworker.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var run; 4 | 5 | var processSepia = function (binaryData, l) { 6 | for (var i = 0; i < l; i += 4) { 7 | var r = binaryData[i]; 8 | var g = binaryData[i + 1]; 9 | var b = binaryData[i + 2]; 10 | 11 | var noise_r = Math.random() * 0.5 + 0.5; 12 | var noise_g = Math.random() * 0.5 + 0.5; 13 | var noise_b = Math.random() * 0.5 + 0.5; 14 | 15 | binaryData[i] = Math.max(Math.min(255, noise_r * ((r * 0.393) + (g * 0.769) + (b * 0.189)) + (1 - noise_r) * r), 0); 16 | binaryData[i + 1] = Math.max(Math.min(255, noise_g * ((r * 0.349) + (g * 0.686) + (b * 0.168)) + (1 - noise_g) * g), 0); 17 | binaryData[i + 2] = Math.max(Math.min(255, noise_b * ((r * 0.272) + (g * 0.534) + (b * 0.131)) + (1 - noise_b) * b), 0); 18 | } 19 | }; 20 | 21 | (function () { 22 | 23 | var source = document.getElementById("source"); 24 | var runButton = document.getElementById("runButton"); 25 | 26 | run = function () { 27 | 28 | log.innerHTML = "Processing..."; 29 | runButton.style.visibility = "hidden"; 30 | 31 | var canvas = document.getElementById("target"); 32 | canvas.width = source.clientWidth; 33 | canvas.height = source.clientHeight; 34 | 35 | if (!canvas.getContext) { 36 | log.innerHTML = "Canvas not supported. Please install a HTML5 compatible browser."; 37 | return; 38 | } 39 | 40 | var tempContext = canvas.getContext("2d"); 41 | var len = canvas.width * canvas.height * 4; 42 | 43 | tempContext.drawImage(source, 0, 0, canvas.width, canvas.height); 44 | 45 | var canvasData = tempContext.getImageData(0, 0, canvas.width, canvas.height); 46 | var binaryData = canvasData.data; 47 | var start = new Date(); 48 | processSepia(binaryData, len); 49 | var diff = new Date() - start; 50 | tempContext.putImageData(canvasData, 0, 0); 51 | log.innerHTML = "Process done in " + diff + " ms (no web workers)"; 52 | runButton.style.visibility = "visible"; 53 | }; 54 | 55 | source.src = "pic.jpg"; 56 | })(); 57 | -------------------------------------------------------------------------------- /examples/sapiaPicture/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sapiaPicture", 3 | "version": "0.0.0", 4 | "description": "The idea and some source code for this sample was taken from [here](http://blogs.msdn.com/b/eternalcoding/archive/2012/09/20/using-web-workers-to-improve-performance-of-image-manipulation.aspx)", 5 | "scripts": { 6 | "start": "./start.sh" 7 | }, 8 | "author": "", 9 | "license": "ISC", 10 | "devDependencies": { 11 | "serve": "^1.4.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/sapiaPicture/pic-1024x668.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/examples/sapiaPicture/pic-1024x668.jpg -------------------------------------------------------------------------------- /examples/sapiaPicture/pic-2448×3264.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/examples/sapiaPicture/pic-2448×3264.jpg -------------------------------------------------------------------------------- /examples/sapiaPicture/pic-300x300.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/examples/sapiaPicture/pic-300x300.jpg -------------------------------------------------------------------------------- /examples/sapiaPicture/pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/examples/sapiaPicture/pic.jpg -------------------------------------------------------------------------------- /examples/sapiaPicture/readme.md: -------------------------------------------------------------------------------- 1 | # Sepia Picture 2 | 3 | The idea and some source code for this sample was taken from [here](http://blogs.msdn.com/b/eternalcoding/archive/2012/09/20/using-web-workers-to-improve-performance-of-image-manipulation.aspx) 4 | 5 | To run the sample you must serve the **default*.html** file using a local server. Otherwise a CORS error is thrown when manipulating the image inside the canvas. 6 | 7 | ## Running the sample 8 | 1. Run `npm i && npm start`. 9 | 2. Open Chrome and browse to `http://127.0.0.1:3000/default-pjs.html`. 10 | 3. Click **Run**. 11 | 12 | ## Sample description 13 | 14 | * `defaultnoworker.html` executes the sample using web workers. 15 | * `default-pjs.html` executes our sample implementation using web workers. 16 | -------------------------------------------------------------------------------- /examples/sapiaPicture/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 3 | pjsFile="$DIR/../../dist/p-j-s.min.js" 4 | 5 | if [ ! -f "$pjsFile" ] 6 | then 7 | echo "missing p-j-s.min.js. run 'npm run build' at project root directory" 8 | exit 1 9 | fi 10 | 11 | cp $pjsFile $DIR 12 | $DIR/node_modules/serve/bin/serve $DIR -------------------------------------------------------------------------------- /examples/sharedSapiaPicture/default-pjs.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | PJS Sample 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 |
14 | 15 |
16 |
17 | 18 |
19 |
Hit run
20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/sharedSapiaPicture/default-pjs.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var run; 4 | 5 | (function () { 6 | var source = document.getElementById("source"); 7 | var runButton = document.getElementById("runButton"); 8 | var pjs = require('p-j-s'); 9 | pjs.init({ maxWorkers: 4 }); 10 | pjs.updateContext({ 11 | f:function(pixel){ 12 | // return pixel; 13 | var r = pixel & 0xFF; 14 | var g = (pixel & 0xFF00) >> 8; 15 | var b = (pixel & 0xFF0000) >> 16; 16 | var noiser = Math.random() * 0.5 + 0.5; 17 | var noiseg = Math.random() * 0.5 + 0.5; 18 | var noiseb = Math.random() * 0.5 + 0.5; 19 | 20 | var new_r = Math.max(Math.min(255, noiser * ((r * 0.393) + (g * 0.769) + (b * 0.189)) + (1 - noiser) * r), 0); 21 | var new_g = Math.max(Math.min(255, noiseg * ((r * 0.349) + (g * 0.686) + (b * 0.168)) + (1 - noiseg) * g), 0); 22 | var new_b = Math.max(Math.min(255, noiseb * ((r * 0.272) + (g * 0.534) + (b * 0.131)) + (1 - noiseb) * b), 0); 23 | 24 | return (pixel & 0xFF000000) + (new_b << 16) + (new_g << 8) + (new_r & 0xFF); 25 | } 26 | }).then(function(){ 27 | run = function(){ 28 | log.innerHTML = "Processing..."; 29 | runButton.style.visibility = "hidden"; 30 | var canvas = document.getElementById("target"); 31 | canvas.width = source.clientWidth; 32 | canvas.height = source.clientHeight; 33 | 34 | if (!canvas.getContext) { 35 | log.innerHTML = "Canvas not supported. Please install a HTML5 compatible browser."; 36 | return; 37 | } 38 | 39 | var tempContext = canvas.getContext("2d"); 40 | 41 | tempContext.drawImage(source, 0, 0, canvas.width, canvas.height); 42 | var canvasData = tempContext.getImageData(0, 0, canvas.width, canvas.height); 43 | var temp = new SharedUint8ClampedArray(canvasData.data.length); 44 | temp.set(canvasData.data); 45 | var copyData = new SharedUint32Array(temp.buffer); 46 | var start = new Date(); 47 | pjs(copyData).map('f').seq(function(err, result) { 48 | var diff = new Date() - start; 49 | canvasData.data.set(new SharedUint8ClampedArray(result.buffer)); 50 | tempContext.putImageData(canvasData, 0, 0); 51 | log.innerHTML = "Process done in " + diff + " ms"; 52 | runButton.style.visibility = "visible"; 53 | }); 54 | }; 55 | source.src = "pic.jpg"; 56 | }); 57 | })(); 58 | -------------------------------------------------------------------------------- /examples/sharedSapiaPicture/default.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | width: 100%; 4 | margin: 0; 5 | padding: 0; 6 | } 7 | body { 8 | min-height: 100%; 9 | } 10 | 11 | #root { 12 | height: 100%; 13 | width: 100%; 14 | } 15 | 16 | #sourceDiv { 17 | position: absolute; 18 | left: 10px; 19 | top: 50px; 20 | } 21 | 22 | #targetDiv { 23 | position: absolute; 24 | right: 10px; 25 | top: 50px; 26 | } 27 | 28 | #source { 29 | } 30 | 31 | #target { 32 | } 33 | 34 | 35 | #log { 36 | position: absolute; 37 | width: 100%; 38 | text-shadow: 2px 2px 1px black; 39 | font-size: 28px; 40 | color: white; 41 | text-align: center; 42 | margin-top: 300px; 43 | } 44 | -------------------------------------------------------------------------------- /examples/sharedSapiaPicture/defaultnoworker.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Serial Sample 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 |
14 | 15 |
16 |
17 | 18 |
19 |
Hit run
20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /examples/sharedSapiaPicture/defaultnoworker.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var run; 4 | 5 | var processSepia = function (binaryData, l) { 6 | for (var i = 0; i < l; i += 4) { 7 | var r = binaryData[i]; 8 | var g = binaryData[i + 1]; 9 | var b = binaryData[i + 2]; 10 | 11 | var noise_r = Math.random() * 0.5 + 0.5; 12 | var noise_g = Math.random() * 0.5 + 0.5; 13 | var noise_b = Math.random() * 0.5 + 0.5; 14 | 15 | binaryData[i] = Math.max(Math.min(255, noise_r * ((r * 0.393) + (g * 0.769) + (b * 0.189)) + (1 - noise_r) * r), 0); 16 | binaryData[i + 1] = Math.max(Math.min(255, noise_g * ((r * 0.349) + (g * 0.686) + (b * 0.168)) + (1 - noise_g) * g), 0); 17 | binaryData[i + 2] = Math.max(Math.min(255, noise_b * ((r * 0.272) + (g * 0.534) + (b * 0.131)) + (1 - noise_b) * b), 0); 18 | } 19 | }; 20 | 21 | (function () { 22 | 23 | var source = document.getElementById("source"); 24 | var runButton = document.getElementById("runButton"); 25 | 26 | run = function () { 27 | 28 | log.innerHTML = "Processing..."; 29 | runButton.style.visibility = "hidden"; 30 | 31 | var canvas = document.getElementById("target"); 32 | canvas.width = source.clientWidth; 33 | canvas.height = source.clientHeight; 34 | 35 | if (!canvas.getContext) { 36 | log.innerHTML = "Canvas not supported. Please install a HTML5 compatible browser."; 37 | return; 38 | } 39 | 40 | var tempContext = canvas.getContext("2d"); 41 | var len = canvas.width * canvas.height * 4; 42 | 43 | tempContext.drawImage(source, 0, 0, canvas.width, canvas.height); 44 | 45 | var canvasData = tempContext.getImageData(0, 0, canvas.width, canvas.height); 46 | var binaryData = canvasData.data; 47 | var start = new Date(); 48 | processSepia(binaryData, len); 49 | var diff = new Date() - start; 50 | tempContext.putImageData(canvasData, 0, 0); 51 | log.innerHTML = "Process done in " + diff + " ms (no web workers)"; 52 | runButton.style.visibility = "visible"; 53 | }; 54 | 55 | source.src = "pic.jpg"; 56 | })(); 57 | -------------------------------------------------------------------------------- /examples/sharedSapiaPicture/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sapiaPicture", 3 | "version": "0.0.0", 4 | "description": "The idea and some source code for this sample was taken from [here](http://blogs.msdn.com/b/eternalcoding/archive/2012/09/20/using-web-workers-to-improve-performance-of-image-manipulation.aspx)", 5 | "scripts": { 6 | "start": "./start.sh" 7 | }, 8 | "author": "", 9 | "license": "ISC", 10 | "devDependencies": { 11 | "serve": "^1.4.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/sharedSapiaPicture/pic-1024x668.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/examples/sharedSapiaPicture/pic-1024x668.jpg -------------------------------------------------------------------------------- /examples/sharedSapiaPicture/pic-2448×3264.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/examples/sharedSapiaPicture/pic-2448×3264.jpg -------------------------------------------------------------------------------- /examples/sharedSapiaPicture/pic-300x300.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/examples/sharedSapiaPicture/pic-300x300.jpg -------------------------------------------------------------------------------- /examples/sharedSapiaPicture/pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/examples/sharedSapiaPicture/pic.jpg -------------------------------------------------------------------------------- /examples/sharedSapiaPicture/readme.md: -------------------------------------------------------------------------------- 1 | # Sepia Picture 2 | 3 | The idea and some source code for this sample was taken from [here](http://blogs.msdn.com/b/eternalcoding/archive/2012/09/20/using-web-workers-to-improve-performance-of-image-manipulation.aspx) 4 | 5 | To run the sample you must serve the **default*.html** file using a local server. Otherwise a CORS error is thrown when manipulating the image inside the canvas. 6 | 7 | ## Running the sample 8 | 1. Run `npm i && npm start`. 9 | 2. Open Chrome and browse to `http://127.0.0.1:3000/default-pjs.html`. 10 | 3. Click **Run**. 11 | 12 | ## Sample description 13 | 14 | * `defaultnoworker.html` executes the sample using web workers. 15 | * `default-pjs.html` executes our sample implementation using web workers. 16 | -------------------------------------------------------------------------------- /examples/sharedSapiaPicture/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 3 | pjsFile="$DIR/../../dist/p-j-s.min.js" 4 | 5 | if [ ! -f "$pjsFile" ] 6 | then 7 | echo "missing p-j-s.min.js. run 'npm run build' at project root directory" 8 | exit 1 9 | fi 10 | 11 | cp $pjsFile $DIR 12 | $DIR/node_modules/serve/bin/serve $DIR -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | karma = require('karma').server, 3 | uglify = require('gulp-uglify'), 4 | browserify = require('browserify'), 5 | rename = require('gulp-rename'), 6 | jshint = require('gulp-jshint'), 7 | transform = require('vinyl-transform'), 8 | projectName = require('./package.json').name, 9 | sourceFile = ['./src/index.js']; 10 | 11 | var browserified = function(standalone) { 12 | return transform(function(filename) { 13 | if (standalone) { 14 | var b = browserify({standalone: projectName}); 15 | b.add(filename); 16 | } else { 17 | var b = browserify(); 18 | b.require(filename, {expose: projectName}); 19 | } 20 | return b.bundle(); 21 | }); 22 | } 23 | 24 | gulp.task('lint', function() { 25 | return gulp.src('./src/*.js') 26 | .pipe(jshint()) 27 | .pipe(jshint.reporter('default')) 28 | .pipe(jshint.reporter('fail')); 29 | }); 30 | 31 | gulp.task('build-browserify', function() { 32 | gulp.src(sourceFile) 33 | .pipe(browserified()) 34 | .pipe(rename(projectName + '.js')) 35 | .pipe(gulp.dest('./dist/')) 36 | .pipe(uglify()) 37 | .pipe(rename(projectName + '.min.js')) 38 | .pipe(gulp.dest('./dist')) 39 | }); 40 | 41 | gulp.task('build-standalone', function() { 42 | gulp.src(sourceFile) 43 | .pipe(browserified(true)) 44 | .pipe(rename(projectName + '-standalone.js')) 45 | .pipe(gulp.dest('./dist/')) 46 | .pipe(uglify()) 47 | .pipe(rename(projectName + '-standalone.min.js')) 48 | .pipe(gulp.dest('./dist')) 49 | }); 50 | 51 | /** 52 | * Run test once and exit 53 | */ 54 | gulp.task('test', ['lint'], function (done) { 55 | karma.start({ 56 | configFile: __dirname + '/karma-src.conf.js', 57 | singleRun: true 58 | }, done); 59 | }); 60 | 61 | gulp.task('test-debug', function (done) { 62 | karma.start({ 63 | configFile: __dirname + '/karma-src.conf.js', 64 | singleRun: false, 65 | autoWatch: true 66 | }, done); 67 | }); 68 | 69 | gulp.task('build', ['build-browserify', 'build-standalone']); 70 | gulp.task('default', ['test', 'build']); 71 | -------------------------------------------------------------------------------- /karma-src.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Thu Aug 21 2014 10:24:39 GMT+0200 (CEST) 3 | 4 | module.exports = function(config) { 5 | var configuration = { 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['mocha', 'chai-jquery', 'jquery-1.8.3', 'sinon-chai', 'browserify'], 14 | 15 | plugins: [ 16 | 'karma-mocha', 17 | 'karma-chai', 18 | 'karma-sinon-chai', 19 | 'karma-chrome-launcher', 20 | 'karma-firefox-launcher', 21 | 'karma-jquery', 22 | 'karma-chai-jquery', 23 | 'karma-mocha-reporter', 24 | 'karma-bro' 25 | ], 26 | 27 | // list of files / patterns to load in the browser 28 | files: [ 29 | 'test/**/*.js' 30 | ], 31 | 32 | 33 | // list of files to exclude 34 | exclude: [ 35 | ], 36 | 37 | 38 | // preprocess matching files before serving them to the browser 39 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 40 | preprocessors: { 41 | 'test/**/*.js': ['browserify'] 42 | }, 43 | 44 | browserify:{ 45 | debug: true 46 | }, 47 | 48 | // test results reporter to use 49 | // possible values: 'dots', 'progress' 50 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 51 | reporters: ['mocha'], 52 | 53 | 54 | // web server port 55 | port: 9876, 56 | 57 | 58 | // enable / disable colors in the output (reporters and logs) 59 | colors: true, 60 | 61 | 62 | // level of logging 63 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 64 | logLevel: config.LOG_INFO, 65 | 66 | 67 | // enable / disable watching file and executing tests whenever any file changes 68 | autoWatch: true, 69 | 70 | 71 | // start these browsers 72 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 73 | browsers: ['Chrome', 'FirefoxNightly'], 74 | 75 | customLaunchers: { 76 | Chrome_travis_ci: { 77 | base: 'Chrome', 78 | flags: ['--no-sandbox'] 79 | } 80 | }, 81 | 82 | // Continuous Integration mode 83 | // if true, Karma captures browsers, runs the tests and exits 84 | singleRun: false 85 | }; 86 | 87 | if(process.env.TRAVIS){ 88 | configuration.browsers = ['Chrome_travis_ci']; 89 | } else if (process.env.BROWSER) { 90 | configuration.browsers = [process.env.BROWSER]; 91 | } 92 | 93 | config.set(configuration); 94 | }; 95 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "p-j-s", 3 | "version": "1.0.0-beta", 4 | "author": { 5 | "name": "pjsteam", 6 | "email": "" 7 | }, 8 | "devDependencies": { 9 | "browserify": "^6.0.3", 10 | "chai": "^1.9.1", 11 | "chai-jquery": "^1.2.3", 12 | "gulp": "^3.8.7", 13 | "gulp-rename": "^1.2.0", 14 | "gulp-uglify": "^0.3.1", 15 | "karma": "^0.12.22", 16 | "karma-browserify": "^2.0.0", 17 | "karma-chai": "^0.1.0", 18 | "karma-chai-jquery": "^1.0.0", 19 | "karma-chrome-launcher": "^0.1.5", 20 | "karma-firefox-launcher": "~0.1.6", 21 | "karma-jasmine": "^0.1.5", 22 | "karma-jquery": "^0.1.0", 23 | "karma-mocha": "^0.1.8", 24 | "karma-mocha-reporter": "^0.3.1", 25 | "karma-sinon-chai": "^0.2.0", 26 | "mocha": "^1.21.4", 27 | "sinon": "^1.10.3", 28 | "sinon-chai": "^2.5.0", 29 | "vinyl-transform": "0.0.1", 30 | "karma-bro": "^0.8.0", 31 | "gulp-jshint": "^1.9.2" 32 | }, 33 | "repository": "pjsteam/pjs", 34 | "scripts": { 35 | "test": "gulp test", 36 | "test-ffn": "BROWSER=FirefoxNightly gulp test", 37 | "test-chr": "BROWSER=Chrome gulp test", 38 | "build": "gulp build" 39 | }, 40 | "dependencies": { 41 | "webworkify": "^1.0.2", 42 | "xtend": "^4.0.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /spikes/1.2/README.md: -------------------------------------------------------------------------------- 1 | 1.2 - Investigación Typed Arrays - Como funcionan los Transferable Objects 2 | ---------------------------- 3 | 4 | Here you can find transferrableVsCloning.js which compares the use of transferrable objects against object cloning when you send elements throw Web Worker's postMessage function. 5 | 6 | *Tests:* 7 | * [Sending small amount of elements](http://jsperf.com/transferrable-vs-cloning/2) 8 | * [Sending a larger amount of elements](http://jsperf.com/long-transferrable-vs-cloning/2) 9 | 10 | *Results:* 11 | 12 | We find out that Transfering objects is faster than cloning them and the difference between them increases proportionally to ArrayBuffer's size. 13 | Also, the tests show that ArrayBuffer's size does not hit Transferable Objects performance. 14 | 15 | *Notes:* 16 | 17 | The same code is used by both test with one diference: 18 | * [Sending small amount of elements](http://jsperf.com/transferrable-vs-cloning/2)'s **createElements** function generates an ArrayBuffer with 100000 elements. 19 | * [Sending a larger amount of elements](http://jsperf.com/transferrable-vs-cloning/2)'s **createElements** function generates an ArrayBuffer with 1000000 elements (10 times the first one). 20 | 21 | We had to create 2 ArrayBuffers to test the Transferable Objects because we encounter that transferring repeatedly the same ArrayBuffer throws: 22 | ``` 23 | Failed to execute 'postMessage' on 'Worker': An ArrayBuffer is neutered and could not be cloned. 24 | ``` 25 | The use of 2 ArrayBuffers gives the time to the other buffer to complete the transferration and then it was available to be use on the next test iteration. 26 | -------------------------------------------------------------------------------- /spikes/1.2/longerSharedTransferrableVsCloning.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/longer-shared-transferrable-vs-cloning 2 | 3 | // HTML setup 4 | 64 | 65 | // JavaScript setup 66 | __finish = function () { 67 | deferred.resolve(); 68 | } 69 | 70 | // Test case 1 - long shared cloning 71 | cloningWW.postMessage(elements); 72 | 73 | // Test case 2 - long shared trasnferrable 74 | transferrableWW.postMessage(elements, [elements.buffer]); -------------------------------------------------------------------------------- /spikes/1.2/longerTransferrableVsCloning.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/longer-transferrable-vs-cloning/3 2 | 3 | // HTML setup 4 | 64 | 65 | // JavaScript setup 66 | __finish = function () { 67 | deferred.resolve(); 68 | } 69 | 70 | // Test case 1 - Long cloning 71 | cloningWW.postMessage(elements); 72 | 73 | // Test case 2 - Long trasnferrable 74 | transferrableWW.postMessage(elements, [elements.buffer]); -------------------------------------------------------------------------------- /spikes/1.2/longerTransferrableVsCloning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/1.2/longerTransferrableVsCloning.png -------------------------------------------------------------------------------- /spikes/1.2/transferableSharedVsNotShared.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/transfer-shared-vs-not-shared 2 | 3 | // HTML setup 4 | 73 | 74 | // JavaScript setup 75 | __finish = function () { 76 | deferred.resolve(); 77 | } 78 | 79 | // Test case 1 - not shared transferrable 80 | transferableNotSharedWW.postMessage(elementsNotShared, [elementsNotShared.buffer]); 81 | 82 | // Test case 2 - shared transferable 83 | transferableSharedWW.postMessage(elementsShared, [elementsShared.buffer]); -------------------------------------------------------------------------------- /spikes/1.2/transferableSharedVsNotShared.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/1.2/transferableSharedVsNotShared.png -------------------------------------------------------------------------------- /spikes/1.2/transferrableVsCloning.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/transferrable-vs-cloning/3 2 | 3 | // HTML setup 4 | 64 | 65 | // JavaScript setup 66 | __finish = function () { 67 | deferred.resolve(); 68 | } 69 | 70 | // Test case 1 - Cloning 71 | cloningWW.postMessage(elements); 72 | 73 | // Test case 2 - Transferrable 74 | transferrableWW.postMessage(elements, [elements.buffer]); 75 | -------------------------------------------------------------------------------- /spikes/1.2/transferrableVsCloning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/1.2/transferrableVsCloning.png -------------------------------------------------------------------------------- /spikes/1/PoC/both.js: -------------------------------------------------------------------------------- 1 | var total = 80000000; 2 | var typed = new Uint32Array(total); 3 | var parts = navigator.hardwareConcurrency || 4; 4 | 5 | for (var i = total; i > 0; i--){ 6 | typed[i - 1] = i; 7 | } 8 | 9 | var wCode2 = function(e){ 10 | var result = new Uint32Array(typed.length); 11 | for (var i = typed.length; i > 0; i--){ 12 | result[i - 1] = Math.floor(Math.random() * 10000) 13 | + Math.floor(Math.random() * 10000) 14 | + Math.floor(Math.random() * 10000) 15 | + Math.floor(Math.random() * 10000); 16 | } 17 | 18 | return result; 19 | } 20 | 21 | setTimeout(function(){ 22 | console.time('worker time'); 23 | var elements = wCode2(); 24 | console.timeEnd('worker time'); 25 | }, 1000); 26 | 27 | 28 | var wCode = function(event){ 29 | console.log(event); 30 | var startDate = new Date(); 31 | 32 | var buffer = event.data; 33 | 34 | var elements = new Uint32Array(buffer); 35 | 36 | var result = new Uint32Array(elements.length); 37 | for (var i = elements.length; i > 0; i--){ 38 | result[i - 1] = Math.floor(Math.random() * 10000) 39 | + Math.floor(Math.random() * 10000) 40 | + Math.floor(Math.random() * 10000) 41 | + Math.floor(Math.random() * 10000); 42 | } 43 | 44 | var end = new Date(); 45 | 46 | postMessage(result.buffer, [result.buffer]); 47 | 48 | postMessage({ start: startDate, end: end }); 49 | } 50 | 51 | var blob = new Blob([ 52 | "onmessage = " + wCode.toString()]); 53 | 54 | // Obtain a blob URL reference to our worker 'file'. 55 | var blobURL = window.URL.createObjectURL(blob); 56 | 57 | var workers = []; 58 | var done = 0; 59 | var doneTimes = 0; 60 | 61 | var composed = []; 62 | 63 | var times = []; 64 | var results = []; 65 | 66 | for (var i = parts - 1; i >= 0; i--) { 67 | var worker = new Worker(blobURL); 68 | workers.push(worker); 69 | } 70 | 71 | setTimeout(function(){ 72 | var startDate = new Date(); 73 | console.time('worker time'); 74 | var factor = (total / parts); 75 | var bytesPerElement = typed.BYTES_PER_ELEMENT; 76 | workers.forEach(function(w, index){ 77 | w.onmessage = function(event){ 78 | if (event.data.start){ 79 | doneTimes++; 80 | times.push(event.data); 81 | if (doneTimes === parts){ 82 | times.forEach(function(t){ 83 | console.log('worker start ' + t.start.toString() + '.' + t.start.getMilliseconds()); 84 | console.log('worker end ' + t.end.toString() + '.' + t.end.getMilliseconds()); 85 | }); 86 | } 87 | return; 88 | } 89 | // results.push(event.data); 90 | 91 | if (++done === parts){ 92 | console.timeEnd('worker time'); 93 | var endDate = new Date(); 94 | console.log('overall start ' + startDate.toString() + '.' + startDate.getMilliseconds()); 95 | console.log('overall end ' + endDate.toString() + '.' + endDate.getMilliseconds()); 96 | console.log(endDate - startDate); 97 | // results.forEach(function(p){ 98 | // console.log(new Uint32Array(p)); 99 | // }); 100 | } 101 | }; 102 | 103 | var start = index * factor; 104 | 105 | // ideally we would just send parts of the same buffer 106 | var sliced = typed.buffer.slice(start * bytesPerElement, (start + factor) * bytesPerElement); 107 | 108 | w.postMessage(sliced, [sliced]); 109 | }); 110 | }, 10000); 111 | -------------------------------------------------------------------------------- /spikes/1/PoC/main-both.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Both 5 | 6 | 7 |

both.js

8 | 9 | 10 | -------------------------------------------------------------------------------- /spikes/1/PoC/main-series.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Series 5 | 6 |

series.js

7 | 8 | -------------------------------------------------------------------------------- /spikes/1/PoC/main-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | paralelel 5 | 6 | 7 |

parallel.js

8 | 9 | 10 | -------------------------------------------------------------------------------- /spikes/1/PoC/series.js: -------------------------------------------------------------------------------- 1 | var total = 1000000; 2 | var typed = new Uint32Array(total); 3 | 4 | for (var i = total; i > 0; i--){ 5 | typed[i - 1] = i; 6 | } 7 | 8 | var wCode = function(e){ 9 | var result = new Uint32Array(typed.length); 10 | for (var i = typed.length; i > 0; i--){ 11 | result[i - 1] = Math.floor(Math.random() * 10000) 12 | + Math.floor(Math.random() * 10000) 13 | + Math.floor(Math.random() * 10000) 14 | + Math.floor(Math.random() * 10000); 15 | } 16 | 17 | return result; 18 | } 19 | 20 | setTimeout(function(){ 21 | console.time('worker time'); 22 | var elements = wCode(); 23 | console.timeEnd('worker time'); 24 | }, 1000); 25 | -------------------------------------------------------------------------------- /spikes/1/PoC/test.js: -------------------------------------------------------------------------------- 1 | var total = 1000000; 2 | var parts = navigator.hardwareConcurrency || 4; 3 | var typed = new Uint32Array(total); 4 | 5 | for (var i = total; i > 0; i--){ 6 | typed[i - 1] = i; 7 | } 8 | 9 | var wCode = function(event){ 10 | // console.log(event); 11 | // var startDate = new Date(); 12 | 13 | var buffer = event.data; 14 | 15 | var elements = new Uint32Array(buffer); 16 | 17 | var result = new Uint32Array(elements.length); 18 | for (var i = elements.length; i > 0; i--){ 19 | result[i - 1] = Math.floor(Math.random() * 10000) 20 | + Math.floor(Math.random() * 10000) 21 | + Math.floor(Math.random() * 10000) 22 | + Math.floor(Math.random() * 10000); 23 | } 24 | 25 | // var end = new Date(); 26 | 27 | postMessage(result.buffer, [result.buffer]); 28 | 29 | // postMessage({ start: startDate, end: end }); 30 | } 31 | 32 | var blob = new Blob([ 33 | "onmessage = " + wCode.toString()]); 34 | 35 | // Obtain a blob URL reference to our worker 'file'. 36 | var blobURL = window.URL.createObjectURL(blob); 37 | 38 | var workers = []; 39 | var done = 0; 40 | var doneTimes = 0; 41 | 42 | var composed = []; 43 | 44 | var times = []; 45 | var results = []; 46 | 47 | for (var i = parts - 1; i >= 0; i--) { 48 | var worker = new Worker(blobURL); 49 | workers.push(worker); 50 | } 51 | 52 | setTimeout(function(){ 53 | var startDate = new Date(); 54 | console.time('worker time'); 55 | var factor = (total / parts); 56 | var bytesPerElement = typed.BYTES_PER_ELEMENT; 57 | workers.forEach(function(w, index){ 58 | w.onmessage = function(event){ 59 | // if (event.data.start){ 60 | // doneTimes++; 61 | // times.push(event.data); 62 | // if (doneTimes === parts){ 63 | // times.forEach(function(t){ 64 | // console.log('worker start ' + t.start.toString() + '.' + t.start.getMilliseconds()); 65 | // console.log('worker end ' + t.end.toString() + '.' + t.end.getMilliseconds()); 66 | // }); 67 | // } 68 | // return; 69 | // } 70 | // results.push(event.data); 71 | 72 | if (++done === parts){ 73 | console.timeEnd('worker time'); 74 | // var endDate = new Date(); 75 | // console.log('overall start ' + startDate.toString() + '.' + startDate.getMilliseconds()); 76 | // console.log('overall end ' + endDate.toString() + '.' + endDate.getMilliseconds()); 77 | // console.log(endDate - startDate); 78 | // results.forEach(function(p){ 79 | // console.log(new Uint32Array(p)); 80 | // }); 81 | workers.forEach(function(w){ 82 | w.terminate(); 83 | }) 84 | } 85 | }; 86 | 87 | var start = index * factor; 88 | 89 | // ideally we would just send parts of the same buffer 90 | var sliced = new Uint32Array(typed.subarray(start, (start + factor))); 91 | 92 | w.postMessage(sliced.buffer, [sliced.buffer]); 93 | }); 94 | }, 1000); 95 | -------------------------------------------------------------------------------- /spikes/1/map/map01-main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Map 01 - Interface example 5 | 6 |

Runs map01-usecase.js. This script shows an use case for pjs.map function.

7 | 8 | -------------------------------------------------------------------------------- /spikes/1/map/map01-usecase.js: -------------------------------------------------------------------------------- 1 | /** 2 | pjs.map interface example. 3 | */ 4 | 5 | // Define 6 | var pjs = { 7 | map: function(array, mapper, callback) { 8 | callback(array.map(mapper)); 9 | } 10 | }; 11 | 12 | // Prepare 13 | var createPersons = function (count) { 14 | var items = []; 15 | var i = count; 16 | 17 | var createPerson = function (pName, pAge) { 18 | return { 19 | name: pName, 20 | age: pAge 21 | }; 22 | }; 23 | 24 | for (; i-- ;) { 25 | items.push(createPerson('name' + i, Math.floor(Math.random() * 40 + 10))); 26 | } 27 | 28 | return items; 29 | } 30 | 31 | var persons = createPersons(100); 32 | 33 | var processPerson = function (person) { 34 | return person.name; 35 | }; 36 | 37 | var callback = function (names) { 38 | console.log(names); 39 | } 40 | 41 | // Start profiling 42 | var startDate = new Date(); 43 | 44 | // Perform 45 | pjs.map(persons, processPerson, callback); 46 | 47 | // End profiling 48 | var ms = new Date() - startDate; 49 | console.log('Overall time: ' + ms + ' ms'); 50 | -------------------------------------------------------------------------------- /spikes/1/map/map02-main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Map 02 - Workers 5 | 6 |

Runs map02-workers.js. This script adds workers for pjs.map function.

7 | 8 | -------------------------------------------------------------------------------- /spikes/1/map/map02-workers.js: -------------------------------------------------------------------------------- 1 | /** 2 | pjs.map with workers. 3 | Only one worker is being used from pjs.workers. 4 | pjs.map @p mapper function is ignored. 5 | pjs.map @p callback is being called for each worker result (unordered). 6 | */ 7 | 8 | // Define 9 | var pjs = { 10 | map: function(array, mapper, callback) { 11 | var w = this.workers[0]; 12 | w.onmessage = function(event) { 13 | var data = event.data; 14 | var time = data.runTime; 15 | var result = data.result; 16 | console.log('Overall time: ' + time + ' ms'); 17 | callback(result); 18 | }; 19 | w.postMessage({ 20 | start: new Date(), 21 | elements: array 22 | }); 23 | }, 24 | workers: ((function (){ 25 | var items = []; 26 | var parts = navigator.hardwareConcurrency || 2; 27 | var i = parts; 28 | 29 | var wCode = function (event) { 30 | var elements = event.data.elements; 31 | var startTime = event.data.start; 32 | var result = []; 33 | var i = elements.length; 34 | 35 | for ( ; i--; ){ 36 | result[i] = elements[i].name + 'WKD'; 37 | } 38 | 39 | postMessage({ 40 | result: result, 41 | runTime: new Date() - startTime 42 | }); 43 | }; 44 | var blob = new Blob(["onmessage = " + wCode.toString()]); 45 | var blobURL = window.URL.createObjectURL(blob); 46 | for (; i--;) { 47 | var worker = new Worker(blobURL); 48 | items.push(worker); 49 | } 50 | console.log('#workers created: ' + parts); 51 | return items; 52 | })()), 53 | terminate: function () { 54 | var i = this.workers.length; 55 | for ( ; i--; ){ 56 | this.workers[i].terminate(); 57 | } 58 | this.workers = []; 59 | } 60 | }; 61 | 62 | // Prepare 63 | var createPersons = function (count) { 64 | var items = []; 65 | var i = count; 66 | 67 | var createPerson = function (pName, pAge) { 68 | return { 69 | name: pName, 70 | age: pAge 71 | }; 72 | }; 73 | 74 | for (; i-- ;) { 75 | items.push(createPerson('name' + i, Math.floor(Math.random() * 40 + 10))); 76 | } 77 | 78 | return items; 79 | } 80 | 81 | var persons = createPersons(100); 82 | 83 | var processPerson = function (person) { 84 | return person.name; 85 | }; 86 | 87 | var callback = function (names) { 88 | console.log(names); 89 | 90 | // Clean 91 | pjs.terminate(); 92 | } 93 | 94 | // Perform 95 | pjs.map(persons, processPerson, callback); 96 | -------------------------------------------------------------------------------- /spikes/1/map/map03-allworkers.js: -------------------------------------------------------------------------------- 1 | /** 2 | All workers are being used from pjs.workers. 3 | pjs.map @p mapper function is ignored. 4 | pjs.map @p callback is being called for each worker result (unordered). 5 | */ 6 | 7 | // Define 8 | var pjs = { 9 | map: function(array, mapper, callback) { 10 | var finishCount = 0; 11 | var wks = this.workers; 12 | var wIndex = wks.length; 13 | var factor = Math.floor(array.length / wks.length); 14 | var wCallback = function(event) { 15 | var data = event.data; 16 | var time = data.runTime; 17 | var overallStart = data.overallStart; 18 | var result = data.result; 19 | console.log('Worker time: ' + time + ' ms'); 20 | finishCount++; 21 | callback(result); 22 | if (finishCount === wks.length) { 23 | console.log('Overall time: ' + (new Date() - overallStart) + ' ms'); 24 | pjs.terminate(); 25 | } 26 | }; 27 | 28 | var overallStart = new Date(); 29 | 30 | for ( ; wIndex-- ; ) { 31 | var wTime = new Date(); 32 | var wElements; 33 | var w = wks[wIndex]; 34 | var aux = 0; 35 | var wStart = wIndex * factor; 36 | var wEnd = wStart + factor; 37 | if (wIndex === (wks.length - 1)) { 38 | aux = array.length - wEnd; 39 | } 40 | 41 | wElements = array.slice(wStart, wEnd + aux); 42 | 43 | console.log('(' + wStart + ' , ' + (wEnd + aux) + ')') 44 | w.onmessage = wCallback; 45 | w.postMessage({ 46 | start: wTime, 47 | overallStart: overallStart, 48 | elements: wElements 49 | }); 50 | } 51 | }, 52 | workers: ((function (){ 53 | var items = []; 54 | var parts = navigator.hardwareConcurrency || 2; 55 | var i = parts; 56 | 57 | var wCode = function (event) { 58 | var elements = event.data.elements; 59 | var startTime = event.data.start; 60 | var overallStart = event.data.overallStart; 61 | var result = []; 62 | var i = elements.length; 63 | 64 | for ( ; i--; ){ 65 | result[i] = elements[i].name + 'WKD'; 66 | } 67 | 68 | postMessage({ 69 | result: result, 70 | overallStart: overallStart, 71 | runTime: new Date() - startTime 72 | }); 73 | }; 74 | var blob = new Blob(["onmessage = " + wCode.toString()]); 75 | var blobURL = window.URL.createObjectURL(blob); 76 | for (; i--;) { 77 | var worker = new Worker(blobURL); 78 | items.push(worker); 79 | } 80 | console.log('#workers created: ' + parts); 81 | return items; 82 | })()), 83 | terminate: function () { 84 | var i = this.workers.length; 85 | for ( ; i--; ){ 86 | this.workers[i].terminate(); 87 | } 88 | this.workers = []; 89 | } 90 | }; 91 | 92 | // Prepare 93 | var createPersons = function (count) { 94 | var items = []; 95 | var i = count; 96 | 97 | var createPerson = function (pName, pAge) { 98 | return { 99 | name: pName, 100 | age: pAge 101 | }; 102 | }; 103 | 104 | for (; i-- ;) { 105 | items.push(createPerson('name' + i, Math.floor(Math.random() * 40 + 10))); 106 | } 107 | 108 | return items; 109 | } 110 | 111 | var persons = createPersons(100); 112 | 113 | var processPerson = function (person) { 114 | return person.name; 115 | }; 116 | 117 | var callback = function (names) { 118 | console.log(names); 119 | } 120 | 121 | // Perform 122 | pjs.map(persons, processPerson, callback); 123 | -------------------------------------------------------------------------------- /spikes/1/map/map03-main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Map 03 - Using All Workers 5 | 6 |

Runs map03-allworkers.js. This script uses all available workers for pjs.map function.

7 | 8 | -------------------------------------------------------------------------------- /spikes/1/map/map04-main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Map 04 - Joining all workers results 5 | 6 |

Runs map04-joinresult.js. This script sends the complete result to pjs.map function.

7 | 8 | -------------------------------------------------------------------------------- /spikes/1/map/map05-main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Map 05 - Using the user mapper function 5 | 6 |

Runs map05-mapping.js. This script uses the user's mapper function on pjs.map function.

7 | 8 | -------------------------------------------------------------------------------- /spikes/1/map/map06-main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Map 06 - Sending functions as blobs 5 | 6 |

Runs map06-blobmapper.js. This script uses blob to send the mapper function to the workers.

7 | 8 | -------------------------------------------------------------------------------- /spikes/1/map/map07-main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Map 07 - Typing arrays 5 | 6 |

Runs map07-typed.js. This script uses Uint32Array to transfer objects to web workers.

7 | 8 | -------------------------------------------------------------------------------- /spikes/1/map/map08-main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Map 08 - Using all arrays 5 | 6 |

Runs map08-alltypes.js. This script uses all BufferArray views to process data.

7 | 8 | -------------------------------------------------------------------------------- /spikes/1/map/map09-main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Map 09 - using pjs library 6 | 7 | 8 |

Runs map09-lib.js. This script imports baby pjs library and use it on a buttons tap action.

9 |
10 |
11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /spikes/1/map/map09-main.js: -------------------------------------------------------------------------------- 1 | // Process 2 | var procesTypedElement = function (element) { 3 | element = Math.floor(Math.random() * 10000) 4 | + Math.floor(Math.random() * 10000) 5 | + Math.floor(Math.random() * 10000) 6 | + Math.floor(Math.random() * 10000); 7 | 8 | return element; 9 | }; 10 | 11 | var callback = function (elements) { 12 | console.log('Callback receives ' + elements.length + ' elements'); 13 | } 14 | 15 | var mapWithBufferArrayView = function (data, Constructor) { 16 | var elementsLength = data.length; 17 | var i = elementsLength; 18 | var results = new Constructor(elementsLength); 19 | for (; i-- ;) { 20 | results[i] = procesTypedElement(data[i]); 21 | } 22 | return results; 23 | } 24 | 25 | // Perform pjs 26 | var runU32Task = function () { 27 | console.log('Run U32 Task'); 28 | pjs.map(u32Data, procesTypedElement, callback); 29 | }; 30 | 31 | var runU8Task = function () { 32 | console.log('Run U8 Task'); 33 | pjs.map(u8Data, procesTypedElement, callback); 34 | }; 35 | 36 | // Perform serial 37 | var runU32Serial = function () { 38 | console.log('Run U32 Serial'); 39 | var startTime = new Date(); 40 | var results = mapWithBufferArrayView(u32Data, Uint32Array); 41 | var endTime = new Date(); 42 | console.log('Run u32 serial time: ' + (endTime - startTime) + ' ms'); 43 | callback(results); 44 | }; 45 | 46 | var runU8Serial = function () { 47 | console.log('Run U8 Serial'); 48 | var startTime = new Date(); 49 | var results = mapWithBufferArrayView(u8Data, Uint8Array); 50 | var endTime = new Date(); 51 | console.log('Run u8 serial time: ' + (endTime - startTime) + ' ms'); 52 | callback(results); 53 | }; 54 | 55 | // Prepare 56 | var createData = function (Constructor) { 57 | var total = 1048576 * 20; // 1024 * 1024 58 | var typed = new Constructor(total); 59 | var i = total; 60 | for ( ; i-- ; ){ 61 | typed[i] = i * 2; 62 | } 63 | return typed; 64 | } 65 | 66 | var u32Data = createData(Uint32Array); 67 | var u8Data = createData(Uint8Array); 68 | -------------------------------------------------------------------------------- /spikes/2.1.1/newVsPreExistent.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/worker-cold-start/6 2 | 3 | // HTML setup 4 | 53 | 54 | // JavaScript setup 55 | __finish = function () { 56 | deferred.resolve(); 57 | }; 58 | 59 | // Test case 1 - Pre existent worker 60 | postCode(worker, function() { postMessage(null); }); 61 | 62 | // Test case 2 - New workers 63 | var newWorker = setupForNewWorkerEachTime(); 64 | 65 | newWorker.onmessage = function(event){ 66 | newWorker.terminate(); 67 | deferred.resolve(); 68 | } 69 | newWorker.postMessage(null); -------------------------------------------------------------------------------- /spikes/2.1.1/newVsPreExistent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/2.1.1/newVsPreExistent.png -------------------------------------------------------------------------------- /spikes/2.2.1/test-functionSerialization/funcSerialization.js: -------------------------------------------------------------------------------- 1 | // Test ran using Chrome javascript console 2 | 3 | // JavaScript setup 4 | function setupFunctionWorker(){ 5 | var wCode = function(event){ 6 | var f = event.data; 7 | postMessage(f()); 8 | }; 9 | var blob = new Blob(["onmessage = " + wCode.toString()]); 10 | var blobURL = window.URL.createObjectURL(blob); 11 | return new Worker(blobURL); 12 | }; 13 | 14 | var f = function () { 15 | return 5; 16 | }; 17 | var functionWorker = setupFunctionWorker(); 18 | functionWorker.onmessage = function(event) { 19 | if (5 == f) { 20 | console.log('hi 5!'); 21 | } 22 | } 23 | 24 | // Test case - Begin test 25 | functionWorker.postMessage(f); -------------------------------------------------------------------------------- /spikes/2.2.1/test-functionSerialization/funcSerialization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/2.2.1/test-functionSerialization/funcSerialization.png -------------------------------------------------------------------------------- /spikes/2.2.1/test-howToSendCode/longSerialization.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/pjs-serialization-long/3 2 | 3 | // HTML setup 4 | 86 | 87 | // JavaScript setup 88 | __finish = function () { 89 | deferred.resolve(); 90 | }; 91 | 92 | // Test case 1 - Sending code by blob 93 | var blob = new Blob(['__f = ' + fStringified], { type: 'application/javascript' }); 94 | var blobURL = window.URL.createObjectURL(blob); 95 | 96 | blobWorker.postMessage(blobURL); 97 | 98 | // Test case 2 - Sending code by copy 99 | copyWorker.postMessage(fStringified); 100 | 101 | // Test case 3 - Sending code by transferrable objects 102 | var b = encoder.encode(fStringified); 103 | transferrableWorker.postMessage(b, [b.buffer]); -------------------------------------------------------------------------------- /spikes/2.2.1/test-howToSendCode/longSerialization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/2.2.1/test-howToSendCode/longSerialization.png -------------------------------------------------------------------------------- /spikes/2.2.1/test-howToSendCode/shortSerialization.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/pjs-serialization/5 2 | 3 | // HTML setup 4 | 73 | 74 | // JavaScript setup 75 | __finish = function () { 76 | deferred.resolve(); 77 | }; 78 | 79 | // Test case 1 - Sending code by blob 80 | var blob = new Blob(['__f = ' + fStringified], { type: 'application/javascript' }); 81 | var blobURL = window.URL.createObjectURL(blob); 82 | 83 | blobWorker.postMessage(blobURL); 84 | 85 | // Test case 2 - Sending code by copy 86 | copyWorker.postMessage(fStringified); 87 | 88 | // Test case 3 - Sending code by transferrable objects 89 | var b = encoder.encode(fStringified); 90 | transferrableWorker.postMessage(b, [b.buffer]); -------------------------------------------------------------------------------- /spikes/2.2.1/test-howToSendCode/shortSerialization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/2.2.1/test-howToSendCode/shortSerialization.png -------------------------------------------------------------------------------- /spikes/2.2.1/test-howToSendCodeWithData/encoding.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/pjs-encoding/3 2 | 3 | // HTML setup 4 | 76 | 77 | // JavaScript setup 78 | __finish = function () { 79 | deferred.resolve(); 80 | }; 81 | 82 | // Test case 1 - Sending code by copy 83 | var data = { 84 | elements: generateElements(), 85 | code: fStringified 86 | }; 87 | copyWorker.postMessage(data, [data.elements.buffer]); 88 | 89 | // Test case 2 - Sending code by transferrable objects 90 | var data = { 91 | elements: generateElements2(), 92 | code: encoder.encode(fStringified) 93 | }; 94 | transferrableWorker.postMessage(data, [data.code.buffer, data.elements.buffer]); -------------------------------------------------------------------------------- /spikes/2.2.1/test-howToSendCodeWithData/encoding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/2.2.1/test-howToSendCodeWithData/encoding.png -------------------------------------------------------------------------------- /spikes/2.2.1/test-syncEncodingAPI/encodingAPI.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/pjs-serialization-comparison/4 2 | 3 | // HTML setup 4 | 5 | 60 | 61 | // Test case 1 - Non-native function 62 | withNonNativeFunction(); 63 | 64 | // Test case 2 - Native encoding API 65 | withEncodingAPI(); -------------------------------------------------------------------------------- /spikes/2.2.1/test-syncEncodingAPI/encodingAPI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/2.2.1/test-syncEncodingAPI/encodingAPI.png -------------------------------------------------------------------------------- /spikes/2.3.1/README.md: -------------------------------------------------------------------------------- 1 | 2.3.1 - Investigar alternativas partición del Typed Array 2 | ----------------------- 3 | 4 | At this directory you can find: 5 | * copyComparison 6 | * splitComparison 7 | 8 | copyComparison 9 | ----------------------- 10 | 11 | Verifies which is the best alternative to copy one TypedArray to new TypedArray. 12 | The following alternatives were tested: 13 | * Manual: it creates a TypedArray with the total count of elements and then iterates all array's elements to copy them into the target. 14 | * Buffer slice: it creates a JavaScript's Array with ArrayBuffer.prototype.slice function and then uses this array to create a TypedArray. 15 | * Constructor: it creates a TypedArray from the typed array to copy using TypedArray constructor. 16 | * TypedArray set: it creates a TypedArray with the total count of elements and then inserts all the parts typed arrays using [TypedArray's set function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/set). 17 | * Buffer subarray: it creates a new TypedArray with [TypedArray's subarray function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/subarray) and then uses the contrcutor to create the new TypedArray. 18 | * Blob: it creates a blob from a TypedArray and then uses a FileReader to obtein the elements and insert them into the target TypedArray. 19 | 20 | *Test:* [Typed Array copy comparison](http://jsperf.com/arraybuffer-copy/3) 21 | 22 | *Results:* 23 | We run the tests both on Chrome 40.X and Chrome 41.X. We find out that the best alternatives where: 24 | * Manual 25 | * Buffer slice 26 | * Buffer subarray 27 | * TypedArray set 28 | The diference between them were so low that we decided to use then on the next test. 29 | 30 | Here you can see the results for [copying TypedArrays](./copyComparison.png) 31 | 32 | splitComparison 33 | ----------------------- 34 | 35 | Verifies which is the best alternative to split a TypedArray in multiple sub arrays. 36 | The following alternatives were tested: 37 | * Manual 38 | * Buffer slice 39 | * Buffer subarray 40 | * TypedArray set 41 | Here we contruct a number of TypedArrays using the best alternatives from the previous test. 42 | 43 | *Test:* [Typed Array split comparison](http://jsperf.com/arraybuffer-split/3) 44 | 45 | *Results:* 46 | We run the tests both on Chrome 40.X and Chrome 41.X. This time we find that: 47 | * Manual test case is not up to the other alternatives. 48 | * The other alternatives had no diference, but it seams that subarray is getting more performant with new Chrome releases. 49 | 50 | Here you can see the results for [spliting TypedArrays](./splitComparison.png) 51 | -------------------------------------------------------------------------------- /spikes/2.3.1/copyComparison.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/arraybuffer-copy/4 2 | 3 | // HTML setup 4 | 5 | 70 | 71 | // Test case 1 - Manual 72 | var r = manualCopy(elements); 73 | if (elementsCount !== r.length || r.buffer === elements.buffer) { 74 | console.log('error manual'); 75 | } 76 | 77 | // Test case 2 - Buffer slice 78 | var r = bufferSliceCopy(elements); 79 | if (elementsCount !== r.length || r.buffer === elements.buffer) { 80 | console.log('error slice'); 81 | } 82 | 83 | // Test case 3 - Constructor 84 | var r = constructorFromArrayLikeCopy(elements); 85 | if (elementsCount !== r.length || r.buffer === elements.buffer) { 86 | console.log('error constructor'); 87 | } 88 | 89 | // Test case 4 - TypedArray set 90 | var r = typedSetCopy(elements); 91 | if (elementsCount !== r.length || r.buffer === elements.buffer) { 92 | console.log('error set'); 93 | } 94 | 95 | // Test case 5 - Buffer subarray 96 | var r = bufferSubarrayCopy(elements); 97 | if (elementsCount !== r.length || r.buffer === elements.buffer) { 98 | console.log('error subarray'); 99 | } 100 | 101 | // Test case 6 - Blob 102 | blobCopy(elements, function (result) { 103 | if (elementsCount === result.length) { 104 | deferred.resolve(); 105 | } else { 106 | console.log('error blob'); 107 | } 108 | }); -------------------------------------------------------------------------------- /spikes/2.3.1/copyComparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/2.3.1/copyComparison.png -------------------------------------------------------------------------------- /spikes/2.3.1/splitComparison.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/arraybuffer-split/3 2 | 3 | // HTML setup 4 | 5 | 64 | 65 | // Test case 1 - Manual 66 | var r = manualSplit(elements); 67 | if (parts !== r.length) { 68 | console.log('error manual'); 69 | } 70 | 71 | // Test case 2 - Buffer slice 72 | var r = bufferSliceSplit(elements); 73 | if (parts !== r.length) { 74 | console.log('error slice'); 75 | } 76 | 77 | // Test case 3 - Buffer subarray 78 | var r = bufferSubarraySplit(elements); 79 | if (parts !== r.length) { 80 | console.log('error subarray'); 81 | } 82 | 83 | // Test case 4 - TypedArray set 84 | var r = typedSetSplit(elements); 85 | if (parts !== r.length) { 86 | console.log('error set'); 87 | } -------------------------------------------------------------------------------- /spikes/2.3.1/splitComparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/2.3.1/splitComparison.png -------------------------------------------------------------------------------- /spikes/2.4.1/README.md: -------------------------------------------------------------------------------- 1 | 2.4.1 - Investigar alternativas para consolidar los resultados 2 | ----------------------- 3 | 4 | **merge.js** 5 | ----------------------- 6 | 7 | Verifies which is the best way to merge multiple TypedArrays into an unique Typed Array containing every element in order and without holes. 8 | The following alternatives were tested: 9 | * Non-native function: it creates a TypedArray with the total count of elements and then iterates all array's elements to copy them into the target. 10 | * TypedArray set function: it creates a TypedArray with the total count of elements and then inserts all the parts typed arrays using [TypedArray's set function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/set). 11 | * DataView set function: it creates a TypedArray with the total count of elements and the we iterate all the arrays to copy them into the target using [DataView's set function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) for 8 bit element. 12 | * Array like function: it creates a native Array object with the total count of elements and the we iterate all the arrays to copy them into the target. Finally this array is used to creare the final ArrayBuffer. 13 | 14 | *Test:* [Typed Arrays Merge](http://jsperf.com/typedarray-merge/2) 15 | 16 | *Result:* 17 | We run the tests both on Chrome 40.X and Chrome 41.X. Each one yields the same result: TypedArray's set function is the best choice of all. No other test case is up to it. That is why we will use it as our TypedArray merging method. 18 | 19 | Here you can see the results for [merging TypedArrays](./merge.png) 20 | -------------------------------------------------------------------------------- /spikes/2.4.1/merge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/2.4.1/merge.png -------------------------------------------------------------------------------- /spikes/2.4.1/sharedMerge.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/sharedtypedarray-merge 2 | 3 | // JavaScript setup 4 | 101 | 102 | // Test case - shared non-native function 103 | withNonNativeFunction(parts); 104 | 105 | // Test case - shared typedArray set function 106 | withTypedArraySet(parts); 107 | 108 | // Test case - shared array like function 109 | withArrayLikeFunction(parts); -------------------------------------------------------------------------------- /spikes/2.4.1/sharedMerge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/2.4.1/sharedMerge.png -------------------------------------------------------------------------------- /spikes/3.1.3/comparison/chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/3.1.3/comparison/chart.png -------------------------------------------------------------------------------- /spikes/3.1.3/comparison/chartShared.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/3.1.3/comparison/chartShared.png -------------------------------------------------------------------------------- /spikes/3.1.3/comparison/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PJS Map vs Serial Map 6 | 7 | 8 |
9 |
10 |
11 | 12 |
13 |
14 | 15 |
16 |
17 | 18 |
19 |
20 | 21 |
22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /spikes/3.1.3/comparison/jspBenckmarkResultTable.txt: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | Chrome 40.0.2214 3 | ---------------------------------------- 4 | xs pjs | serial | shared 5 | ---------------------------------------- 6 | 10E2 1,473 | 182,243 - 7 | 10E3 1,456 | 19,757 - 8 | 10E4 890 | 2,040 - 9 | 10E5 213 | 208 - 10 | 10E6 24 | 20 - 11 | ---------------------------------------- 12 | 13 | ---------------------------------------- 14 | Chrome 41.0.2272 15 | ---------------------------------------- 16 | xs pjs | serial | shared 17 | ---------------------------------------- 18 | 10E2 2,125 | 223,529 - 19 | 10E3 1,956 | 25,424 - 20 | 10E4 1,200 | 2,607 - 21 | 10E5 274 | 264 - 22 | 10E6 31 | 25 - 23 | ---------------------------------------- 24 | 25 | ---------------------------------------- 26 | Firefox 41.0 27 | ---------------------------------------- 28 | xs pjs | serial | shared 29 | ---------------------------------------- 30 | 10E2 1,454 | 130,572 385 31 | 10E3 1,431 | 14,697 421 32 | 10E4 752 | 1,469 357 33 | 10E5 166 | 151 188 34 | 10E6 20 | 14 32 35 | ---------------------------------------- -------------------------------------------------------------------------------- /spikes/3.1.3/comparison/pjs_map_seq_vs_par_jsperf.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/3.1.3/comparison/pjs_map_seq_vs_par_jsperf.xlsx -------------------------------------------------------------------------------- /spikes/3.1.3/comparison/pjs_map_ser_vs_par_no_jsperf.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/3.1.3/comparison/pjs_map_ser_vs_par_no_jsperf.xlsx -------------------------------------------------------------------------------- /spikes/3.1.3/comparison/setup.js: -------------------------------------------------------------------------------- 1 | var pjs = require('p-j-s'); 2 | pjs.init(); 3 | 4 | var generateElements = function (total) { 5 | var typed = new Uint32Array(total); 6 | for (var i = total; i > 0; i--){ 7 | typed[i - 1] = 0xdddddddd; 8 | } 9 | return typed; 10 | }; 11 | var xsLen = 10000000; 12 | var xs = generateElements(xsLen); 13 | var wrappedXs = pjs(xs); 14 | 15 | function mapper(pixel) { 16 | var r = pixel & 0xFF; 17 | var g = (pixel & 0xFF00) >> 8; 18 | var b = (pixel & 0xFF0000) >> 16; 19 | 20 | var noise_r = Math.random() * 0.5 + 0.5; 21 | var noise_g = Math.random() * 0.5 + 0.5; 22 | var noise_b = Math.random() * 0.5 + 0.5; 23 | 24 | var new_r = Math.max(Math.min(255, noise_r * ((r * 0.393) + (g * 0.769) + (b * 0.189)) + (1 - noise_r) * r), 0); 25 | var new_g = Math.max(Math.min(255, noise_g * ((r * 0.349) + (g * 0.686) + (b * 0.168)) + (1 - noise_g) * g), 0); 26 | var new_b = Math.max(Math.min(255, noise_b * ((r * 0.272) + (g * 0.534) + (b * 0.131)) + (1 - noise_b) * b), 0); 27 | 28 | return (pixel & 0xFF000000) + (new_b << 16) + (new_g << 8) + (new_r & 0xFF); 29 | }; 30 | 31 | function serialMap(pixels, l) { 32 | var result = new Uint32Array(l); 33 | for (var i = 0; i < l; i++) { 34 | var pixel = pixels[i]; 35 | var r = pixel & 0xFF; 36 | var g = (pixel & 0xFF00) >> 8; 37 | var b = (pixel & 0xFF0000) >> 16; 38 | 39 | var noise_r = Math.random() * 0.5 + 0.5; 40 | var noise_g = Math.random() * 0.5 + 0.5; 41 | var noise_b = Math.random() * 0.5 + 0.5; 42 | 43 | var new_r = Math.max(Math.min(255, noise_r * ((r * 0.393) + (g * 0.769) + (b * 0.189)) + (1 - noise_r) * r), 0); 44 | var new_g = Math.max(Math.min(255, noise_g * ((r * 0.349) + (g * 0.686) + (b * 0.168)) + (1 - noise_g) * g), 0); 45 | var new_b = Math.max(Math.min(255, noise_b * ((r * 0.272) + (g * 0.534) + (b * 0.131)) + (1 - noise_b) * b), 0); 46 | 47 | result[i] = (pixel & 0xFF000000) + (new_b << 16) + (new_g << 8) + (new_r & 0xFF); 48 | } 49 | return result; 50 | }; 51 | 52 | function runSerial() { 53 | console.time('serial'); 54 | var r = serialMap(xs, xs.length); 55 | console.timeEnd('serial'); 56 | if (0 === r.length) { 57 | console.log('error'); 58 | } 59 | }; 60 | 61 | function runPjs() { 62 | console.time('pjs'); 63 | wrappedXs.map(mapper, function (r) { 64 | console.timeEnd('pjs'); 65 | }); 66 | }; 67 | 68 | var samplesCount = 100; 69 | function runSerialInLoop() { 70 | var n = samplesCount; 71 | console.time('serial-loop'); 72 | for (var i = 0; i < n; i += 1) { 73 | var r = serialMap(xs, xs.length); 74 | } 75 | console.timeEnd('serial-loop'); 76 | }; 77 | 78 | function runPjsInLoop() { 79 | var n = samplesCount; 80 | var it = n; 81 | console.time('pjs-loop'); 82 | runInnerPjsInLoop(it); 83 | }; 84 | 85 | function runInnerPjsInLoop (it) { 86 | if (0 === it) { 87 | console.timeEnd('pjs-loop'); 88 | return; 89 | } 90 | wrappedXs.map(mapper, function (r) { 91 | runInnerPjsInLoop(it - 1); 92 | }); 93 | }; 94 | 95 | 96 | infoElements.innerHTML = 'Elements count = ' + xsLen; 97 | infoLoops.innerHTML = 'Loops count = ' + samplesCount; -------------------------------------------------------------------------------- /spikes/3.1.3/functionCache/functionCache.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/p-j-s-with-vs-without-function-cache/4 2 | 3 | // HTML setup 4 | 5 | 6 | 49 | 50 | // javascript setup 51 | __finish = function(){ 52 | deferred.resolve(); 53 | } 54 | 55 | // Test case 1 - Without function cache 56 | wrappedNoCacheXs.map(mapper).seq(mapCallback); 57 | 58 | // Test case 2 - Function cache 59 | wrappedCacheXs.map(mapper).seq(mapCallback); -------------------------------------------------------------------------------- /spikes/3.1.3/functionCache/functionCache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/3.1.3/functionCache/functionCache.png -------------------------------------------------------------------------------- /spikes/3.1.3/irhydra-noworkers/inlined-clamp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/3.1.3/irhydra-noworkers/inlined-clamp.png -------------------------------------------------------------------------------- /spikes/3.1.3/irhydra-noworkers/inlined-colorDistance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/3.1.3/irhydra-noworkers/inlined-colorDistance.png -------------------------------------------------------------------------------- /spikes/3.1.3/irhydra-noworkers/inlined-noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/3.1.3/irhydra-noworkers/inlined-noise.png -------------------------------------------------------------------------------- /spikes/3.1.3/irhydra-noworkers/inlined-processSepia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/3.1.3/irhydra-noworkers/inlined-processSepia.png -------------------------------------------------------------------------------- /spikes/3.1.3/irhydra-pjs-noinlined/ww-clamp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/3.1.3/irhydra-pjs-noinlined/ww-clamp.png -------------------------------------------------------------------------------- /spikes/3.1.3/irhydra-pjs-noinlined/ww-colorDistance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/3.1.3/irhydra-pjs-noinlined/ww-colorDistance.png -------------------------------------------------------------------------------- /spikes/3.1.3/irhydra-pjs-noinlined/ww-mapper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/3.1.3/irhydra-pjs-noinlined/ww-mapper.png -------------------------------------------------------------------------------- /spikes/3.1.3/irhydra-pjs-noinlined/ww-noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/3.1.3/irhydra-pjs-noinlined/ww-noise.png -------------------------------------------------------------------------------- /spikes/3.1.3/pjsMapInlining/pjsMapInlining.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/pjs-map-inlining/7 2 | 3 | // HTML setup 4 | 5 | 78 | 79 | // Javascript setup 80 | __finish = function () { 81 | deferred.resolve(); 82 | }; 83 | 84 | // Test case 1 - Not inlined map 85 | notInlinedTest(); 86 | 87 | // Test case 2 - Inlined map 88 | inlinedTest(); -------------------------------------------------------------------------------- /spikes/3.1.3/pjsMapInlining/pjsMapInlining.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/3.1.3/pjsMapInlining/pjsMapInlining.png -------------------------------------------------------------------------------- /spikes/3.2.3/comparison/jspBenchmark.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/pjs-filter-vs-serial 2 | 3 | // HTML preparation 4 | 5 | 76 | 77 | // Javascript setup 78 | __finish = function () { 79 | deferred.resolve(); 80 | }; 81 | 82 | // Test Case - serial filter 100 83 | runSerial(xs100); 84 | 85 | // Test Case - pjs filter 100 86 | runPjs(wrappedXs100); 87 | 88 | // Test Case - serial filter 1,000 89 | runSerial(xs1000); 90 | 91 | // Test Case - pjs filter 1,000 92 | runPjs(wrappedXs1000); 93 | 94 | // Test Case - serial filter 10,000 95 | runSerial(xs10000); 96 | 97 | // Test Case - pjs filter 10,000 98 | runPjs(wrappedXs10000); 99 | 100 | // Test Case - serial filter 100,000 101 | runSerial(xs100000); 102 | 103 | // Test Case - pjs filter 100,000 104 | runPjs(wrappedXs100000); 105 | 106 | // Test Case - serial filter 1,000,000 107 | runSerial(xs1000000); 108 | 109 | // Test Case - pjs filter 1,000,000 110 | runPjs(wrappedXs1000000); -------------------------------------------------------------------------------- /spikes/3.3.3/comparison/jspBenchmark.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/pjs-reduce-vs-serial 2 | 3 | // HTML preparation 4 | 5 | 72 | 73 | // Javascript setup 74 | __finish = function () { 75 | deferred.resolve(); 76 | }; 77 | 78 | // Test Case - serial reduce 100 79 | runSerial(xs100); 80 | 81 | // Test Case - pjs reduce 100 82 | runPjs(wrappedXs100); 83 | 84 | // Test Case - serial reduce 1,000 85 | runSerial(xs1000); 86 | 87 | // Test Case - pjs reduce 1,000 88 | runPjs(wrappedXs1000); 89 | 90 | // Test Case - serial reduce 10,000 91 | runSerial(xs10000); 92 | 93 | // Test Case - pjs reduce 10,000 94 | runPjs(wrappedXs10000); 95 | 96 | // Test Case - serial reduce 100,000 97 | runSerial(xs100000); 98 | 99 | // Test Case - pjs reduce 100,000 100 | runPjs(wrappedXs100000); 101 | 102 | // Test Case - serial reduce 1,000,000 103 | runSerial(xs1000000); 104 | 105 | // Test Case - pjs reduce 1,000,000 106 | runPjs(wrappedXs1000000); -------------------------------------------------------------------------------- /spikes/6.1/cloningComparison/jspBenchmark.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/additional-clonningdata-comparison/3 2 | 3 | // HTML setup 4 | 96 | 97 | // JavaScript setup 98 | __finish = function () { 99 | deferred.resolve(); 100 | }; 101 | 102 | 103 | // Test case 1 - sending clonned context 104 | worker.postMessage({ctx: ctx, pack: pack}, [pack.buffer]); 105 | 106 | // Test case 2 - sending stringified context 107 | strfyWorker.postMessage({ctx: strfyCtx, pack: pack}, [pack.buffer]); -------------------------------------------------------------------------------- /spikes/6.1/cloningComparison/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/6.1/cloningComparison/result.png -------------------------------------------------------------------------------- /spikes/6.1/cloningComparison/resultLong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/6.1/cloningComparison/resultLong.png -------------------------------------------------------------------------------- /spikes/6.1/context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/6.1/context.png -------------------------------------------------------------------------------- /spikes/6.1/mapComparison/consoleError.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/6.1/mapComparison/consoleError.png -------------------------------------------------------------------------------- /spikes/6.1/mapComparison/error.js: -------------------------------------------------------------------------------- 1 | function createTypedArray (length) { 2 | var result = new Uint32Array(length); 3 | for (var i = length; i > 0; i--){ 4 | result[i - 1] = Math.floor(Math.random() * 10000) 5 | + Math.floor(Math.random() * 10000) 6 | + Math.floor(Math.random() * 10000) 7 | + Math.floor(Math.random() * 10000); 8 | } 9 | return result; 10 | }; 11 | 12 | function createPackage(typedArray) { 13 | return { 14 | index: 2, 15 | buffer: typedArray.buffer, 16 | operations: [{ 17 | name: 'map', 18 | args: ['e'], 19 | code: '{ return e * 4 + Math.rand() % 1000 + 1 / e; }' 20 | }], 21 | elementsType: 'Uint32Array' 22 | }; 23 | }; 24 | 25 | var elements = createTypedArray(1000000); 26 | var pack = createPackage(elements); 27 | 28 | var strFunc = (function (e) { return (Math.rand() % 1000) + e * 3; }).toString(); 29 | var mapCtx = new Map(); 30 | mapCtx.set('func', { 31 | __isFunction: true, 32 | code: strFunc 33 | }); 34 | mapCtx.set('data', 'daaaaaata'); 35 | 36 | var d = new Map(); 37 | d.set('ctx', mapCtx); 38 | d.set('pack', pack); 39 | console.log(d); 40 | JSON.stringify(d); -------------------------------------------------------------------------------- /spikes/6.1/mapComparison/jspError.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/6.1/mapComparison/jspError.png -------------------------------------------------------------------------------- /spikes/6.1/sendingAdditionalsSeqVsPar/jspBenchmark-all-browsers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/6.1/sendingAdditionalsSeqVsPar/jspBenchmark-all-browsers.png -------------------------------------------------------------------------------- /spikes/6.1/sendingAdditionalsSeqVsPar/jspBenchmark.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/additional-sendingdata-comparison/3 2 | 3 | // HTML setup 4 | 83 | 84 | // JavaScript setup 85 | __finish = function () { 86 | deferred.resolve(); 87 | }; 88 | 89 | 90 | // Test case 1 - sending together 91 | worker.postMessage({ctx: ctx, pack: pack}, [pack.buffer]); 92 | 93 | // Test case 2 - sending separated 94 | worker.postMessage({ctx: ctx}); 95 | worker.postMessage({pack: pack}, [pack.buffer]); -------------------------------------------------------------------------------- /spikes/6.1/sendingAdditionalsSeqVsPar/jspBenchmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/6.1/sendingAdditionalsSeqVsPar/jspBenchmark.png -------------------------------------------------------------------------------- /spikes/6.2/inlinedVsGlobalInlined/jspBenchmark.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/global-ctx-inlined-func/3 2 | 3 | // HTML setup 4 | 5 | 40 | 41 | // JavaScript Setup 42 | var __finish = function (result) { 43 | if (!result) { 44 | console.log('error'); 45 | } 46 | deferred.resolve(); 47 | }; 48 | 49 | // Test case 1 - map with function instance 50 | wrappedXs.map(inlinedMapper).seq(function (result) { 51 | __finish(); 52 | }); 53 | 54 | // Test case 2 - map with function key 55 | wrappedXs.map('x').seq(function (result) { 56 | __finish(); 57 | }); -------------------------------------------------------------------------------- /spikes/6.2/inlinedVsGlobalInlined/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/6.2/inlinedVsGlobalInlined/result.png -------------------------------------------------------------------------------- /spikes/6.2/inlinedVsGlobalNotInlined/jspBenchmark.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/global-ctx-no-inlined-vs-inlined/2 2 | 3 | // HTML setup 4 | 5 | 76 | 77 | // JavaScript Setup 78 | var __finish = function (result) { 79 | if (!result) { 80 | console.log('error'); 81 | } 82 | deferred.resolve(); 83 | }; 84 | 85 | // Test case 1 - map with inlining 86 | wrappedXs.map(inlinedMapper).seq(function (result) { 87 | __finish(result); 88 | }); 89 | 90 | // Test case 2 - map with global not-inlining 91 | wrappedXs.map('contextMapper').seq(function (result) { 92 | __finish(result); 93 | }); -------------------------------------------------------------------------------- /spikes/6.2/inlinedVsGlobalNotInlined/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/6.2/inlinedVsGlobalNotInlined/result.png -------------------------------------------------------------------------------- /spikes/6.2/noInlinedVsGlobalNotInlinedWithContext/jspBenchmark.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/global-ctx-no-inlined-func/2 2 | 3 | // HTML setup 4 | 5 | 64 | 65 | // JavaScript Setup 66 | var __finish = function (result) { 67 | if (!result) { 68 | console.log('error'); 69 | } 70 | deferred.resolve(); 71 | }; 72 | 73 | // Test case 1 - map with no-inlined 74 | wrappedXs.map(contextMapper, ctx).seq(function (result) { 75 | __finish(result); 76 | }); 77 | 78 | // Test case 2 - map with global no-inlined 79 | wrappedXs.map('contextMapper', ctx).seq(function (result) { 80 | __finish(result); 81 | }); -------------------------------------------------------------------------------- /spikes/6.2/noInlinedVsGlobalNotInlinedWithContext/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/6.2/noInlinedVsGlobalNotInlinedWithContext/result.png -------------------------------------------------------------------------------- /spikes/6.2/noInlinedVsGlobalWithGlobalContext/jspBenchmark.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/all-global-ctx-no-inlined-func/2 2 | 3 | // HTML setup 4 | 5 | 61 | 62 | // JavaScript Setup 63 | var __finish = function (result) { 64 | if (!result) { 65 | console.log('error'); 66 | } 67 | deferred.resolve(); 68 | }; 69 | 70 | // Test case 1 - map with no-inlined 71 | wrappedXs.map(contextMapper).seq(function (result) { 72 | __finish(result); 73 | }); 74 | 75 | // Test case 2 - map with global no-inlined 76 | wrappedXs.map('contextMapper').seq(function (result) { 77 | __finish(result); 78 | }); -------------------------------------------------------------------------------- /spikes/6.2/noInlinedVsGlobalWithGlobalContext/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/6.2/noInlinedVsGlobalWithGlobalContext/result.png -------------------------------------------------------------------------------- /spikes/6.2/readme.md: -------------------------------------------------------------------------------- 1 | # js pref benchmarks 2 | 3 | Here you can find: 4 | * Inlined function vs global context's inlined function 5 | * Not inlined functions vs global context's not inlined function 6 | > * Sending additional elements on local context 7 | > * Adding additional elements on global context 8 | * Inlined function vs global context's not-inlined function with adding additional elements on global context 9 | 10 | ## Inlined function vs global context's inlined function 11 | 12 | This test can be found at: ./inlinedVsGlobalInlined 13 | 14 | This benchmark tests pjs' map with a function sent each time it is called vs a function sent to global context on pjs' initialization. 15 | 16 | ``` 17 | pjs(...).map(mapper).seq(...); 18 | ``` 19 | vs 20 | ``` 21 | pjs.updateContext({ 22 | mapper: mapper 23 | }); 24 | ... 25 | pjs(...).map('mapper').seq(...); 26 | ``` 27 | 28 | ## Not inlined functions vs global context's not inlined function 29 | 30 | ### Sending additional elements on local context 31 | 32 | This test can be found at: ./noInlinedVsGlobalNotInlinedWithContext 33 | 34 | This benchmark tests pjs' map with a not inlined function sent each time it is called vs a function sent to global context on pjs' initialization. Both times a context is added to each pjs' map call. 35 | 36 | ``` 37 | var ctx = { 38 | func1: function () {...}, 39 | func2: function () {...}, 40 | obj1: {...}, 41 | obj2: {...} 42 | }; 43 | pjs(...).map(mapper, ctx).seq(...); 44 | ``` 45 | vs 46 | ``` 47 | pjs.updateContext({ 48 | mapper: mapper 49 | }); 50 | ... 51 | var ctx = { 52 | func1: function () {...}, 53 | func2: function () {...}, 54 | obj1: {...}, 55 | obj2: {...} 56 | }; 57 | pjs(...).map('mapper', ctx).seq(...); 58 | ``` 59 | 60 | ### Adding additional elements on global context 61 | 62 | This test can be found at: ./noInlinedVsGlobalWithGlobalContext 63 | 64 | This benchmark tests pjs' map with a not inlined function sent each time it is called vs a function sent to global context on pjs' initialization. Both times a context is added to global context. 65 | 66 | ``` 67 | pjs.updateContext({ 68 | func1: function () {...}, 69 | func2: function () {...}, 70 | obj1: {...}, 71 | obj2: {...} 72 | }); 73 | pjs(...).map(mapper).seq(...); 74 | ``` 75 | vs 76 | ``` 77 | pjs.updateContext({ 78 | mapper: mapper, 79 | func1: function () {...}, 80 | func2: function () {...}, 81 | obj1: {...}, 82 | obj2: {...} 83 | }); 84 | ... 85 | pjs(...).map('mapper').seq(...); 86 | ``` 87 | 88 | ## Inlined function vs global context's not-inlined function with adding additional elements on global context 89 | 90 | This test can be found at: ./noInlinedVsGlobalNotInlinedWithContext 91 | 92 | This benchmark tests pjs' map with an inlined function sent each time it is called vs a function sent to global context on pjs' initialization with additional elements added to global context. 93 | 94 | ``` 95 | pjs(...).map(mapper).seq(...); 96 | ``` 97 | vs 98 | ``` 99 | pjs.updateContext({ 100 | mapper: mapper, 101 | func1: function () {...}, 102 | func2: function () {...}, 103 | obj1: {...}, 104 | obj2: {...} 105 | }); 106 | ... 107 | pjs(...).map('mapper').seq(...); 108 | ``` 109 | -------------------------------------------------------------------------------- /spikes/6.2/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/6.2/result.png -------------------------------------------------------------------------------- /spikes/shared/2.2.1/jspBenchmark.longSendCode.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/shared-function-transfer 2 | 3 | // HTML setup 4 | 106 | 107 | // JavaScript setup 108 | __finish = function () { 109 | deferred.resolve(); 110 | }; 111 | 112 | // Test case 1 - Copy 113 | copy(); 114 | 115 | // Test case 2 - Transferrable 116 | transfer(); -------------------------------------------------------------------------------- /spikes/shared/2.2.1/jspBenchmarklongSendCode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/shared/2.2.1/jspBenchmarklongSendCode.png -------------------------------------------------------------------------------- /spikes/shared/3.1.3/results.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjsteam/pjs/f0eb51307f2c89f112e87d7086189fc5eea05fb6/spikes/shared/3.1.3/results.xlsx -------------------------------------------------------------------------------- /spikes/shared/paper-bechmarks/section-4.1/jspBenchmark-long.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/longer-transferrable-vs-cloning/2 -------------------------------------------------------------------------------- /spikes/shared/paper-bechmarks/section-4.1/jspBenchmark-short.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/transferrable-vs-cloning/2 -------------------------------------------------------------------------------- /spikes/shared/paper-bechmarks/section-5.1/jspBenchmark.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/pjs-serialization-comparison/2 -------------------------------------------------------------------------------- /spikes/shared/paper-bechmarks/section-5.2.1/jspBenchmark-long.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/pjs-serialization-long/3 -------------------------------------------------------------------------------- /spikes/shared/paper-bechmarks/section-5.2.1/jspBenchmark-short.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/pjs-serialization/3 -------------------------------------------------------------------------------- /spikes/shared/paper-bechmarks/section-5.2.2/jspBenchmark.js: -------------------------------------------------------------------------------- 1 | // http://jsperf.com/pjs-encoding/2 -------------------------------------------------------------------------------- /src/chain_context.js: -------------------------------------------------------------------------------- 1 | var contextUtils = require('./context'); 2 | var extend = require("xtend"); 3 | 4 | var chainContext = module.exports = {}; 5 | 6 | chainContext.serializeChainContext = function (chainContext) { 7 | return JSON.stringify(chainContext); 8 | }; 9 | 10 | chainContext.deserializeChainContext = function (chainContext) { 11 | return JSON.parse(chainContext); 12 | }; 13 | 14 | chainContext.extendChainContext = function (currentIndex, localContext, chainContext) { 15 | if (!localContext && !chainContext) { 16 | return undefined; 17 | } 18 | if (!localContext) { 19 | return extend(chainContext, undefined); 20 | } 21 | var nextContext = {}; 22 | nextContext[currentIndex] = contextUtils.serializeFunctions(localContext); 23 | if (!chainContext) { 24 | return nextContext; 25 | } 26 | return extend(chainContext, nextContext); 27 | }; 28 | 29 | chainContext.contextFromChainContext = function (currentIndex, chainContext) { 30 | if (chainContext && chainContext[currentIndex]) { 31 | return contextUtils.deserializeFunctions(chainContext[currentIndex]); 32 | } 33 | return undefined; 34 | }; 35 | -------------------------------------------------------------------------------- /src/context.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils'); 2 | 3 | var ctx = {}; 4 | module.exports = ctx; 5 | 6 | ctx.deserializeFunctions = function(obj){ 7 | return Object.keys(obj).reduce(function(c,v){ 8 | var value = obj[v]; 9 | 10 | if (utils.isObject(value)){ 11 | if (value.__isFunction){ 12 | c[v] = deserializeFunction(value); 13 | } else { 14 | ctx.deserializeFunctions(value); 15 | } 16 | } 17 | 18 | return c; 19 | }, obj); 20 | }; 21 | 22 | ctx.serializeFunctions = function (obj) { 23 | var res; 24 | if (obj) { 25 | res = Object.keys(obj).reduce(function(c,v){ 26 | var value = obj[v]; 27 | if (utils.isFunction(value)){ 28 | c[v] = serializeFunction(value); 29 | } else if (utils.isObject(value)){ 30 | c[v] = ctx.serializeFunctions(value); 31 | } else { 32 | c[v] = value; 33 | } 34 | 35 | return c; 36 | }, {}); 37 | } 38 | return res; 39 | }; 40 | 41 | function serializeFunction (value) { 42 | var parsed = utils.parseFunction(value); 43 | return { 44 | __isFunction: true, 45 | args: parsed.args, 46 | code: parsed.body 47 | }; 48 | } 49 | 50 | function deserializeFunction(value) { 51 | return utils.createFunction(value.args, value.code); 52 | } -------------------------------------------------------------------------------- /src/context_update_packager.js: -------------------------------------------------------------------------------- 1 | var errors = require('./errors'); 2 | var contextUtils = require('./context'); 3 | 4 | var ContextUpdatePackager = module.exports = function (parts) { 5 | if (!parts) { 6 | throw new errors.InvalidArgumentsError(errors.messages.INVALID_PARTS); 7 | } 8 | this.parts = parts; 9 | }; 10 | 11 | ContextUpdatePackager.prototype.generatePackages = function (contextUpdate) { 12 | if (!contextUpdate) { 13 | throw new errors.InvalidArgumentsError(errors.messages.INVALID_CONTEXT); 14 | } 15 | 16 | var parts = new Array(this.parts); 17 | 18 | for (var i = 0; i < this.parts; i++) { 19 | parts[i] = { 20 | index: i, 21 | contextUpdate: JSON.stringify(contextUtils.serializeFunctions(contextUpdate)) 22 | }; 23 | } 24 | 25 | return parts; 26 | }; 27 | -------------------------------------------------------------------------------- /src/errors.js: -------------------------------------------------------------------------------- 1 | var errors = module.exports = {}; 2 | 3 | var InvalidOperationError = function InvalidOperationError(message) { 4 | this.name = 'InvalidOperationError'; 5 | this.message = message || 'Invalid Operation'; 6 | }; 7 | 8 | InvalidOperationError.prototype = new Error(); 9 | InvalidOperationError.prototype.constructor = InvalidOperationError; 10 | errors.InvalidOperationError = InvalidOperationError; 11 | 12 | 13 | var InvalidArgumentsError = function InvalidArgumentsError(message) { 14 | this.name = 'InvalidArgumentsError'; 15 | this.message = message || 'Invalid Arguments'; 16 | }; 17 | 18 | InvalidArgumentsError.prototype = new Error(); 19 | InvalidArgumentsError.prototype.constructor = InvalidArgumentsError; 20 | errors.InvalidArgumentsError = InvalidArgumentsError; 21 | 22 | var WorkerError = function WorkerError(message){ 23 | this.name = 'WorkerError'; 24 | this.message = message || 'An unknown error ocurred in the worker'; 25 | }; 26 | 27 | WorkerError.prototype = new Error(); 28 | WorkerError.prototype.constructor = WorkerError; 29 | errors.WorkerError = WorkerError; 30 | 31 | errors.messages = { 32 | CONSECUTIVE_INITS: 'You should not recall init if the library is already initialized.', 33 | TERMINATE_WITHOUT_INIT: 'You should not terminate pjs if it was not initialized before.', 34 | PARTITIONER_ARGUMENT_IS_NOT_TYPED_ARRAY: 'Expected TypedArray argument.', 35 | ZERO_ARRAYS_TO_MERGE: 'Zero arrays to merge. Provide at least one.', 36 | INVALID_PARTS: 'Invalid number of parts.', 37 | INVALID_CONTEXT: 'Invalid context.', 38 | PART_ALREADY_COLLECTED: 'Tried to collect part {0} more than once', 39 | MISSING_CODE_OR_PATH: 'Missing "code" or "functionPath" argument to package.', 40 | INVALID_IDENTITY_CODE: 'Invalid identity code argument to package.', 41 | INVALID_ELEMENTS: 'Invalid number of elements to package.', 42 | INVALID_PACKAGE_INDEX: 'Package index should be not negative and less than {0}.', 43 | INVALID_TYPED_ARRAY: 'Invalid argument. It should be of TypedArray.', 44 | INVALID_OPERATION: 'Invalid pjs operation. Possible values are \'filter\', \'map\' or \'reduce\'.', 45 | INVALID_OPERATIONS: 'Invalid operation chain sent to JobPackager. Either undefined or empty.', 46 | MISSING_SEED: 'Missing Seed argument for reduce operation packaging.', 47 | MISSING_IDENTITY: 'Missing Identity argument for reduce operation packaging.', 48 | INVALID_CHAINING_OPERATION: 'Can not perform more chaining after reduce operation.' 49 | }; 50 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | var errors = require('./errors'); 2 | var utils = require('./utils'); 3 | var workers = require('./workers'); 4 | var WrappedTypedArray = require('./wrapped_typed_array'); 5 | var ContextUpdatePackager = require('./context_update_packager'); 6 | var mutableExtend = require('xtend/mutable'); 7 | var pjs; 8 | 9 | var initialized = false; 10 | var globalContext = {}; 11 | 12 | function wrap(typedArray){ 13 | if (!utils.isTypedArray(typedArray)) { 14 | throw new errors.InvalidArgumentsError(errors.messages.INVALID_TYPED_ARRAY); 15 | } 16 | return new WrappedTypedArray(typedArray, workers.length, globalContext); 17 | } 18 | 19 | function init(options) { 20 | if (initialized) { 21 | throw new errors.InvalidOperationError(errors.messages.CONSECUTIVE_INITS); 22 | } 23 | 24 | options = options || {}; 25 | var cpus = navigator.hardwareConcurrency || options.maxWorkers || 1; 26 | var maxWorkers = options.maxWorkers || cpus; 27 | var workersCount = Math.min(maxWorkers, cpus); 28 | workers.init(workersCount); 29 | 30 | var config = { 31 | get workers () { 32 | return workers.length; 33 | } 34 | }; 35 | 36 | utils.getter(pjs, 'config', config); 37 | utils.getter(pjs, 'contextUpdatePackager', new ContextUpdatePackager(workers.length)); 38 | 39 | initialized = true; 40 | } 41 | 42 | function updateContext(updates, done){ 43 | var self = this; 44 | return new Promise(function (resolve, reject) { 45 | var packs = self.contextUpdatePackager.generatePackages(updates); 46 | workers.sendPacks(packs, function(err){ 47 | if (err) { if (done) { done(err); } else { reject(err); } return; } 48 | mutableExtend(globalContext, updates); 49 | if (done) { 50 | done(); 51 | } else { 52 | resolve(); 53 | } 54 | }); 55 | }); 56 | } 57 | 58 | function terminate() { 59 | if (!initialized) { 60 | throw new errors.InvalidOperationError(errors.messages.TERMINATE_WITHOUT_INIT); 61 | } 62 | 63 | workers.terminate(); 64 | 65 | globalContext = {}; 66 | delete pjs.config; 67 | delete pjs.contextUpdatePackager; 68 | 69 | initialized = false; 70 | } 71 | 72 | pjs = module.exports = wrap; 73 | 74 | pjs.init = init; 75 | pjs.terminate = terminate; 76 | pjs.updateContext = updateContext; -------------------------------------------------------------------------------- /src/job_packager.js: -------------------------------------------------------------------------------- 1 | var errors = require('./errors'); 2 | var utils = require('./utils'); 3 | var Partitioner = require('./typed_array_partitioner'); 4 | var contextSerializer = require('./chain_context'); 5 | 6 | var operation_names = require('./operation_names'); 7 | operation_names = Object.keys(operation_names).map(function (k) { 8 | return operation_names[k]; 9 | }); 10 | 11 | var JobPackager = module.exports = function (parts, elements) { 12 | if (!parts) { 13 | throw new errors.InvalidArgumentsError(errors.messages.INVALID_PARTS); 14 | } 15 | if (!elements) { 16 | throw new errors.InvalidArgumentsError(errors.messages.INVALID_ELEMENTS); 17 | } 18 | this.parts = parts; 19 | this.elements = elements; 20 | }; 21 | 22 | JobPackager.prototype.generatePackages = function (operations, chainContext) { 23 | if (!(operations && operations.length)){ 24 | throw new errors.InvalidArgumentsError(errors.messages.INVALID_OPERATIONS); 25 | } 26 | 27 | var parsedOperations = operations.map(function(op){ 28 | if (!(op.code || op.functionPath)) { 29 | throw new errors.InvalidArgumentsError(errors.messages.MISSING_CODE_OR_PATH); 30 | } 31 | 32 | if (!op.name || -1 === operation_names.indexOf(op.name)) { 33 | throw new errors.InvalidArgumentsError(errors.messages.INVALID_OPERATION); 34 | } 35 | 36 | if (!op.functionPath){ 37 | var parsed = utils.parseFunction(op.code.toString()); 38 | 39 | return { 40 | identity: op.identity, 41 | args: parsed.args, 42 | code: parsed.body, 43 | name: op.name 44 | }; 45 | } 46 | 47 | return { 48 | identity: op.identity, 49 | name: op.name, 50 | functionPath: op.functionPath 51 | }; 52 | }); 53 | 54 | var elementsType = utils.getTypedArrayType(this.elements); 55 | var partitioner = new Partitioner(this.parts); 56 | var partitionedElements = partitioner.partition(this.elements); 57 | var isShared = utils.isSharedArray(this.elements); 58 | var strfyCtx = contextSerializer.serializeChainContext(chainContext); 59 | return partitionedElements.map(function (partitionedElement, index) { 60 | var sourceBuffer, buffer, start, to; 61 | if (isShared) { 62 | sourceBuffer = partitionedElement.sourceArray.buffer; 63 | buffer = partitionedElement.sharedArray.buffer; 64 | start = partitionedElement.from; 65 | to = partitionedElement.to; 66 | } else { 67 | buffer = partitionedElement.buffer; 68 | start = 0; 69 | to = partitionedElement.length; 70 | } 71 | return { 72 | index: index, 73 | start: start, 74 | end: to, 75 | sourceBuffer: sourceBuffer, 76 | buffer: buffer, 77 | operations: parsedOperations, 78 | elementsType: elementsType, 79 | ctx: strfyCtx 80 | }; 81 | }); 82 | }; 83 | -------------------------------------------------------------------------------- /src/operation_names.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | MAP: 'map', 3 | FILTER: 'filter', 4 | REDUCE: 'reduce' 5 | }; -------------------------------------------------------------------------------- /src/operation_packager.js: -------------------------------------------------------------------------------- 1 | var errors = require('./errors'); 2 | var utils = require('./utils'); 3 | var operation_names = require('./operation_names'); 4 | operation_names = Object.keys(operation_names).map(function (k) { 5 | return operation_names[k]; 6 | }); 7 | 8 | module.exports = function (name, code, seed, identity, identityCode) { 9 | if (!name || -1 === operation_names.indexOf(name)) { 10 | throw new errors.InvalidArgumentsError(errors.messages.INVALID_OPERATION); 11 | } 12 | if (!code) { 13 | throw new errors.InvalidArgumentsError(errors.messages.INVALID_CODE); 14 | } 15 | if (name === 'reduce' && typeof identityCode === 'undefined') { 16 | throw new errors.InvalidArgumentsError(errors.messages.INVALID_IDENTITY_CODE); 17 | } 18 | if (name === 'reduce' && typeof seed === 'undefined') { 19 | throw new errors.InvalidArgumentsError(errors.messages.MISSING_SEED); 20 | } 21 | if (name === 'reduce' && typeof identity === 'undefined') { 22 | throw new errors.InvalidArgumentsError(errors.messages.MISSING_IDENTITY); 23 | } 24 | 25 | var toReturn = { 26 | name: name, 27 | seed: seed, 28 | identity: identity, 29 | identityCode: identityCode 30 | }; 31 | 32 | var key = utils.isFunction(code) ? 'code' : 'functionPath'; 33 | 34 | toReturn[key] = code; 35 | 36 | return toReturn; 37 | }; 38 | -------------------------------------------------------------------------------- /src/result_collector.js: -------------------------------------------------------------------------------- 1 | var errors = require('./errors.js'); 2 | var utils = require('./utils.js'); 3 | 4 | var Collector = module.exports = function (parts, cb) { 5 | if (!parts) { 6 | throw new errors.InvalidArgumentsError(errors.messages.INVALID_PARTS); 7 | } 8 | 9 | if (!cb){ 10 | throw new errors.InvalidArgumentsError(errors.messages.MISSING_CALLBACK); 11 | } 12 | this.cb = cb; 13 | this.parts = parts; 14 | this.collected = new Array(parts); 15 | this.completed = 0; 16 | this.error = null; 17 | }; 18 | 19 | Collector.prototype.onError = function(message){ 20 | if (!this.error){ 21 | this.error = new errors.WorkerError(message); 22 | } 23 | this.updateCompleted(); 24 | }; 25 | 26 | Collector.prototype.onPart = function (data){ 27 | if (this.error) { 28 | // we don't care about succesful results if an error ocurred 29 | return this.updateCompleted(); 30 | } 31 | 32 | if (this.collected[data.index]) { 33 | throw new errors.InvalidArgumentsError( 34 | utils.format(errors.messages.PART_ALREADY_COLLECTED, data.index)); 35 | } 36 | this.collected[data.index] = {value: data.value, start: data.start, newEnd: data.newEnd} ; 37 | this.updateCompleted(); 38 | }; 39 | 40 | Collector.prototype.updateCompleted = function(){ 41 | if (++this.completed === this.parts){ 42 | if (this.error) { 43 | return this.cb(this.error); 44 | } 45 | 46 | return this.cb(null, this.collected); 47 | } 48 | }; -------------------------------------------------------------------------------- /src/typed_array_merger.js: -------------------------------------------------------------------------------- 1 | var errors = require('./errors.js'); 2 | 3 | module.exports = function merge(arrays){ 4 | if (!arrays.length) { throw new errors.InvalidArgumentsError( 5 | errors.messages.ZERO_ARRAYS_TO_MERGE); 6 | } 7 | var first = arrays[0]; 8 | 9 | if (arrays.length === 1){ 10 | return first; 11 | } 12 | 13 | var total = arrays.reduce(function(c,a) { return c + a.length; }, 0); 14 | var result = new first.constructor(total); 15 | var start = 0; 16 | 17 | for (var i = 0; i < arrays.length; i++) { 18 | var array = arrays[i]; 19 | result.set(array, start); 20 | start += array.length; 21 | } 22 | 23 | return result; 24 | }; -------------------------------------------------------------------------------- /src/typed_array_partitioner.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils.js'); 2 | var errors = require('./errors.js'); 3 | 4 | var typedArraySlice = function (array, from, to) { 5 | var subXs = array.subarray(from, to); 6 | return new array.constructor(subXs); 7 | }; 8 | 9 | var Partitioner = module.exports = function (parts) { 10 | if (!parts) { 11 | throw new errors.InvalidArgumentsError(errors.messages.INVALID_PARTS); 12 | } 13 | this.parts = parts; 14 | }; 15 | 16 | Partitioner.prototype.constructor = Partitioner; 17 | 18 | Partitioner.prototype.partition = function (array) { 19 | this.__validateTypedArray(array); 20 | return this.__doPartition(array, this); 21 | }; 22 | 23 | Partitioner.prototype.__validateTypedArray = function (array) { 24 | if (!utils.isTypedArray(array)) { 25 | var message = utils.format('Invalid type {0}. {1}', array, errors.messages.PARTITIONER_ARGUMENT_IS_NOT_TYPED_ARRAY); 26 | throw new errors.InvalidArgumentsError(message); 27 | } 28 | }; 29 | 30 | Partitioner.prototype.__doPartition = function (array) { 31 | var parts = this.parts; 32 | var elementsCount = array.length; 33 | var subElementsCount = Math.floor(elementsCount / parts) | 0; 34 | var from = 0; 35 | var to = 0; 36 | var isShared = utils.isSharedArray(array); 37 | var sharedArray; 38 | var arrays = new Array(parts); 39 | if (isShared) { 40 | sharedArray = utils.duplicateTypedArray(array); 41 | } 42 | for (var i = 0; i < parts; i++) { 43 | if (parts - 1 === i) { 44 | to = elementsCount; 45 | } else { 46 | to += subElementsCount; 47 | } 48 | if (isShared) { 49 | arrays[i] = { from:from, to: to, sharedArray: sharedArray, sourceArray: array}; 50 | } else { 51 | arrays[i] = typedArraySlice(array, from, to); 52 | } 53 | from += subElementsCount; 54 | } 55 | return arrays; 56 | }; -------------------------------------------------------------------------------- /src/worker.js: -------------------------------------------------------------------------------- 1 | var worker_core = require('./worker_core'); 2 | 3 | module.exports = function (self) { 4 | self.addEventListener('message', function (event){ 5 | var result = worker_core(event); 6 | self.postMessage(result.message, result.transferables); 7 | }); 8 | }; -------------------------------------------------------------------------------- /src/workers.js: -------------------------------------------------------------------------------- 1 | var ResultCollector = require('./result_collector'); 2 | var work = require('webworkify'); 3 | 4 | var workers = []; 5 | var packQueue; 6 | var sentPack; 7 | 8 | Object.defineProperty(module.exports, 'length', { 9 | get: function(){ 10 | return workers.length; 11 | } 12 | }); 13 | 14 | module.exports.init = function(workersCount){ 15 | while (workersCount--) { 16 | var worker = work(require('./worker.js')); 17 | workers.push(worker); 18 | } 19 | packQueue = []; 20 | sentPack = undefined; 21 | }; 22 | 23 | module.exports.terminate = function(){ 24 | workers.forEach(function(w){ 25 | w.terminate(); 26 | }); 27 | 28 | workers = []; 29 | packQueue = undefined; 30 | sentPack = undefined; 31 | }; 32 | 33 | module.exports.sendPacks = function(packs, callback){ 34 | packQueue.push({ 35 | packs: packs, 36 | callback: callback 37 | }); 38 | sendPacksIfNeeded(); 39 | }; 40 | 41 | function sendPacksIfNeeded() { 42 | if (!sentPack) { 43 | sentPack = packQueue.shift(); 44 | if (sentPack) { 45 | doSendPacks(); 46 | } 47 | } 48 | } 49 | 50 | function doSendPacks() { 51 | var callback = sentPack.callback; 52 | var packs = sentPack.packs; 53 | var collector = new ResultCollector(workers.length, function (err, result) { 54 | sentPack = undefined; 55 | callback(err, result); 56 | sendPacksIfNeeded(); 57 | }); 58 | 59 | packs.forEach(function(pack, index){ 60 | var onMessageHandler = function (event){ 61 | event.target.removeEventListener('error', onErrorHandler); 62 | event.target.removeEventListener('message', onMessageHandler); 63 | return collector.onPart(event.data); 64 | }; 65 | 66 | var onErrorHandler = function (event){ 67 | event.preventDefault(); 68 | event.target.removeEventListener('error', onErrorHandler); 69 | event.target.removeEventListener('message', onMessageHandler); 70 | return collector.onError(event.message); 71 | }; 72 | 73 | workers[index].addEventListener('error', onErrorHandler); 74 | workers[index].addEventListener('message', onMessageHandler); 75 | 76 | if (pack.buffer){ 77 | if (pack.sourceBuffer) { 78 | workers[index].postMessage(pack, [ pack.sourceBuffer, pack.buffer ]); 79 | } else { 80 | workers[index].postMessage(pack, [ pack.buffer ]); 81 | } 82 | } else { 83 | workers[index].postMessage(pack); 84 | } 85 | }); 86 | } -------------------------------------------------------------------------------- /src/wrapped_typed_array.js: -------------------------------------------------------------------------------- 1 | var operation_names = require('./operation_names'); 2 | var Chain = require('./chain'); 3 | var operation_packager = require('./operation_packager'); 4 | var contextUtils = require('./chain_context'); 5 | var utils = require('./utils'); 6 | 7 | var WrappedTypedArray = function (source, parts, globalContext) { 8 | this.source = source; 9 | this.parts = parts; 10 | this.globalContext = globalContext; 11 | }; 12 | 13 | WrappedTypedArray.prototype.map = function(mapper, context) { 14 | return this.__operation(operation_names.MAP, mapper, context); 15 | }; 16 | 17 | WrappedTypedArray.prototype.filter = function(predicate, context) { 18 | return this.__operation(operation_names.FILTER, predicate, context); 19 | }; 20 | 21 | WrappedTypedArray.prototype.reduce = function(reducer, seed, identityReducer, identity, context) { 22 | if (!utils.isFunction(identityReducer)) { 23 | context = identity; 24 | identity = identityReducer; 25 | identityReducer = reducer; 26 | } 27 | return this.__operation(operation_names.REDUCE, reducer, context, seed, identity, identityReducer); 28 | }; 29 | 30 | WrappedTypedArray.prototype.__operation = function(name, code, localContext, seed, identity, identityCode) { 31 | var operation = operation_packager(name, code, seed, identity, identityCode); 32 | var chainContext = contextUtils.extendChainContext(0, localContext); 33 | var options = { 34 | source: this.source, 35 | parts: this.parts, 36 | operation: operation, 37 | globalContext: this.globalContext, 38 | chainContext: chainContext 39 | }; 40 | return new Chain(options); 41 | }; 42 | 43 | module.exports = WrappedTypedArray; -------------------------------------------------------------------------------- /test/chrome_version_helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (minVersion, callback, shouldIgnoreNotMatchingCallback) { 4 | var match = window.navigator.appVersion.match(/Chrome\/(\d+)\./); 5 | if (match) { 6 | var chromeVersion = parseInt(match[1], 10); 7 | if(chromeVersion && chromeVersion > minVersion){ 8 | callback(); 9 | } 10 | } else { 11 | if (!shouldIgnoreNotMatchingCallback) { 12 | callback(); 13 | } 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /test/errors.tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var chromeHelper = require('./chrome_version_helper'); 4 | 5 | chromeHelper(39, function () { 6 | describe('error tests', function(){ 7 | 8 | var pjs; 9 | 10 | before(function () { 11 | pjs = require('../src/index.js'); 12 | pjs.init({maxWorkers:4}); 13 | }); 14 | 15 | after(function(){ 16 | if (pjs.config){ 17 | pjs.terminate(); 18 | } 19 | }); 20 | 21 | it ('should return error if is thrown as first seq done parameter', function(done){ 22 | pjs(new Uint32Array([1,2,3,4])).map(function(){ 23 | throw new Error('Failed'); 24 | }).seq(function(err){ 25 | expect(err.name).to.equal('WorkerError'); 26 | expect(err.message).to.match(/(Uncaught Error: Failed)|(Error: Failed)/); 27 | done(); 28 | }); 29 | }); 30 | 31 | it ('should return error if is accidentally thrown', function(done){ 32 | pjs(new Uint32Array([1,2,3,4])).map(function(element){ 33 | return element.inexistent(); 34 | }).seq(function(err){ 35 | expect(err.name).to.equal('WorkerError'); 36 | expect(err.message).to.match(/(Uncaught TypeError: undefined is not a function)|(TypeError: element.inexistent is not a function)/); 37 | done(); 38 | }); 39 | }); 40 | 41 | it ('should be able to run another job after one previously failed', function(done){ 42 | pjs(new Uint32Array([1,2,3,4])).map(function(element){ 43 | return element.inexistent(); 44 | }).seq(function(err){ 45 | expect(err.name).to.equal('WorkerError'); 46 | expect(err.message).to.match(/(Uncaught TypeError: undefined is not a function)|(TypeError: element.inexistent is not a function)/); 47 | 48 | pjs(new Uint32Array([1,2,3,4])).map(function(e){ 49 | return e * 2; 50 | }).seq(function(err, result){ 51 | expect(err).to.be.null; 52 | expect(result).to.have.length(4); 53 | expect(result[0]).to.equal(2); 54 | expect(result[1]).to.equal(4); 55 | expect(result[2]).to.equal(6); 56 | expect(result[3]).to.equal(8); 57 | 58 | done(); 59 | }); 60 | }); 61 | }); 62 | }); 63 | }); -------------------------------------------------------------------------------- /test/filter.tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('filter tests', function(){ 4 | 5 | var pjs; 6 | var utils = require('../src/utils.js'); 7 | var supportedArrays = require('./supported_array_types_helper')(); 8 | 9 | before(function () { 10 | pjs = require('../src/index.js'); 11 | pjs.init({maxWorkers:4}); 12 | }); 13 | 14 | after(function(){ 15 | if (pjs.config){ 16 | pjs.terminate(); 17 | } 18 | }); 19 | 20 | supportedArrays.forEach(function (TypedArray) { 21 | describe(utils.format('tests for {0}', utils.getTypedArrayConstructorType(TypedArray)), function(){ 22 | var sourceArray = new TypedArray([1,2,3,4,5]); 23 | 24 | it('should return filtered elements in callback', function(done){ 25 | pjs(sourceArray).filter(function(e){ 26 | return 0 === (e % 2); 27 | }).seq(function(err, result) { 28 | if (err) { return done(err); } 29 | expect(result).to.have.length(2); 30 | expect(utils.getTypedArrayType(result)).to.equal(utils.getTypedArrayType(sourceArray)); 31 | expect(result[0]).to.equal(2); 32 | expect(result[1]).to.equal(4); 33 | done(); 34 | }); 35 | }); 36 | 37 | var emptySourceArray = new TypedArray([]); 38 | it('should return no elements for empty array in callback', function(done){ 39 | pjs(emptySourceArray).map(function(e){ 40 | return true; 41 | }).seq(function(err, result){ 42 | if (err) { return done(err); } 43 | expect(result).to.have.length(0); 44 | expect(utils.getTypedArrayType(result)).to.equal(utils.getTypedArrayType(emptySourceArray)); 45 | done(); 46 | }); 47 | }); 48 | 49 | it('should return no elements if no elements match predicate in callback', function(done){ 50 | pjs(sourceArray).filter(function(e){ 51 | return false; 52 | }).seq(function(err, result) { 53 | if (err) { return done(err); } 54 | expect(result).to.have.length(0); 55 | expect(utils.getTypedArrayType(result)).to.equal(utils.getTypedArrayType(sourceArray)); 56 | done(); 57 | }); 58 | }); 59 | 60 | it('should return all elements if all elements match predicate in callback', function(done){ 61 | pjs(sourceArray).filter(function(e){ 62 | return true; 63 | }).seq(function(err, result) { 64 | if (err) { return done(err); } 65 | expect(result).to.have.length(sourceArray.length); 66 | expect(utils.getTypedArrayType(result)).to.equal(utils.getTypedArrayType(sourceArray)); 67 | for (var i = 0; i < sourceArray.length; i += 1) { 68 | expect(result[i]).to.equal(sourceArray[i]); 69 | } 70 | done(); 71 | }); 72 | }); 73 | 74 | var singleElementSourceArray = new TypedArray([4]); 75 | it('should return filtered elements for single element in callback', function(done){ 76 | pjs(singleElementSourceArray).filter(function(e){ 77 | return true; 78 | }).seq(function(err, result){ 79 | if (err) { return done(err); } 80 | expect(result).to.have.length(singleElementSourceArray.length); 81 | expect(result[0]).to.equal(singleElementSourceArray[0]); 82 | expect(utils.getTypedArrayType(result)).to.equal(utils.getTypedArrayType(singleElementSourceArray)); 83 | done(); 84 | }); 85 | }); 86 | }); 87 | }); 88 | }); -------------------------------------------------------------------------------- /test/firefox_helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (callback) { 4 | if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) { 5 | callback(); 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /test/map.tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('map tests', function(){ 4 | 5 | var pjs; 6 | var utils = require('../src/utils.js'); 7 | var supportedArrays = require('./supported_array_types_helper')(); 8 | 9 | before(function () { 10 | pjs = require('../src/index.js'); 11 | pjs.init({maxWorkers:4}); 12 | }); 13 | 14 | after(function(){ 15 | if (pjs.config){ 16 | pjs.terminate(); 17 | } 18 | }); 19 | 20 | supportedArrays.forEach(function (TypedArray) { 21 | describe(utils.format('tests for {0}', utils.getTypedArrayConstructorType(TypedArray)), function(){ 22 | var sourceArray = new TypedArray([1,2,3,4,5]); 23 | it('should return mapped elements in callback', function(done){ 24 | pjs(sourceArray).map(function(e){ 25 | return e * 2; 26 | }).seq(function(err, result){ 27 | if (err) { return done(err); } 28 | expect(result).to.have.length(sourceArray.length); 29 | expect(utils.getTypedArrayType(result)).to.equal(utils.getTypedArrayType(sourceArray)); 30 | for (var i = sourceArray.length - 1; i >= 0; i--) { 31 | expect(result[i]).to.equal(sourceArray[i] * 2); 32 | } 33 | done(); 34 | }); 35 | }); 36 | 37 | var emptySourceArray = new TypedArray([]); 38 | it('should return no elements for empty array in callback', function(done){ 39 | pjs(emptySourceArray).map(function(e){ 40 | return e * 2; 41 | }).seq(function(err, result){ 42 | if (err) { return done(err); } 43 | expect(result).to.have.length(0); 44 | expect(utils.getTypedArrayType(result)).to.equal(utils.getTypedArrayType(emptySourceArray)); 45 | done(); 46 | }); 47 | }); 48 | 49 | var singleElementSourceArray = new TypedArray([4]); 50 | it('should return mapped elements for single element array in callback', function(done){ 51 | pjs(singleElementSourceArray).map(function(e){ 52 | return e * 2; 53 | }).seq(function(err, result){ 54 | if (err) { return done(err); } 55 | expect(result).to.have.length(singleElementSourceArray.length); 56 | expect(result[0]).to.equal(singleElementSourceArray[0] * 2); 57 | expect(utils.getTypedArrayType(result)).to.equal(utils.getTypedArrayType(singleElementSourceArray)); 58 | done(); 59 | }); 60 | }); 61 | }); 62 | }); 63 | }); -------------------------------------------------------------------------------- /test/operation_packager.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('operation packager', function () { 4 | var operationPackager = require('../src/operation_packager'); 5 | var errors = require('../src/errors'); 6 | 7 | it('should not package operation without name', function () { 8 | expect(operationPackager).to.throw(errors.InvalidArgumentsError); 9 | }); 10 | 11 | it('should not package operation without code', function () { 12 | expect(function () { 13 | operationPackager('filter'); 14 | }).to.throw(errors.InvalidArgumentsError); 15 | }); 16 | 17 | it('should package filter operation', function () { 18 | var operation = 'filter'; 19 | var code = function (e) { return true; }; 20 | var pack = operationPackager(operation, code); 21 | expect(pack.name).to.equal(operation); 22 | expect(pack.code).to.equal(code); 23 | }); 24 | 25 | it('should package map operation', function () { 26 | var operation = 'map'; 27 | var code = function (e) { return e + 1; }; 28 | var pack = operationPackager(operation, code); 29 | expect(pack.name).to.equal(operation); 30 | expect(pack.code).to.equal(code); 31 | }); 32 | 33 | it('should not package reduce operation without seed', function () { 34 | var operation = 'reduce'; 35 | var code = function (p, e) { return p + e; }; 36 | expect(function () { 37 | operationPackager(operation, code); 38 | }).to.throw(errors.InvalidArgumentsError); 39 | }); 40 | 41 | it('should not package reduce operation without identity', function () { 42 | var operation = 'reduce'; 43 | var code = function (p, e) { return p + e; }; 44 | var seed = 1; 45 | expect(function () { 46 | operationPackager(operation, code, seed); 47 | }).to.throw(errors.InvalidArgumentsError); 48 | }); 49 | 50 | it('should not package reduce operation without identity code', function () { 51 | var operation = 'reduce'; 52 | var code = function (p, e) { return p + e; }; 53 | var seed = 1; 54 | expect(function () { 55 | operationPackager(operation, code, seed, 0); 56 | }).to.throw(errors.InvalidArgumentsError); 57 | }); 58 | 59 | it('should package reduce operation', function () { 60 | var operation = 'reduce'; 61 | var code = function (p, e) { return p + e; }; 62 | var seed = 1; 63 | var identity = 0; 64 | var identityCode = function (p, e) { return p + e; }; 65 | expect(function () { 66 | operationPackager(operation, code, seed, identity, identityCode); 67 | }).to.not.throw(errors.InvalidArgumentsError); 68 | }); 69 | 70 | it('should package functionPath if code is string an not function', function(){ 71 | var operation = 'map'; 72 | var pack = operationPackager(operation, 'f'); 73 | expect(pack.name).to.equal(operation); 74 | expect(pack.code).to.be.undefined; 75 | expect(pack.functionPath).to.equal('f'); 76 | }); 77 | }); -------------------------------------------------------------------------------- /test/supported_array_types_helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function () { 4 | var supportedArrays = [ 5 | Uint8Array, 6 | Int8Array, 7 | Uint8ClampedArray, 8 | Uint16Array, 9 | Int16Array, 10 | Uint32Array, 11 | Int32Array, 12 | Float32Array, 13 | Float64Array 14 | ]; 15 | var SharedUint8Array = SharedUint8Array || undefined; 16 | if (SharedUint8Array) { 17 | var sharedArrays = [ 18 | SharedUint8Array, 19 | SharedUint8ClampedArray, 20 | SharedUint16Array, 21 | SharedUint32Array, 22 | SharedInt8Array, 23 | SharedInt16Array, 24 | SharedInt32Array, 25 | SharedFloat32Array, 26 | SharedFloat64Array 27 | ]; 28 | supportedArrays.concat(sharedArrays); 29 | } 30 | return supportedArrays; 31 | }; -------------------------------------------------------------------------------- /test/typed_array_merger.tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('array merge', function(){ 4 | var merge = require('../src/typed_array_merger.js'); 5 | var errors = require('../src/errors.js'); 6 | 7 | it('should not be posible to merge with empty array', function () { 8 | expect(function () { 9 | merge([]); 10 | }).to.throw(errors.InvalidArgumentsError); 11 | }); 12 | 13 | it('should return same array if only one array to merge', function(){ 14 | var array = new Uint32Array([1,2,3]); 15 | var toMerge = [ array ]; 16 | 17 | var merged = merge(toMerge); 18 | 19 | expect(merged.length).to.equal(array.length); 20 | for (var i = merged.length - 1; i >= 0; i--) { 21 | expect(merged[i]).to.equal(array[i]); 22 | }; 23 | }); 24 | 25 | it ('should merge seven arrays', function(){ 26 | var lengths = [0, 1, 2, 100, 200, 500, 1000]; 27 | 28 | var toMerge = lengths.map(function(l){ 29 | var r = new Uint32Array(l); 30 | for (var i = 0; i < l; i++){ r[l] = i; } 31 | return r; 32 | }); 33 | 34 | var expected_length = lengths.reduce(function(c,v) { return c + v; }, 0); 35 | 36 | var merged = merge(toMerge); 37 | 38 | expect(merged.length).to.equal(expected_length); 39 | 40 | var currentArray = 0, currentSum = 0; 41 | 42 | for (var i = 0; i < expected_length; i++) { 43 | if (currentSum >= lengths[currentArray]){ 44 | currentSum = 0; 45 | currentArray++; 46 | } 47 | 48 | expect(merged[i]).to.equal(toMerge[currentArray][currentSum]); 49 | }; 50 | }) 51 | }); -------------------------------------------------------------------------------- /test/worker_core.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('worker core', function(){ 4 | var worker = require('../src/worker_core'); 5 | var utils = require('../src/utils'); 6 | var JobPackager = require('../src/job_packager'); 7 | var supportedArrays = require('./supported_array_types_helper')(); 8 | 9 | supportedArrays.forEach(function (TypedArray) { 10 | describe(utils.format('tests for {0}', utils.getTypedArrayConstructorType(TypedArray)), function(){ 11 | var parts = 4; 12 | var code = function (a) { return a + 1; }; 13 | var packageIndex = 3, result; 14 | var elements = new TypedArray([1,2,3,4,5,6,7,8]); 15 | var packager = new JobPackager(parts, elements); 16 | var packages = packager.generatePackages([{ code: code, name: 'map'}]); 17 | 18 | var expectedElements = new TypedArray([8,9]); 19 | 20 | var jobPackage = packages[packageIndex]; 21 | before(function(){ 22 | result = worker({data: jobPackage}); 23 | }); 24 | 25 | it('should add index to result object', function () { 26 | expect(result.message.index).to.equal(packageIndex); 27 | }); 28 | 29 | it('should add transferables to result object', function () { 30 | expect(result.transferables).to.not.be.undefined; 31 | expect(result.transferables.length).to.equal(1); 32 | expect(result.transferables[0]).to.equal(result.message.value); 33 | }); 34 | 35 | it('should transform array elements', function () { 36 | var transformedElements = new TypedArray(result.message.value); 37 | expect(transformedElements.length).to.equal(expectedElements.length); 38 | for (var i = 0; i < expectedElements.length; i++) { 39 | expect(transformedElements[i]).to.equal(expectedElements[i]); 40 | } 41 | }); 42 | }); 43 | }); 44 | }); --------------------------------------------------------------------------------