Suppose I have a function f that takes some input and produces a number. Inside the function f , a list is created by the input, which is then reduced (for example, using foldl' g ) to obtain the final output number. Since in the end the intermediate list must be reduced, is it possible to apply the reduction function g without expressing the intermediate list. The goal here is to limit the memory used for storage (or expression if you "save" a less accurate word).
To illustrate this, this foldPairProduct function takes up O(N1 * N2) space for an intermediate list (space consumed can be more complex due to expression and lazy rating, but I assume it is proportional or worse). Here N1, N2 is the size of two input lists.
foldPairProduct :: (Num a, Ord a) => (a -> a -> a) -> [a] -> [a] -> a foldPairProduct f xs ys = foldl1 f [ x*y | x <- xs, y <- ys]
An alternative implementation of the logic is the foldPairProduct command, which occupies the O(2 * 2) space.
foldPairProduct' :: Num a => (Maybe a -> Maybe a -> Maybe a) -> [a] -> [a] -> Maybe a foldPairProduct' _ _ [] = Nothing foldPairProduct' _ [] _ = Nothing foldPairProduct' f (x:xs) (y:ys) = foldl1 f [Just $ x*y, foldPairProduct' f [x] ys, foldPairProduct' f xs [y], foldPairProduct' f xs ys]
The situation is exacerbated for foldCrossProduct , whose implementation is similar to foldPairProduct , except that it accepts multiple lists as input. Its spatial complexity (still in the sense of imperative languages) for the intermediate list is O(N1 * N2 * ...* Nk) , where k is the length [[a]] .
foldCrossProduct :: Num a => (a -> a -> a) -> [[a]] -> a foldCrossProduct f xss = foldl1 f (crossProduct xss) crossProduct :: Num a => [[a]] -> [a] crossProduct [] = [] crossProduct (xs:[]) = xs crossProduct (xs:xss) = [x * y | x <- xs, y <- crossProduct xss]
If we followed the idea of ββimplementing foldPairProduct' , then the complexity of the space would be k^2 , which is much more efficient in terms of space. My questions:
I have implemented foldPairProduct' for a couple of lists. However, it seems that implementing it for an arbitrary number of lists is not easy.
I do not want to compare Haskell with an imperative language, but is there an implementation that will use constant space (or in another word without expressing an intermediate list of the specified length)? Maybe Monad would help, but I'm not very familiar with this.
Does the compiler really do its magic? That is, he notices that the list is intermediate and needs to be reduced, and really figure out how to efficiently calculate it. In the end, this is what I was thinking about lazy evaluating and optimizing the compiler.
Any comments are welcome. Thank you
Update 1
The performance test confirmed the analysis of the "spatial complexity" of foldPairProduct and foldCrossProduct based on a change in input size N1, N2, N3 and observation of bytes copied by GC.
The performance test did not confirm the foldPairProduct' analysis, which unexpectedly showed N1 * N2 or even worse use of space. This is likely due to the fact that the recursive call is inefficiently evaluated. The results are attached below (with ghc settings the same as Yuras).
Update 2
Another experiment is updated when I learn from comments and answers. For foldPairProduct total memory used corresponds to the spatial complexity explained by Daniel Fisher.
For foldCrossProduct , although Daniel complexity analysis makes sense to me, the results do not show linear memory usage. Following Daniel's advice, x <- xs and y <- crossproduct ys , and this really led to the complexity of the linear space.
For foldCrossProduct (max) [[1..100],[1..n], [1..1000]] , with n = 100, 1000, 10000, 100000, the used memory is 2, 2, 3, 14 MB .
foldPairProduct [1..n] [1..10000]
n = 100 120,883,320 bytes allocated in the heap 56,867,728 bytes copied during GC 428,384 bytes maximum residency (50 sample(s)) 98,664 bytes maximum slop 3 MB total memory in use (0 MB lost due to fragmentation) n = 1000 1,200,999,280 bytes allocated in the heap 569,837,360 bytes copied during GC 428,384 bytes maximum residency (500 sample(s)) 99,744 bytes maximum slop 3 MB total memory in use (0 MB lost due to fragmentation) n = 10000 12,002,152,040 bytes allocated in the heap 5,699,468,024 bytes copied during GC 428,384 bytes maximum residency (5000 sample(s)) 99,928 bytes maximum slop 3 MB total memory in use (0 MB lost due to fragmentation) n = 100000 120,013,672,800 bytes allocated in the heap 56,997,625,608 bytes copied during GC 428,384 bytes maximum residency (50000 sample(s)) 99,984 bytes maximum slop 3 MB total memory in use (0 MB lost due to fragmentation)
foldPairProduct [1..10000] [1..n]
n = 100 121,438,536 bytes allocated in the heap 55,920 bytes copied during GC 32,408 bytes maximum residency (1 sample(s)) 19,856 bytes maximum slop 1 MB total memory in use (0 MB lost due to fragmentation) n = 1000 1,201,511,296 bytes allocated in the heap 491,864 bytes copied during GC 68,392 bytes maximum residency (1 sample(s)) 20,696 bytes maximum slop 1 MB total memory in use (0 MB lost due to fragmentation) n = 10000 12,002,232,056 bytes allocated in the heap 5,712,004,584 bytes copied during GC 428,408 bytes maximum residency (5000 sample(s)) 98,688 bytes maximum slop 3 MB total memory in use (0 MB lost due to fragmentation) n = 100000 120,009,432,816 bytes allocated in the heap 81,694,557,064 bytes copied during GC 4,028,408 bytes maximum residency (10002 sample(s)) 769,720 bytes maximum slop 14 MB total memory in use (0 MB lost due to fragmentation)
foldPairProduct [1..n] [1..n]
n = 100 1,284,024 bytes allocated in the heap 15,440 bytes copied during GC 32,336 bytes maximum residency (1 sample(s)) 19,920 bytes maximum slop 1 MB total memory in use (0 MB lost due to fragmentation) n = 1000 120,207,224 bytes allocated in the heap 114,848 bytes copied during GC 68,336 bytes maximum residency (1 sample(s)) 24,832 bytes maximum slop 1 MB total memory in use (0 MB lost due to fragmentation) n = 10000 12,001,432,024 bytes allocated in the heap 5,708,472,592 bytes copied during GC 428,336 bytes maximum residency (5000 sample(s)) 99,960 bytes maximum slop 3 MB total memory in use (0 MB lost due to fragmentation) n = 100000 1,200,013,672,824 bytes allocated in the heap 816,574,713,664 bytes copied during GC 4,028,336 bytes maximum residency (100002 sample(s)) 770,264 bytes maximum slop 14 MB total memory in use (0 MB lost due to fragmentation)
foldCrossProduct (max) [[1..n], [1..100], [1..1000]]
n = 100 105,131,320 bytes allocated in the heap 38,697,432 bytes copied during GC 427,832 bytes maximum residency (34 sample(s)) 209,312 bytes maximum slop 3 MB total memory in use (0 MB lost due to fragmentation) n = 1000 1,041,254,480 bytes allocated in the heap 374,148,224 bytes copied during GC 427,832 bytes maximum residency (334 sample(s)) 211,936 bytes maximum slop 3 MB total memory in use (0 MB lost due to fragmentation) n = 10000 10,402,479,240 bytes allocated in the heap 3,728,429,728 bytes copied during GC 427,832 bytes maximum residency (3334 sample(s)) 215,936 bytes maximum slop 3 MB total memory in use (0 MB lost due to fragmentation)
foldCrossProduct (max) [[1..100], [1..n], [1..1000]]
n = 100 105,131,344 bytes allocated in the heap 38,686,648 bytes copied during GC 431,408 bytes maximum residency (34 sample(s)) 205,456 bytes maximum slop 3 MB total memory in use (0 MB lost due to fragmentation) n = 1000 1,050,614,504 bytes allocated in the heap 412,084,688 bytes copied during GC 4,031,456 bytes maximum residency (53 sample(s)) 1,403,976 bytes maximum slop 15 MB total memory in use (0 MB lost due to fragmentation) n = 10000 quit after over 1362 MB total memory in use (0 MB lost due to fragmentation)
foldPairProduct '[1..n] [1..n]
n = 100 4,351,176 bytes allocated in the heap 59,432 bytes copied during GC 74,296 bytes maximum residency (1 sample(s)) 21,320 bytes maximum slop 1 MB total memory in use (0 MB lost due to fragmentation) n = 1000 527,009,960 bytes allocated in the heap 45,827,176 bytes copied during GC 211,680 bytes maximum residency (1 sample(s)) 25,760 bytes maximum slop 2 MB total memory in use (0 MB lost due to fragmentation)