├── .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 
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 |
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 | });
--------------------------------------------------------------------------------