factory.js (8799B)
1 /** 2 * @license Apache-2.0 3 * 4 * Copyright (c) 2018 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 isString = require( '@stdlib/assert/is-string' ).isPrimitive; 24 var isNonNegativeInteger = require( '@stdlib/assert/is-nonnegative-integer' ).isPrimitive; 25 var isCollection = require( '@stdlib/assert/is-collection' ); 26 var isTypedArrayLike = require( '@stdlib/assert/is-typed-array-like' ); 27 var isArrayBuffer = require( '@stdlib/assert/is-arraybuffer' ); 28 var setReadOnly = require( '@stdlib/utils/define-nonenumerable-read-only-property' ); 29 var setReadOnlyAccessor = require( '@stdlib/utils/define-nonenumerable-read-only-accessor' ); 30 var ctors = require( './../../typed-ctors' ); 31 var copy = require( '@stdlib/utils/copy' ); 32 var ArrayBuffer = require( './../../buffer' ); 33 var ceil = require( '@stdlib/math/base/special/ceil' ); 34 var floor = require( '@stdlib/math/base/special/floor' ); 35 var ceil2 = require( '@stdlib/math/base/special/ceil2' ); 36 var log2 = require( '@stdlib/math/base/special/log2' ); 37 var min = require( '@stdlib/math/base/special/min' ); 38 var defaults = require( './defaults.json' ); 39 var validate = require( './validate.js' ); 40 var createPool = require( './pool.js' ); 41 var BYTES_PER_ELEMENT = require( './bytes_per_element.json' ); 42 43 44 // MAIN // 45 46 /** 47 * Creates a typed array pool. 48 * 49 * @param {Options} [options] - pool options 50 * @param {NonNegativeInteger} [options.highWaterMark] - maximum total memory which can be allocated 51 * @throws {TypeError} options argument must be an object 52 * @throws {TypeError} must provide valid options 53 * @returns {Function} allocator 54 * 55 * @example 56 * var typedarraypool = factory(); 57 * 58 * // Allocate an array of doubles: 59 * var arr = typedarraypool( 5, 'float64' ); 60 * // returns <Float64Array>[ 0.0, 0.0, 0.0, 0.0, 0.0 ] 61 * 62 * arr[ 0 ] = 3.14; 63 * arr[ 1 ] = 3.14; 64 * 65 * // ... 66 * 67 * // Free the allocated memory to be used in a future allocation: 68 * typedarraypool.free( arr ); 69 */ 70 function factory( options ) { 71 var nbytes; 72 var pool; 73 var opts; 74 var err; 75 76 opts = copy( defaults ); 77 if ( arguments.length ) { 78 err = validate( opts, options ); 79 if ( err ) { 80 throw err; 81 } 82 } 83 pool = createPool( ceil( log2( opts.highWaterMark ) ) ); 84 nbytes = 0; 85 86 setReadOnly( malloc, 'malloc', malloc ); // circular reference 87 setReadOnly( malloc, 'calloc', calloc ); 88 setReadOnly( malloc, 'free', free ); 89 setReadOnly( malloc, 'clear', clear ); 90 setReadOnly( malloc, 'highWaterMark', opts.highWaterMark ); 91 setReadOnlyAccessor( malloc, 'nbytes', getBytes ); 92 93 return malloc; 94 95 /** 96 * Returns the number of allocated bytes. 97 * 98 * @private 99 * @returns {NonNegativeInteger} number of allocated bytes 100 */ 101 function getBytes() { 102 return nbytes; 103 } 104 105 /** 106 * Returns an array buffer. 107 * 108 * @private 109 * @param {NonNegativeInteger} n - number of bytes 110 * @returns {(ArrayBuffer|null)} array buffer or null 111 */ 112 function arraybuffer( n ) { 113 var buf; 114 var i; 115 116 // Convert the number of bytes to an index in our pool table: 117 i = log2( n ); 118 119 // If we already have an available array buffer, use it... 120 if ( i < pool.length && pool[ i ].length ) { 121 return pool[ i ].pop(); 122 } 123 // Before allocating a new array buffer, ensure that we have not exceeded the maximum number of bytes we are allowed to allocate... 124 if ( nbytes+n > opts.highWaterMark ) { 125 return null; 126 } 127 buf = new ArrayBuffer( n ); 128 129 // Update the running counter of allocated bytes: 130 nbytes += n; 131 132 return buf; 133 } 134 135 /** 136 * Returns a typed array. 137 * 138 * @private 139 * @param {Function} ctor - typed array constructor 140 * @param {NonNegativeInteger} len - view length 141 * @param {string} dtype - data type 142 * @returns {(TypedArray|null)} typed array or null 143 */ 144 function typedarray( ctor, len, dtype ) { 145 var buf; 146 if ( len === 0 ) { 147 return new ctor( 0 ); 148 } 149 buf = arraybuffer( ceil2( len )*BYTES_PER_ELEMENT[ dtype ] ); 150 if ( buf === null ) { 151 return buf; 152 } 153 return new ctor( buf, 0, len ); 154 } 155 156 /** 157 * Returns an uninitialized typed array. 158 * 159 * ## Notes 160 * 161 * - Memory is **not** initialized. 162 * - Memory is lazily allocated. 163 * - If the function returns `null`, the function was unable to allocate a new typed array from the typed array pool (most likely due to insufficient memory). 164 * 165 * @private 166 * @param {(NonNegativeInteger|Collection)} [arg] - an array length or an array-like object 167 * @param {string} [dtype="float64"] - data type 168 * @throws {TypeError} must provide a valid array length or an array-like object 169 * @throws {TypeError} must provide a recognized data type 170 * @returns {(TypedArray|null)} typed array or null 171 */ 172 function malloc() { 173 var nargs; 174 var dtype; 175 var ctor; 176 var len; 177 var arr; 178 var out; 179 var i; 180 181 nargs = arguments.length; 182 if ( nargs && isString( arguments[ nargs-1 ] ) ) { 183 nargs -= 1; 184 dtype = arguments[ nargs ]; 185 } else { 186 dtype = 'float64'; 187 } 188 ctor = ctors( dtype ); 189 if ( ctor === null ) { 190 throw new TypeError( 'invalid argument. Must provide a recognized data type. Value: `'+dtype+'`.' ); 191 } 192 if ( nargs <= 0 ) { 193 return new ctor( 0 ); 194 } 195 // Check if provided a typed array length... 196 if ( isNonNegativeInteger( arguments[ 0 ] ) ) { 197 return typedarray( ctor, arguments[ 0 ], dtype ); 198 } 199 // Check if provided an array-like object containing data elements... 200 if ( isCollection( arguments[ 0 ] ) ) { 201 arr = arguments[ 0 ]; 202 len = arr.length; 203 out = typedarray( ctor, len, dtype ); 204 if ( out === null ) { 205 return out; 206 } 207 for ( i = 0; i < len; i++ ) { 208 out[ i ] = arr[ i ]; 209 } 210 return out; 211 } 212 throw new TypeError( 'invalid argument. First argument must be either an array length or an array-like object. Value: `'+arguments[ 0 ]+'`.' ); 213 } 214 215 /** 216 * Returns a zero-initialized typed array. 217 * 218 * ## Notes 219 * 220 * - If the function returns `null`, the function was unable to allocate a new typed array from the typed array pool (most likely due to insufficient memory). 221 * 222 * @private 223 * @param {NonNegativeInteger} [len=0] - array length 224 * @param {string} [dtype="float64"] - data type 225 * @throws {TypeError} must provide a valid array length 226 * @throws {TypeError} must provide a recognized data type 227 * @returns {(TypedArray|null)} typed array or null 228 */ 229 function calloc() { 230 var nargs; 231 var out; 232 var i; 233 234 nargs = arguments.length; 235 if ( nargs === 0 ) { 236 out = malloc(); 237 } else if ( nargs === 1 ) { 238 out = malloc( arguments[ 0 ] ); 239 } else { 240 out = malloc( arguments[ 0 ], arguments[ 1 ] ); 241 } 242 if ( out !== null ) { 243 // Initialize the memory... 244 for ( i = 0; i < out.length; i++ ) { 245 out[ i ] = 0.0; 246 } 247 } 248 return out; 249 } 250 251 /** 252 * Frees a typed array or typed array buffer. 253 * 254 * ## Notes 255 * 256 * - Implicitly, we support providing non-internally allocated arrays and array buffer (e.g., "freeing" a typed array allocated in userland); however, the freed array buffer is likely to have excess capacity when compared to other members in its pool. 257 * 258 * @private 259 * @param {(TypedArray|ArrayBuffer)} buf - typed array or array buffer to free 260 * @throws {TypeError} must provide a typed array or typed array buffer 261 * @returns {boolean} boolean indicating whether the typed array or array buffer was successfully freed 262 */ 263 function free( buf ) { 264 var n; 265 var p; 266 var i; 267 if ( isTypedArrayLike( buf ) && buf.buffer ) { 268 buf = buf.buffer; 269 } else if ( !isArrayBuffer( buf ) ) { 270 throw new TypeError( 'invalid argument. Must provide a typed array or typed array buffer. Value: `'+buf+'`.' ); 271 } 272 if ( buf.byteLength > 0 ) { 273 n = floor( log2( buf.byteLength ) ); 274 275 // Prohibit "freeing" array buffers which would potentially allow users to circumvent high water mark limits: 276 n = min( pool.length-1, n ); 277 278 // Ensure that we do not attempt to free the same buffer more than once... 279 p = pool[ n ]; 280 for ( i = 0; i < p.length; i++ ) { 281 if ( p[ i ] === buf ) { 282 return false; 283 } 284 } 285 // Add the buffer to our pool of free buffers: 286 p.push( buf ); 287 } 288 return true; 289 } 290 291 /** 292 * Clears the typed array pool allowing garbage collection of previously allocated (and currently free) array buffers. 293 * 294 * @private 295 */ 296 function clear() { 297 var i; 298 for ( i = 0; i < pool.length; i++ ) { 299 pool[ i ].length = 0; 300 } 301 nbytes = 0; 302 } 303 } 304 305 306 // EXPORTS // 307 308 module.exports = factory;