In Swift, how efficient is it to append an array? - arrays

If you have an array which can very in size throughout the course of your program, would it be more efficient to declare the array as the maximum size it will ever reach and then control how much of the array your program can access, or to change the size of the array quite frequently throughout the course of the program?

From the Swift headers, there's this about array growth and capacity:
When an array's contiguous storage fills up, new storage must be allocated and elements must be moved to the new storage. Array, ContiguousArray, and Slice share an exponential growth strategy that makes append a constant time operation when amortized over many invocations. In addition to a count property, these array types have a capacity that reflects their potential to store elements without reallocation, and when you know how many elements you'll store, you can call reserveCapacity to pre-emptively reallocate and prevent intermediate reallocations.
Reading that, I'd say it's best to reserve the capacity you need, and only come back to optimize that if you find it's really a problem. You'll make more work for yourself if you're faking the length all the time.

Related

Is it possible to implement a dynamic array without reallocation?

The default way to implement dynamic arrays is to use realloc. Once len == capacity we use realloc to grow our array. This can cause copying of the whole array to another heap location. I don't want this copying to happen, since I'm designing a dynamic array that should be able to store large amount of elements, and the system that would run this code won't be able to handle such a heavy operation.
Is there a way to achieve that?
I'm fine with loosing some performance - O(logN) for search instead of O(1) is okay. I was thinking that I could use a hashtable for this, but it looks like I'm in a deadlock since in order to implement such a hashtable I would need a dynamic array in the first place.
Thanks!
Not really, not in the general case.
The copy happens when the memory manager can't increase the the current allocation, and needs to move the memory block somewhere else.
One thing you can try is to allocate fixed sized blocks and keep a dynamic array pointing to the blocks. This way the blocks don't need to be reallocated, keeping the large payloads in place. If you need to reallocate, you only reallocate the array of reference which should be much cheaper (move 8 bytes instead 1 or more MB). The ideal case the block size is about sqrt(N), so it's not working in a very general case (any fixed size will be some large or some small for some values).
If you are not against small allocations, you could use a singly linked list of tables, where each new table doubles the capacity and becomes the front of the list.
If you want to access the last element, you can just get the value from the last allocated block, which is O(1).
If you want to access the first element, you have to run through the list of allocated blocks to get to the correct block. Since the length of each block is two times the previous one, this means the access complexity is O(logN).
This data structures relies on the same principles that dynamic arrays use (doubling the size of the array when expanding), but instead of copying the values after allocating a new block, it keeps track of the previous block, meaning accessing the previous blocks adds overhead but not accessing the last ones.
The index is not a position in a specific block, but in an imaginary concatenation of all the blocks, starting from the first allocated block.
Thus, this data structure cannot be implemented as a recursive type because it needs a wrapper keeping track of the total capacity to compute which block is refered to.
For example:
There are three blocks, of sizes 100, 200, 400.
Accessing 150th value (index 149 if starting from 0) means the 50th value of the second block. The interface needs to know the total length is 700, compare the index to 700 - 400 to determine whether the index refers to the last block (if the index is above 300) or a previous block.
Then, the interface compares with the capacity of the previous blocks (300 - 200) and knows 150 is in the second block.
This algorithm can have as many iterations as there are blocks, which is O(logN).
Again, if you only try to access the last value, the complexity becomes O(1).
If you have concerns about copy times for real time applications or large amounts of data, this data structure could be better than having a contiguous storage and having to copy all of your data in some cases.
I ended up with the following:
Implement "small dynamic array" that can grow, but only up to some maximum capacity (e.g. 4096 words).
Implement an rbtree
Combine them together to make a "big hash map", where "small array" is used as a table and a bunch of rbtrees are used as buckets.
Use this hashmap as a base for a "big dynamic array", using indexes as keys
While the capacity is less than maximum capacity, the table grows according to the load factor. Once the capacity reached maximum, the table won't grow anymore, and new elements are just inserted into buckets. This structure in theory should work with O(log(N/k)) complexity.

How to efficiently append items to large arrays in Swift?

I am working on a Swift project the involves very large dynamically changing arrays. I am running into a problem where each successive operation take longer than the former. I am reasonably sure this problem is caused by appending to the arrays, as I get the same problem with a simple test that just appends to a large array.
My Test Code:
import Foundation
func measureExecution(elements: Int, appendedValue: Int) -> Void {
var array = Array(0...elements)
//array.reserveCapacity(elements)
let start = DispatchTime.now()
array.append(appendedValue)
let end = DispatchTime.now()
print(Double(end.uptimeNanoseconds - start.uptimeNanoseconds) / 1_000_000_000)
}
for i in 0...100 {
measureExecution(elements: i*10000, appendedValue: 1)
}
This tries for a 100 different array sizes between 10000 and 1000000, timing how long it take to append one item to the end of the array. As I understand it, Swift arrays are dynamic arrays that will reallocate memory geometrically (it allocates more and more memory each time it needs to reallocate), which Apple's documentation says should mean appending a single element to an array is an O(1) operation when averaged over many calls to the append(_:) method (source). As such, I don't think memory allocation is causing the issue.
However, there is a linear relationship between the length of the array and the time it takes to append an element. I graphed the times for a bunch of array lengths, and baring some outliers it is pretty clearly O(n). I also ran the same test with reserved capacity (commented out in the code block) to confirm that memory allocation was not the issue, and I got nearly identical results:
How do I efficiently append to the end of massive arrays (preferably without using reserveCapacity)?
From what I've read, Swift arrays pre-allocate storage. Each time you fill an Array's allocated storage, it doubles the space allocated. That way you don't do a new memory allocation that often, and also don't allocate a bunch of space you don't need.
The Array class does have a reserveCapacity(_:). If you know how many elements you are going to store you might want to try that.

