Skip to content

Commit d151153

Browse files
committed
feat: add ndarray/base/binary-loop-interchange-order
This commit adds support for reordering ndarray dimensions and associated strides for loop interchange.
1 parent 0ac8825 commit d151153

File tree

11 files changed

+1042
-0
lines changed

11 files changed

+1042
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
<!--
2+
3+
@license Apache-2.0
4+
5+
Copyright (c) 2023 The Stdlib Authors.
6+
7+
Licensed under the Apache License, Version 2.0 (the "License");
8+
you may not use this file except in compliance with the License.
9+
You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
19+
-->
20+
21+
# binaryLoopOrder
22+
23+
> Reorder ndarray dimensions and associated strides for loop interchange.
24+
25+
<!-- Section to include introductory text. Make sure to keep an empty line after the intro `section` element and another before the `/section` close. -->
26+
27+
<section class="intro">
28+
29+
</section>
30+
31+
<!-- /.intro -->
32+
33+
<!-- Package usage documentation. -->
34+
35+
<section class="usage">
36+
37+
## Usage
38+
39+
```javascript
40+
var binaryLoopOrder = require( '@stdlib/ndarray/base/binary-loop-interchange-order' );
41+
```
42+
43+
#### binaryLoopOrder( shape, stridesX, stridesY, stridesZ )
44+
45+
Reorders [ndarray][@stdlib/ndarray/ctor] dimensions and associated strides for [loop interchange][loop-interchange].
46+
47+
```javascript
48+
// Define an array shape:
49+
var shape = [ 2, 2 ];
50+
51+
// Define the strides for the input arrays:
52+
var stridesX = [ 2, 1 ]; // row-major
53+
var stridesY = [ 4, 2 ]; // row-major
54+
55+
// Define the strides for the output array:
56+
var stridesZ = [ 1, 2 ]; // column-major
57+
58+
// Resolve the loop interchange order:
59+
var o = binaryLoopOrder( shape, stridesX, stridesY, stridesZ );
60+
// returns {...}
61+
```
62+
63+
The function returns an object having the following properties:
64+
65+
- **sh**: ordered dimensions.
66+
- **sx**: first input array strides sorted in loop order.
67+
- **sy**: second input array strides sorted in loop order.
68+
- **sz**: output array strides sorted in loop order.
69+
70+
For all returned arrays, the first element corresponds to the innermost loop, and the last element corresponds to the outermost loop.
71+
72+
</section>
73+
74+
<!-- /.usage -->
75+
76+
<!-- Package usage notes. Make sure to keep an empty line after the `section` element and another before the `/section` close. -->
77+
78+
<section class="notes">
79+
80+
## Notes
81+
82+
- When iterating over the elements of a multi-dimensional array, accessing elements which are closer in memory can improve performance. To this end, [loop interchange][loop-interchange] is a technique used in [loop nest optimization][loop-nest-optimization] to improve locality of reference and take advantage of CPU cache.
83+
84+
The purpose of this function is to order [ndarray][@stdlib/ndarray/ctor] dimensions according to the magnitude of array strides. By using the ordered dimensions and associated strides, one can construct nested loops (one for each dimension) such that the innermost loop iterates over the dimension in which array elements are closest in memory and the outermost loop iterates over the dimension in which array elements are farthest apart in memory. As a consequence, element iteration is optimized to minimize cache misses and ensure locality of reference.
85+
86+
- Cache performance may be degraded if the layout order (i.e., row-major or column-major) differs for the input and output [ndarrays][@stdlib/ndarray/ctor]. This function is intended to optimize cache performance for the most common layout order. Accordingly, if the output [ndarray][@stdlib/ndarray/ctor] has a different layout order (e.g., if the input [ndarrays][@stdlib/ndarray/ctor] are row-major and the output [ndarray][@stdlib/ndarray/ctor] is column-major), cache misses are likely for the output [ndarray][@stdlib/ndarray/ctor]. In general, to ensure best performance, input and output [ndarrays][@stdlib/ndarray/ctor] should have the same layout order.
87+
88+
- The function assumes that the input and output [ndarrays][@stdlib/ndarray/ctor] have the same shape. Hence, loop interchange order should only be determined **after** broadcasting.
89+
90+
</section>
91+
92+
<!-- /.notes -->
93+
94+
<!-- Package usage examples. -->
95+
96+
<section class="examples">
97+
98+
## Examples
99+
100+
<!-- eslint no-undef: "error" -->
101+
102+
```javascript
103+
var array = require( '@stdlib/ndarray/array' );
104+
var loopOrder = require( '@stdlib/ndarray/base/binary-loop-interchange-order' );
105+
106+
// Create ndarrays:
107+
var x = array( [ [ 1, 2 ], [ 3, 4 ] ] );
108+
var y = array( [ [ 5, 6 ], [ 7, 8 ] ] );
109+
var z = array( [ [ 0, 0 ], [ 0, 0 ] ] );
110+
111+
// Resolve loop interchange data:
112+
var o = loopOrder( x.shape, x.strides, y.strides, z.strides );
113+
// returns {...}
114+
115+
console.log( o );
116+
```
117+
118+
</section>
119+
120+
<!-- /.examples -->
121+
122+
<!-- Section to include cited references. If references are included, add a horizontal rule *before* the section. Make sure to keep an empty line after the `section` element and another before the `/section` close. -->
123+
124+
<section class="references">
125+
126+
</section>
127+
128+
<!-- /.references -->
129+
130+
<!-- Section for related `stdlib` packages. Do not manually edit this section, as it is automatically populated. -->
131+
132+
<section class="related">
133+
134+
</section>
135+
136+
<!-- /.related -->
137+
138+
<!-- Section for all links. Make sure to keep an empty line after the `section` element and another before the `/section` close. -->
139+
140+
<section class="links">
141+
142+
[loop-interchange]: https://en.wikipedia.org/wiki/Loop_interchange
143+
144+
[loop-nest-optimization]: https://en.wikipedia.org/wiki/Loop_nest_optimization
145+
146+
[@stdlib/ndarray/ctor]: https://github.com/stdlib-js/stdlib
147+
148+
</section>
149+
150+
<!-- /.links -->
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* @license Apache-2.0
3+
*
4+
* Copyright (c) 2023 The Stdlib Authors.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
'use strict';
20+
21+
// MODULES //
22+
23+
var bench = require( '@stdlib/bench' );
24+
var randu = require( '@stdlib/random/base/randu' );
25+
var isArray = require( '@stdlib/assert/is-array' );
26+
var shape2strides = require( '@stdlib/ndarray/base/shape2strides' );
27+
var pkg = require( './../package.json' ).name;
28+
var loopOrder = require( './../lib' );
29+
30+
31+
// MAIN //
32+
33+
bench( pkg+'::row-major', function benchmark( b ) {
34+
var strides;
35+
var shape;
36+
var out;
37+
var i;
38+
39+
shape = [ 10, 10, 10 ];
40+
strides = shape2strides( shape, 'row-major' );
41+
42+
b.tic();
43+
for ( i = 0; i < b.iterations; i++ ) {
44+
strides[ i%shape.length ] *= ( randu() < 0.5 ) ? -1 : 1;
45+
out = loopOrder( shape, strides, strides, strides );
46+
if ( typeof out !== 'object' ) {
47+
b.fail( 'should return an object' );
48+
}
49+
}
50+
b.toc();
51+
if ( !isArray( out.sh ) || !isArray( out.sx ) || !isArray( out.sy ) || !isArray( out.sz ) ) { // eslint-disable-line max-len
52+
b.fail( 'should return an array' );
53+
}
54+
b.pass( 'benchmark finished' );
55+
b.end();
56+
});
57+
58+
bench( pkg+'::column-major', function benchmark( b ) {
59+
var strides;
60+
var shape;
61+
var out;
62+
var i;
63+
64+
shape = [ 10, 10, 10 ];
65+
strides = shape2strides( shape, 'column-major' );
66+
67+
b.tic();
68+
for ( i = 0; i < b.iterations; i++ ) {
69+
strides[ i%shape.length ] *= ( randu() < 0.5 ) ? -1 : 1;
70+
out = loopOrder( shape, strides, strides, strides );
71+
if ( typeof out !== 'object' ) {
72+
b.fail( 'should return an object' );
73+
}
74+
}
75+
b.toc();
76+
if ( !isArray( out.sh ) || !isArray( out.sx ) || !isArray( out.sy ) || !isArray( out.sz ) ) { // eslint-disable-line max-len
77+
b.fail( 'should return an array' );
78+
}
79+
b.pass( 'benchmark finished' );
80+
b.end();
81+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
2+
{{alias}}( shape, stridesX, stridesY, stridesZ )
3+
Reorders ndarray dimensions and associated strides for loop interchange.
4+
5+
The function returns an object having the following properties:
6+
7+
- sh: ordered dimensions.
8+
- sx: first input array strides sorted in loop order.
9+
- sy: second input array strides sorted in loop order.
10+
- sz: output array strides sorted in loop order.
11+
12+
For all returned arrays, the first element corresponds to the innermost
13+
loop, and the last element corresponds to the outermost loop.
14+
15+
The function assumes that the input and output ndarrays have the same shape.
16+
Hence, loop interchange order should only be determined after broadcasting.
17+
18+
Parameters
19+
----------
20+
shape: ArrayLikeObject<integer>
21+
Array dimensions.
22+
23+
stridesX: ArrayLikeObject<integer>
24+
First input array strides.
25+
26+
stridesY: ArrayLikeObject<integer>
27+
Second input array strides.
28+
29+
stridesZ: ArrayLikeObject<integer>
30+
Output array strides.
31+
32+
Returns
33+
-------
34+
out: Object
35+
Loop interchange data.
36+
37+
out.sh: Array<integer>
38+
Ordered dimensions.
39+
40+
out.sx: Array<integer>
41+
First input array strides sorted in loop order.
42+
43+
out.sy: Array<integer>
44+
Second input array strides sorted in loop order.
45+
46+
out.sz: Array<integer>
47+
Output array strides sorted in loop order.
48+
49+
Examples
50+
--------
51+
> var x = {{alias:@stdlib/ndarray/array}}( [ [ 1, 2 ], [ 3, 4 ] ] );
52+
> var y = {{alias:@stdlib/ndarray/array}}( [ [ 5, 6 ], [ 7, 8 ] ] );
53+
> var z = {{alias:@stdlib/ndarray/array}}( [ [ 0, 0 ], [ 0, 0 ] ] );
54+
> var o = {{alias}}( x.shape, x.strides, y.strides, z.strides )
55+
{...}
56+
57+
See Also
58+
--------
59+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* @license Apache-2.0
3+
*
4+
* Copyright (c) 2023 The Stdlib Authors.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
// TypeScript Version: 2.0
20+
21+
/// <reference types="@stdlib/types"/>
22+
23+
import { ArrayLike } from '@stdlib/types/array';
24+
25+
/**
26+
* Interface describing loop interchange data.
27+
*/
28+
interface LoopOrderObject {
29+
/**
30+
* Dimensions sorted in loop order.
31+
*/
32+
sh: Array<number>;
33+
34+
/**
35+
* First input array strides sorted in loop order.
36+
*/
37+
sx: Array<number>;
38+
39+
/**
40+
* Second input array strides sorted in loop order.
41+
*/
42+
sy: Array<number>;
43+
44+
/**
45+
* Output array strides sorted in loop order.
46+
*/
47+
sz: Array<number>;
48+
}
49+
50+
/**
51+
* Reorders ndarray dimensions and associated strides for loop interchange.
52+
*
53+
* ## Notes
54+
*
55+
* - The returned object has the following properties:
56+
*
57+
* - **sh**: dimensions sorted in loop order.
58+
* - **sx**: first input ndarray strides sorted in loop order.
59+
* - **sy**: second input ndarray strides sorted in loop order.
60+
* - **sz**: output ndarray strides sorted in loop order.
61+
*
62+
* - When iterating over the elements of a multi-dimensional array, accessing elements which are closer in memory can improve performance. To this end, loop interchange is a technique used in loop nest optimization to improve locality of reference and take advantage of CPU cache.
63+
*
64+
* The purpose of this function is to order ndarray dimensions according to the magnitude of array strides. By using the ordered dimensions and associated strides, one can construct nested loops (one for each dimension) such that the innermost loop iterates over the dimension in which array elements are closest in memory and the outermost loop iterates over the dimension in which array elements are farthest apart in memory. As a consequence, element iteration is optimized to minimize cache misses and ensure locality of reference.
65+
*
66+
* - Cache performance may be degraded if the layout order (i.e., row-major or column-major) differs for the input and output ndarrays. This function is intended to optimize cache performance for the most common layout order. Accordingly, if the output ndarray has a different layout order (e.g., if the input ndarrays are row-major and the output ndarray is column-major), cache misses are likely for the output ndarray. In general, to ensure best performance, input and output ndarrays should have the same layout order.
67+
*
68+
* - The function assumes that the input and output ndarrays have the same shape. Hence, loop interchange order should only be determined **after** broadcasting.
69+
*
70+
* @param sh - array dimensions
71+
* @param sx - first input array stride lengths
72+
* @param sy - second input array stride lengths
73+
* @param sz - output array stride lengths
74+
* @returns loop interchange data
75+
*
76+
* @example
77+
* var sh = [ 2, 3, 4 ];
78+
*
79+
* var sx = [ 12, 4, 1 ]; // row-major
80+
* var sy = [ 24, 8, 1 ]; // row-major
81+
* var sz = [ 1, -2, 6 ]; // column-major
82+
*
83+
* var o = loopOrder( sh, sx, sy, sz );
84+
* // returns {...}
85+
*
86+
* var ssh = o.sh;
87+
* // returns [ 4, 3, 2 ]
88+
*
89+
* var ssx = o.sx;
90+
* // returns [ 1, 4, 12 ]
91+
*
92+
* var ssy = o.sy;
93+
* // returns [ 1, 8, 24 ]
94+
*
95+
* var ssz = o.sz;
96+
* // returns [ 6, -2, 1 ]
97+
*/
98+
declare function binaryLoopOrder( shape: ArrayLike<number>, stridesX: ArrayLike<number>, stridesY: ArrayLike<number>, stridesZ: ArrayLike<number> ): LoopOrderObject; // tslint-disable-line max-line-length
99+
100+
101+
// EXPORTS //
102+
103+
export = binaryLoopOrder;

0 commit comments

Comments
 (0)