numrs2 0.3.3

A Rust implementation inspired by NumPy for numerical computing (NumRS2)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
//! Memory allocation strategy selection and configuration
//!
//! This module defines the various memory allocation strategies and
//! provides the ability to select the appropriate strategy for
//! different numerical workloads.

use std::alloc::{alloc, dealloc, Layout};
use std::marker::{Send, Sync};
use std::ptr::NonNull;
use std::sync::atomic::{AtomicUsize, Ordering};

use super::aligned::{AlignedAllocator, AlignmentConfig};
use super::arena::{ArenaAllocator, ArenaConfig};
use super::pool::{PoolAllocator, PoolConfig};

/// Memory allocation strategies for numerical computing
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AllocStrategy {
    /// Standard system allocator
    Standard,
    /// Memory pool for fixed-size allocations
    Pool,
    /// Arena allocator for bulk temporary allocations
    Arena,
    /// Aligned allocator for SIMD operations
    Aligned,
    /// Automatic selection based on workload characteristics
    Auto,
}

// Global allocation strategy
static GLOBAL_STRATEGY: AtomicUsize = AtomicUsize::new(0); // 0 = Standard

/// Set the global allocation strategy
pub fn set_global_allocator(strategy: AllocStrategy) {
    let value = match strategy {
        AllocStrategy::Standard => 0,
        AllocStrategy::Pool => 1,
        AllocStrategy::Arena => 2,
        AllocStrategy::Aligned => 3,
        AllocStrategy::Auto => 4,
    };
    GLOBAL_STRATEGY.store(value, Ordering::SeqCst);
}

/// Get the current global allocation strategy
pub fn get_global_allocator_strategy() -> AllocStrategy {
    match GLOBAL_STRATEGY.load(Ordering::SeqCst) {
        0 => AllocStrategy::Standard,
        1 => AllocStrategy::Pool,
        2 => AllocStrategy::Arena,
        3 => AllocStrategy::Aligned,
        4 => AllocStrategy::Auto,
        _ => AllocStrategy::Standard, // Fallback
    }
}

/// Reset the global allocator to the default strategy (Standard)
pub fn reset_global_allocator() {
    GLOBAL_STRATEGY.store(0, Ordering::SeqCst);
}

/// Get the default memory allocator based on global strategy
pub fn get_default_allocator() -> Box<dyn MemoryAllocator> {
    match get_global_allocator_strategy() {
        AllocStrategy::Standard => Box::new(StandardAllocator),
        AllocStrategy::Pool => Box::new(PoolAllocator::new(PoolConfig::default())),
        AllocStrategy::Arena => Box::new(ArenaAllocator::new(ArenaConfig::default())),
        AllocStrategy::Aligned => Box::new(AlignedAllocator::new(AlignmentConfig::default())),
        AllocStrategy::Auto => Box::new(AutoAllocator::new()),
    }
}

/// Get the recommended allocation strategy based on workload characteristics
pub fn recommend_strategy(
    alloc_size: usize,
    alloc_frequency: AllocFrequency,
    simd_usage: bool,
) -> AllocStrategy {
    match (alloc_size, alloc_frequency, simd_usage) {
        // Large allocations are best with the standard allocator
        (size, _, _) if size > 1_000_000 => AllocStrategy::Standard,

        // Very small, frequent allocations work well with a pool
        (size, AllocFrequency::VeryHigh, _) if size < 8192 => AllocStrategy::Pool,

        // Medium, frequent allocations work well with an arena
        (size, AllocFrequency::High, _) if size < 65536 => AllocStrategy::Arena,

        // SIMD operations benefit from aligned memory
        (_, _, true) => AllocStrategy::Aligned,

        // Default to standard allocator for other cases
        _ => AllocStrategy::Standard,
    }
}

/// Allocation frequency classification
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AllocFrequency {
    /// Very infrequent allocations (once per operation)
    Low,
    /// Moderate allocation frequency
    Medium,
    /// High allocation frequency (many per operation)
    High,
    /// Very high allocation frequency (thousands per operation)
    VeryHigh,
}

/// Trait for memory allocators
pub trait MemoryAllocator: Send + Sync {
    /// Allocate memory of the given size
    fn allocate(&self, size: usize) -> Option<NonNull<u8>>;

    /// Allocate memory with the given layout
    fn allocate_layout(&self, layout: Layout) -> Option<NonNull<u8>>;

    /// Deallocate previously allocated memory
    ///
    /// # Safety
    ///
    /// - The pointer must have been allocated by this allocator
    /// - The layout must match what was used for allocation
    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout);
}

