Skip to content

Commit bdc9110

Browse files
committed
feat: add ndarray/base/loop-interchange-order
--- type: pre_commit_static_analysis_report description: Results of running static analysis checks when committing changes. report: - task: lint_filenames status: passed - task: lint_editorconfig status: passed - task: lint_markdown status: passed - task: lint_package_json status: passed - task: lint_repl_help status: passed - task: lint_javascript_src status: passed - task: lint_javascript_cli status: na - task: lint_javascript_examples status: passed - task: lint_javascript_tests status: passed - task: lint_javascript_benchmarks status: passed - task: lint_python status: na - task: lint_r status: na - task: lint_c_src status: na - task: lint_c_examples status: na - task: lint_c_benchmarks status: na - task: lint_c_tests_fixtures status: na - task: lint_shell status: na - task: lint_typescript_declarations status: passed - task: lint_typescript_tests status: passed - task: lint_license_headers status: passed ---
1 parent 61706f6 commit bdc9110

File tree

11 files changed

+1077
-0
lines changed

11 files changed

+1077
-0
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
<!--
2+
3+
@license Apache-2.0
4+
5+
Copyright (c) 2025 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+
# loopOrder
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 loopOrder = require( '@stdlib/ndarray/base/loop-interchange-order' );
41+
```
42+
43+
#### loopOrder( shape, strides )
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 = loopOrder( shape, [ stridesX, stridesY, stridesZ ] );
60+
// returns [...]
61+
```
62+
63+
The function returns an array having the following elements:
64+
65+
```text
66+
[ <shape>, ...<strides> ]
67+
```
68+
69+
where
70+
71+
- **shape**: dimensions sorted in loop order.
72+
- **...strides**: strides for each respective ndarray sorted in loop order.
73+
74+
For all returned arrays, the first element corresponds to the innermost loop, and the last element corresponds to the outermost loop.
75+
76+
</section>
77+
78+
<!-- /.usage -->
79+
80+
<!-- Package usage notes. Make sure to keep an empty line after the `section` element and another before the `/section` close. -->
81+
82+
<section class="notes">
83+
84+
## Notes
85+
86+
- 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.
87+
88+
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.
89+
90+
- 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.
91+
92+
- 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.
93+
94+
</section>
95+
96+
<!-- /.notes -->
97+
98+
<!-- Package usage examples. -->
99+
100+
<section class="examples">
101+
102+
## Examples
103+
104+
<!-- eslint-disable max-len -->
105+
106+
<!-- eslint no-undef: "error" -->
107+
108+
```javascript
109+
var array = require( '@stdlib/ndarray/array' );
110+
var getShape = require( '@stdlib/ndarray/shape' );
111+
var getStrides = require( '@stdlib/ndarray/strides' );
112+
var loopOrder = require( '@stdlib/ndarray/base/loop-interchange-order' );
113+
114+
// Create ndarrays:
115+
var x = array( [ [ 1, 2 ], [ 3, 4 ] ] );
116+
var y = array( [ [ 5, 6 ], [ 7, 8 ] ] );
117+
var z = array( [ [ 0, 0 ], [ 0, 0 ] ] );
118+
119+
// Resolve loop interchange data:
120+
var o = loopOrder( getShape( x ), [ getStrides( x ), getStrides( y ), getStrides( z ) ] );
121+
// returns [...]
122+
123+
console.log( o );
124+
```
125+
126+
</section>
127+
128+
<!-- /.examples -->
129+
130+
<!-- 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. -->
131+
132+
<section class="references">
133+
134+
</section>
135+
136+
<!-- /.references -->
137+
138+
<!-- Section for related `stdlib` packages. Do not manually edit this section, as it is automatically populated. -->
139+
140+
<section class="related">
141+
142+
</section>
143+
144+
<!-- /.related -->
145+
146+
<!-- Section for all links. Make sure to keep an empty line after the `section` element and another before the `/section` close. -->
147+
148+
<section class="links">
149+
150+
[loop-interchange]: https://en.wikipedia.org/wiki/Loop_interchange
151+
152+
[loop-nest-optimization]: https://en.wikipedia.org/wiki/Loop_nest_optimization
153+
154+
[@stdlib/ndarray/ctor]: https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/ndarray/ctor
155+
156+
</section>
157+
158+
<!-- /.links -->
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* @license Apache-2.0
3+
*
4+
* Copyright (c) 2025 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 isArrayArray = require( '@stdlib/assert/is-array-array' );
25+
var pkg = require( './../package.json' ).name;
26+
var loopOrder = require( './../lib' );
27+
28+
29+
// MAIN //
30+
31+
bench( pkg+'::row-major', function benchmark( b ) {
32+
var strides;
33+
var shape;
34+
var out;
35+
var st;
36+
var i;
37+
38+
shape = [ 10, 10, 10 ];
39+
strides = [
40+
[ 100, 10, 1 ],
41+
[ 200, 20, 2 ]
42+
];
43+
44+
b.tic();
45+
for ( i = 0; i < b.iterations; i++ ) {
46+
st = strides[ i%strides.length ];
47+
out = loopOrder( shape, [ st, st, st ] );
48+
if ( typeof out !== 'object' ) {
49+
b.fail( 'should return an object' );
50+
}
51+
}
52+
b.toc();
53+
if ( !isArrayArray( out ) ) {
54+
b.fail( 'should return an array' );
55+
}
56+
b.pass( 'benchmark finished' );
57+
b.end();
58+
});
59+
60+
bench( pkg+'::column-major', function benchmark( b ) {
61+
var strides;
62+
var shape;
63+
var out;
64+
var st;
65+
var i;
66+
67+
shape = [ 10, 10, 10 ];
68+
strides = [
69+
[ 1, 10, 100 ],
70+
[ 2, 20, 200 ]
71+
];
72+
73+
b.tic();
74+
for ( i = 0; i < b.iterations; i++ ) {
75+
st = strides[ i%strides.length ];
76+
out = loopOrder( shape, [ st, st, st ] );
77+
if ( typeof out !== 'object' ) {
78+
b.fail( 'should return an object' );
79+
}
80+
}
81+
b.toc();
82+
if ( !isArrayArray( out ) ) {
83+
b.fail( 'should return an array' );
84+
}
85+
b.pass( 'benchmark finished' );
86+
b.end();
87+
});
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
2+
{{alias}}( shape, strides )
3+
Reorders ndarray dimensions and associated strides for loop interchange.
4+
5+
The function returns an array having the following elements:
6+
7+
[ <shape>, ...<strides> ]
8+
9+
where
10+
11+
- shape: dimensions sorted in loop order.
12+
- strides: strides for each respective ndarray sorted in loop order.
13+
14+
For all returned arrays, the first element corresponds to the innermost
15+
loop, and the last element corresponds to the outermost loop.
16+
17+
The function assumes that the input and output ndarrays have the same shape.
18+
Hence, loop interchange order should only be determined after broadcasting.
19+
20+
Parameters
21+
----------
22+
shape: Array<integer>
23+
Array dimensions.
24+
25+
strides: Array<Array<integer>>
26+
List of stride arrays containing the stride lengths for each input and
27+
output ndarray.
28+
29+
Returns
30+
-------
31+
out: Array
32+
Loop interchange data.
33+
34+
Examples
35+
--------
36+
> var x = {{alias:@stdlib/ndarray/array}}( [ [ 1, 2 ], [ 3, 4 ] ] );
37+
> var y = {{alias:@stdlib/ndarray/array}}( [ [ 5, 6 ], [ 7, 8 ] ] );
38+
> var z = {{alias:@stdlib/ndarray/array}}( [ [ 0, 0 ], [ 0, 0 ] ] );
39+
> var o = {{alias}}( x.shape, [ x.strides, y.strides, z.strides ] )
40+
[...]
41+
42+
See Also
43+
--------
44+
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* @license Apache-2.0
3+
*
4+
* Copyright (c) 2025 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: 4.1
20+
21+
/// <reference types="@stdlib/types"/>
22+
23+
import { ArrayLike } from '@stdlib/types/array';
24+
25+
/**
26+
* Reorders ndarray dimensions and associated strides for loop interchange.
27+
*
28+
* ## Notes
29+
*
30+
* - The returned array has the following elements:
31+
*
32+
* ```text
33+
* [ <shape>, ...<strides> ]
34+
* ```
35+
*
36+
* where
37+
*
38+
* - **shape**: dimensions sorted in loop order.
39+
* - **...strides**: strides for each respective ndarray sorted in loop order.
40+
*
41+
* - 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.
42+
*
43+
* 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.
44+
*
45+
* - 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.
46+
*
47+
* - The function assumes that the input and output ndarrays have the same shape. Hence, loop interchange order should only be determined **after** broadcasting.
48+
*
49+
* @param shape - array dimensions
50+
* @param strides - list of stride arrays containing the stride lengths for each input and output ndarray
51+
* @returns loop interchange data
52+
*
53+
* @example
54+
* var sh = [ 2, 3, 4 ];
55+
*
56+
* var sx = [ 12, 4, 1 ]; // row-major
57+
* var sy = [ 24, 8, 1 ]; // row-major
58+
* var sz = [ 1, -2, 6 ]; // column-major
59+
*
60+
* var o = loopOrder( shape, [ sx, sy, sz ] );
61+
* // returns {...}
62+
*
63+
* var ssh = o[ 0 ];
64+
* // returns [ 4, 3, 2 ]
65+
*
66+
* var ssx = o[ 1 ];
67+
* // returns [ 1, 4, 12 ]
68+
*
69+
* var ssy = o[ 2 ];
70+
* // returns [ 1, 8, 24 ]
71+
*
72+
* var ssz = o[ 3 ];
73+
* // returns [ 6, -2, 1 ]
74+
*/
75+
declare function loopOrder( shape: ArrayLike<number>, strides: ArrayLike<ArrayLike<number>> ): Array<Array<number>>;
76+
77+
78+
// EXPORTS //
79+
80+
export = loopOrder;

0 commit comments

Comments
 (0)