How is the term “dynamically sized” for slice justifiable, when maximum length of a slice is less than length of the underlying array?

From A Tour of Go:
An array has a fixed size. A slice, on the other hand, is a dynamically-sized, flexible view into the elements of an array.
How can the slice be called as dynamically sized when it cannot go beyond the size of the underlying array.
The size of a Go array is fixed at compile time. The size of a Go slice is set dynamically at runtime.
References:
The Go Blog: Go Slices: usage and internals
The Go Blog: Arrays, slices (and strings): The mechanics of 'append'
Even with a cap, a dynamic size is still dynamic: it can range between zero and whatever the cap is.
That said, as Flimzy noted in a comment, if you use an operation that can "grow" a slice, it always returns a new slice, or takes a pointer to the slice—usually the former—so that the routine that needed to go past the current capacity could allocate a new, larger array1 and make the slice use that instead of the old array.
That's why append returns a new value, and you must write:
s = append(s, element)
for instance.
(The previous underlying array, if any, is garbage collected if and when it is appropriate to do so. A nil slice has no underlying array and hence a zero capacity.)
1The runtime uses unsafe and other special tricks to allocate this array, bypassing type-checking, but coordinating with the runtime's own garbage collection code. Hence it can allocate an array whose size is chosen at runtime instead of at compile time. The compiler's new and make and append built-ins have access to this same ability.
You can write this same kind of tricky code yourself using unsafe, but if you do, you run the risk of having to rewrite your code when a new Go release comes out, if the new Go has changed something internally. So, don't do that: use append or make to create the runtime-sized array with the slice data already set up for you.
How can the slice be called as dynamically sized when it cannot go beyond the size of the underlying array.
The types are static vs dynamic. An array type is like [4]byte - the size is part of the type definition, and therefore set at compile time. Only a [4]byte can be stored in a variable of type [4]byte. Not a [3]byte, not a [5]byte. It's static.
A slice type is like []byte - the size isn't part of the type definition, so it is not set at compile time, and a slice of any size can be stored in a []byte at runtime. It could be zero-length, it could be a thousand, it could be a four-byte window into a million-length array. It's dynamic.
The size of a slice can also shrink and grow within its capacity at runtime, though the capacity can only change by replacing the underlying array (which, being an array, is of fixed size). This is done automatically behind the scenes by append, for example. But, to my understanding at least, this is not what makes slices "dynamic"; it's the fact that a slice can be of any size at runtime - it is not known at compile time. That is what defines them as "dynamic" to me.

Dynamic array guarantee clarification

In Skiena's Algorithm Design Manual, he mentions at one point:
The primary thing lost using dynamic arrays is the guarantee that each array
access takes constant time in the worst case. Now all the queries will be fast, except
for those relatively few queries triggering array doubling. What we get instead is a
promise that the nth array access will be completed quickly enough that the total
effort expended so far will still be O(n).
I'm struggling to understand this. How will an array query expand the array?
Dynamic arrays are arrays where the size does not need to be specified (Think of an ArrayList in java). Under the hood, dynamic arrays are implemented using a regular array. Though, because it's a regular array the implementation of the ArrayList needs to specify the size of the underlying array.
So the typical way to handle this in dynamic arrays is to initialize the standard array with a certain amount of elements, then when it reached it's maximum elements, the array is doubled in size.
Because of this underlying functionality, most of the time it will take constant time when adding to a dynamic array, but occasionally it will double the size of the 'under the hood' standard array which will take longer than the normal add time.
If your confusion lies with his use of the word 'query', I believe he means to say 'adding or removing from the array' because a simple 'get' query shouldn't be related to the underlying standard array size.

Vector object in swift

I've noticed that arrays are not declared with specific sizes in swift. I know c++ where this is not the case and to use a dynamic list I need to use a vector object. Is there a certain vector object in swift or do arrays adjust size at runtime?
Is there a certain vector object in swift or do arrays adjust size at runtime?
The latter: Any array reserves a well-determined amount of memory to hold its elements. Whenever you append elements to an array, making the array exceed the reserved capacity, it allocates a larger region of memory, whose size is a multiple of the old storage's capacity.
Why does an array grow its memory exponentially?
Exponential growth is meant to average the time performance of the append() (add element(s)) function. Whenever the memory is increased, the elements are copied into the new storage. Of course, when memory reallocation is triggered, the performance of the append() function is significantly reduced, though this happens less often as the array becomes bigger and bigger.
I suggest you to look over the Array Documentation, especially the "Growing the Size of an Array" Chapter. If you want to preallocate space in the array, it is good to know of the reserveCapacity(_:) method. Related SO post for more details about capacity.

Resources