/// Standard system allocator
#[derive(Debug, Clone, Copy)]
pub struct StandardAllocator;

impl MemoryAllocator for StandardAllocator {
    fn allocate(&self, size: usize) -> Option<NonNull<u8>> {
        if size == 0 {
            return None;
        }
        let layout = Layout::from_size_align(size, 8).ok()?;
        self.allocate_layout(layout)
    }

    fn allocate_layout(&self, layout: Layout) -> Option<NonNull<u8>> {
        unsafe { NonNull::new(alloc(layout)) }
    }

    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
        dealloc(ptr.as_ptr(), layout);
    }
}

/// Auto-selecting allocator based on workload characteristics
pub struct AutoAllocator {
    standard: StandardAllocator,
    pool: PoolAllocator,
    arena: ArenaAllocator,
    aligned: AlignedAllocator,
}

impl Default for AutoAllocator {
    fn default() -> Self {
        Self::new()
    }
}

impl AutoAllocator {
    /// Create a new auto-selecting allocator
    pub fn new() -> Self {
        Self {
            standard: StandardAllocator,
            pool: PoolAllocator::new(PoolConfig::default()),
            arena: ArenaAllocator::new(ArenaConfig::default()),
            aligned: AlignedAllocator::new(AlignmentConfig::default()),
        }
    }

    /// Select the appropriate allocator for the given allocation size
    fn select_allocator(&self, size: usize) -> &dyn MemoryAllocator {
        // Simple selection based just on size
        // A more sophisticated implementation would consider more factors
        match size {
            0..=4096 => &self.pool as &dyn MemoryAllocator,
            4097..=65536 => &self.arena as &dyn MemoryAllocator,
            _ => &self.standard as &dyn MemoryAllocator,
        }
    }
}

impl MemoryAllocator for AutoAllocator {
    fn allocate(&self, size: usize) -> Option<NonNull<u8>> {
        if size == 0 {
            return None;
        }

        // For SIMD operations, we typically want aligned memory
        // But only for small to medium sizes - large allocations use standard
        // This is a simplification - in a real implementation, we would detect
        // if the allocation is for SIMD usage
        if size.is_multiple_of(16) && (16..=65536).contains(&size) {
            return self.aligned.allocate(size);
        }

        self.select_allocator(size).allocate(size)
    }

    fn allocate_layout(&self, layout: Layout) -> Option<NonNull<u8>> {
        // If highly aligned, use aligned allocator
        if layout.align() >= 16 {
            return self.aligned.allocate(layout.size());
        }

        self.select_allocator(layout.size()).allocate_layout(layout)
    }

    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
        // Select the same allocator that would have been used for the allocation
        // Match the routing criteria from allocate() method: size-based routing
        let size = layout.size();
        if size.is_multiple_of(16) && (16..=65536).contains(&size) {
            self.aligned.deallocate(ptr, size);
            return;
        }

        self.select_allocator(size).deallocate(ptr, layout);
    }
}

// For Arena allocator
impl MemoryAllocator for ArenaAllocator {
    fn allocate(&self, size: usize) -> Option<NonNull<u8>> {
        ArenaAllocator::allocate(self, size)
    }

    fn allocate_layout(&self, layout: Layout) -> Option<NonNull<u8>> {
        self.allocate_aligned(layout.size(), layout.align())
    }

    unsafe fn deallocate(&self, _ptr: NonNull<u8>, _layout: Layout) {
        // ArenaAllocator doesn't deallocate individual allocations
        // They're freed when the arena is reset
    }
}

// For Pool allocator
impl MemoryAllocator for PoolAllocator {
    fn allocate(&self, size: usize) -> Option<NonNull<u8>> {
        if size <= self.block_size() {
            PoolAllocator::allocate(self)
        } else {
            None // Block size too small
        }
    }

    fn allocate_layout(&self, layout: Layout) -> Option<NonNull<u8>> {
        if layout.size() <= self.block_size() {
            PoolAllocator::allocate(self)
        } else {
            None // Block size too small
        }
    }

    unsafe fn deallocate(&self, ptr: NonNull<u8>, _layout: Layout) {
        PoolAllocator::deallocate(self, ptr);
    }
}

// For Aligned allocator
impl MemoryAllocator for AlignedAllocator {
    fn allocate(&self, size: usize) -> Option<NonNull<u8>> {
        AlignedAllocator::allocate(self, size)
    }

