You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Jul 5, 2020. It is now read-only.
Copy file name to clipboardExpand all lines: README.md
+170-1Lines changed: 170 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -18,4 +18,173 @@ if (turbojs) {
18
18
19
19
Now we need some memory. Because data has to be transferred to and from GPU and system memory, we want to reduce the overhead this copy operation creates. To do this, turbo.js provides the `alloc` function. This will reserve memory on the GPU and in your browser. JavaScript can access and change contents of allocated memory by accessing the `.data` sub-array of a variable that contains allocated memory.
20
20
21
-
For both turbo.js and JavaScript, the allocated memory is strictly typed and represents a one-dimensional array of 32bit IEEE floating-point vlaues. Thus, the `.data` sub-array is a standard JavaScript [`Float32Array`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Float32Array) object. After allocation, you can interact with this array however you want, except for changing it's size. Doing so will result in undefined behavior.
21
+
For both turbo.js and JavaScript, the allocated memory is strictly typed and represents a one-dimensional array of 32bit IEEE floating-point values. Thus, the `.data` sub-array is a standard JavaScript [`Float32Array`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Float32Array) object. After allocation, you can interact with this array however you want, except for changing it's size. Doing so will result in undefined behavior.
22
+
23
+
```js
24
+
if (turbojs) {
25
+
var foo =turbojs.alloc(1e6);
26
+
}
27
+
```
28
+
29
+
We now have an array with 1,000,000 elements. Let's fill it with some data.
30
+
31
+
```js
32
+
if (turbojs) {
33
+
var foo =turbojs.alloc(5e6);
34
+
35
+
for (var i =0; i <1e6; i++) foo.data[i] = i;
36
+
37
+
// print first five elements
38
+
console.log(foo.data.subarray(0, 5));
39
+
}
40
+
```
41
+
42
+
Running this, the console should now display `[0, 1, 2, 3, 4]`. Now for our simple calculation: Multiplying each value by `nFactor` and printing the results:
43
+
44
+
```js
45
+
if (turbojs) {
46
+
var foo =turbojs.alloc(1e6);
47
+
var nFactor =4;
48
+
49
+
for (var i =0; i <1e6; i++) foo.data[i] = i;
50
+
51
+
turbojs.run(foo, `void main(void) {
52
+
commit(read() * ${nFactor}.);
53
+
}`);
54
+
55
+
console.log(foo.data.subarray(0, 5));
56
+
}
57
+
```
58
+
59
+
The console should now display `[0, 4, 8, 12, 16]`. That was easy, wasn't it? Let's break done what we've done:
60
+
61
+
-`turbojs.run`'s first parameter is the previously allocated memory. The second parameter is the code that will be executed for each value in the array.
62
+
- The code is written in an extension of C called GLSL. If you are not familiar with it, there is some good documentation on the internet. If you now C (or JS and know what types are), you'll pick it up in no time.
63
+
- The kernel code here consists just of the main function, which takes no parameters. However, kernels can have any number of functions (except zero).
64
+
- The `read()` function reads the current input value.
65
+
-`${nFactor}` is substituted by the value of `nFactor`. Since GLSL expects numerical constant expressions to be typed, we append a `.` to mark it as a float. Otherwise the GLSL compiler will throw a type error.
66
+
-`commit()` writes the result back to memory. You can `commit` from any function, but it is good practise to do so from the last line of the `main` function.
67
+
68
+
### Example 2: Working with vectors
69
+
70
+
That's great. But sometimes you need to return more than a single value from each operation. Well, it might no look like it, but we've been doing that all along. Both `commit` and `read` actually work on 4-dimensional vectors. To break it down:
71
+
72
+
-`vec4 read()` returns the GLSL data type `vec4`.
73
+
-`void commit(vec4)` takes a `vec4` and writes it to memory
74
+
75
+
A `vec4` is basically just an array. You could say it's akin to `vec4 foobar = {r:0, g:0, b:0, a:0}` in JS, but it's much more similar to JavaScript [`SIMD`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SIMD)'s `Float32x4`.
76
+
77
+
The nice thing about GLSL is that all operations are overloaded so that they can work with vectors without the need to deal with each element individually, so
Neat, huh? Of course there are other types of vectors in GLSL, namely `vec2` and `vec3`. If you create a bigger vector and supply a smaller one as a parameter, GLSL will automatically align the values:
90
+
91
+
```GLSL
92
+
vec2 foo = vec2(1., 2.);
93
+
94
+
commit(vec4(foo.r, foo.g, 0., 0.));
95
+
96
+
// is the same as
97
+
98
+
commit(vec4(foo.rg, 0., 0.));
99
+
```
100
+
101
+
So we'll use that right now. If you visit the website mentioned above, you will get results from a simple benchmark comparing JS to JS + turbo.js. The benchmark calculates random points on a mandelbrot fractal. Let's break down what happens there, starting with the JavaScript code:
102
+
103
+
For each run, the first two values of each `vec4` of the allocated memory are filled with random coordinates as the input for the fractal function:
104
+
105
+
```js
106
+
for (var i =0; i < sampleSize; i +=4) {
107
+
testData.data[i] =Math.random();
108
+
testData.data[i +1] =Math.random();
109
+
}
110
+
```
111
+
112
+
For each operation, the result will be a greyscale color value. That will be written to the third (i.e. `b`) component of each vector:
113
+
114
+
```js
115
+
functiontestJS() {
116
+
for (var i =0; i < sampleSize; i +=4) {
117
+
var x0 =-2.5+ (3.5*testData.data[i]);
118
+
var y0 =testData.data[i +1], x =0, y =0, xt =0, c =0;
119
+
120
+
for (var n =0; n < sampleIterations; n++) {
121
+
if (x * x + y * y >=2*2) break;
122
+
123
+
xt = x * x - y * y + x0;
124
+
y =2* x * y + y0;
125
+
x = xt;
126
+
c++;
127
+
}
128
+
129
+
var col = c / sampleIterations;
130
+
131
+
testData.data[i +2] = col;
132
+
}
133
+
}
134
+
```
135
+
136
+
The fractal is calculated to the iteration depth of `sampleIterations`. Now let's take a look at the turbo.js code performing the same task:
137
+
138
+
```js
139
+
functiontestTurbo() {
140
+
turbojs.run(testData, `void main(void) {
141
+
vec4 ipt = read();
142
+
143
+
float x0 = -2.5 + (3.5 * ipt.r);
144
+
float y0 = ipt.g, x, y, xt, c;
145
+
146
+
for(int i = 0; i < ${sampleIterations}; i++) {
147
+
if (x * x + y * y >= 2. * 2.) break;
148
+
149
+
xt = x * x - y * y + x0;
150
+
y = 2. * x * y + y0;
151
+
x = xt;
152
+
c++;
153
+
}
154
+
155
+
float col = c / ${sampleIterations}.;
156
+
157
+
commit(vec4(ipt.rg, col, 0.));
158
+
}`);
159
+
}
160
+
```
161
+
162
+
Notice how easy the JS code can be translated to GLSL and vice versa, as long as no exclusive paradigms are used. Of course this example is not the optimal algorithm in neither JS or GLSL, but this is just for comparison.
163
+
164
+
### Example 3: Debugging
165
+
166
+
GLSL code is compiled by your GPU vendor's compiler. Usually these compilers provide verbose error information. You can catch compile-time errors by catching exceptions thrown by turbo.js. As an example, consider this invalid code:
167
+
168
+
```js
169
+
if (turbojs) {
170
+
var foo =turbojs.alloc(1e6);
171
+
var nFactor =4;
172
+
173
+
turbojs.run(foo, `void main(void) {
174
+
commit(${nFactor}. + bar);
175
+
}`);
176
+
}
177
+
```
178
+
179
+
This will generate two errors. The first one is `bar` being undefined. The second one is a type mismatch: `commit` expects a vector, but we've just given it a float. Opening your browser's console will reveal the error:
180
+
181
+

182
+
183
+
### Further considerations
184
+
185
+
- Always provide a JS fallback if you detect that turbo.js is not supported.
186
+
- Use web workers for huge datasets to prevent the page from blocking.
187
+
- Always warm-up the GPU using dummy data. You won't get the full performance if you don't.
188
+
- In addition to error checking, do a sanity check using a small dataset and a simple kernel. If the numbers don't check out, fall back to JS.
189
+
- I haven't tried it, but I guess you can adapt [glsl-transpiler](https://github.com/stackgl/glsl-transpiler) to create JS fallback code automatically.
190
+
- Consider if you *really* need turbo.js. Optimize your *algorithm* (not code) first. Consider using JS SIMD. turbo.js can't be used for non-parallel workloads.
0 commit comments