    fn allocate_layout(&self, layout: Layout) -> Option<NonNull<u8>> {
        // Use the regular allocate method but ensure alignment is respected
        self.allocate(layout.size())
    }

    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
        AlignedAllocator::deallocate(self, ptr, layout.size());
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_global_allocator_settings() {
        // Test setting and getting global allocator strategy
        set_global_allocator(AllocStrategy::Pool);
        assert_eq!(get_global_allocator_strategy(), AllocStrategy::Pool);

        set_global_allocator(AllocStrategy::Arena);
        assert_eq!(get_global_allocator_strategy(), AllocStrategy::Arena);

        reset_global_allocator();
        assert_eq!(get_global_allocator_strategy(), AllocStrategy::Standard);
    }

    #[test]
    fn test_recommended_strategies() {
        // Test strategy recommendations for different workloads

        // Large allocation should use standard allocator
        let large_alloc_strategy = recommend_strategy(2_000_000, AllocFrequency::Low, false);
        assert_eq!(large_alloc_strategy, AllocStrategy::Standard);

        // Small, very frequent allocations should use pool
        let small_freq_strategy = recommend_strategy(100, AllocFrequency::VeryHigh, false);
        assert_eq!(small_freq_strategy, AllocStrategy::Pool);

        // Medium, high frequency allocations should use arena
        let medium_freq_strategy = recommend_strategy(10_000, AllocFrequency::High, false);
        assert_eq!(medium_freq_strategy, AllocStrategy::Arena);

        // SIMD operations should use aligned allocator
        let simd_strategy = recommend_strategy(1024, AllocFrequency::Medium, true);
        assert_eq!(simd_strategy, AllocStrategy::Aligned);
    }

    #[test]
    fn test_standard_allocator() {
        let allocator = StandardAllocator;

        // Allocate some memory
        let layout = Layout::from_size_align(100, 8).expect("Layout should succeed");
        let ptr = allocator
            .allocate_layout(layout)
            .expect("Allocation should succeed");

        // Write to the memory to ensure it's valid
        unsafe {
            let slice = std::slice::from_raw_parts_mut(ptr.as_ptr(), 100);
            for (i, item) in slice.iter_mut().enumerate() {
                *item = i as u8;
            }

            // Read back and verify
            for (i, &item) in slice.iter().enumerate() {
                assert_eq!(item, i as u8);
            }

            // Deallocate
            allocator.deallocate(ptr, layout);
        }
    }

    #[test]
    fn test_auto_allocator() {
        let allocator = AutoAllocator::new();

        // Test small allocation (should use pool)
        let small_ptr = allocator
            .allocate(100)
            .expect("Small allocation should succeed");

        // Test medium allocation (should use arena)
        let medium_ptr = allocator
            .allocate(10_000)
            .expect("Medium allocation should succeed");

        // Test large allocation (should use standard)
        let large_ptr = allocator
            .allocate(100_000)
            .expect("Large allocation should succeed");

        // Test aligned allocation (should use aligned)
        let layout = Layout::from_size_align(64, 64).expect("Layout should succeed");
        let aligned_ptr = allocator
            .allocate_layout(layout)
            .expect("Aligned allocation should succeed");
        assert_eq!(
            aligned_ptr.as_ptr() as usize % 64,
            0,
            "Should be 64-byte aligned"
        );

        // Deallocate all
        unsafe {
            allocator.deallocate(
                small_ptr,
                Layout::from_size_align(100, 8).expect("Layout should succeed"),
            );
            allocator.deallocate(
                medium_ptr,
                Layout::from_size_align(10_000, 8).expect("Layout should succeed"),
            );
            allocator.deallocate(
                large_ptr,
                Layout::from_size_align(100_000, 8).expect("Layout should succeed"),
            );
            allocator.deallocate(aligned_ptr, layout);
        }
    }

    #[test]
    fn test_get_default_allocator() {
        // Test getting the default allocator with different strategies

        // Standard
        set_global_allocator(AllocStrategy::Standard);
        let allocator = get_default_allocator();
        let ptr = allocator.allocate(100).expect("Allocation should succeed");
        unsafe {
            allocator.deallocate(
                ptr,
                Layout::from_size_align(100, 8).expect("Layout should succeed"),
            );
        }

        // Pool
        set_global_allocator(AllocStrategy::Pool);
        let allocator = get_default_allocator();
        let ptr = allocator.allocate(100).expect("Allocation should succeed");
        unsafe {
            allocator.deallocate(
                ptr,
                Layout::from_size_align(100, 8).expect("Layout should succeed"),
            );
        }

        // Reset to standard
        reset_global_allocator();
    }
}