Open swift-ci opened 7 years ago
Comment by Rejean Lamy (JIRA)
This is becoming more and more important especially since a new limitation appears with Xcode 11 / Swift 5, we can not access a C array of size over 4096!
With Xcode 10 this was possible, but no longer with Xcode 11 (see SR-11763).
The reason seems because large tuple are limiting compiler performance.
So the Swift compiler team decided to put a limit at 4096 on the size of a C array that can be exposed to Swift.
An array of size 4097 is too expensive but 4096 is ok !
My guess is they must decide of a limit because there is no Swift fixed-size array and they have no choice to rely on tuple to expose a C array.
This is very bad. Now for each C array larger than 4096 we must write glue code in C to access such array from Swift.
Yes, Swift needs a fixed-size array !
Additional Detail from JIRA
| | | |------------------|-----------------| |Votes | 9 | |Component/s | Compiler | |Labels | New Feature, LanguageFeatureRequest | |Assignee | None | |Priority | Medium | md5: a8d7bd10a0fb7f98dd6ea758e9bd4b0frelates to:
Issue Description:
Swift needs a fixed-size array.
Motivation: I’ve been porting code for Monte Carlo Tree Search in my Go-playing program from C++ to Swift. Performance is crucial for this code, as more simulations lead to better play. After the initial port, the Swift code was more than 10x slower than C++. After several weeks of optimizing, profiling, and digging through disassembly, I’ve gotten to within a factor of 2.
The main issues I had to overcome:
avoid heap allocation
avoid reference counting
avoid indirection
avoid initialization checks
For my application, these issues would all be addressed by having a fixed-size array data structure in Swift. My app is designed for a 19x19 (or smaller) Go board, not an arbitrary N x N board, so I don’t want to pay the high cost of variable-size data structures in the lowest levels of my app.
(Arrays in Swift are implemented as a header pointing to an object on the heap. Thus every array involves a heap allocation, and accessing an element in the array requires an extra indirection. Same for ContiguousArray.)
Workaround: Import C array
You can define a fixed-size array in C, then import that to Swift. For example, define the following in a C header file:
const int NumWords = 6;
struct Bitset {
uint64_t bits[NumWords];
};
Then in Swift:
struct PointSet {
var bits = Bitset()
}
The problem is that Bitset is imported into Swift as a tuple (Int, Int, Int, Int, Int, Int), and there is no direct way to use an index to access these members. You can hack it by going through UnsafeRawPointer:
struct FixedSizeArray\<T> {
var array = SomeArrayImportedFromC()
let count = ...
private func setElement(_ pointer: UnsafeMutableRawPointer, at index: Int, to value: T) {
let a = pointer.bindMemory(to: T.self, capacity: count)
a[index] = value
}
private func getElement(_ pointer: UnsafeRawPointer, at index: Int) -> T {
let a = pointer.bindMemory(to: T.self, capacity: count)
return a[index]
}
subscript(_ index: Int) -> T {
// This 'mutating' is necessary due to bug SR-4542.
mutating get { return getElement(&array, at: index) }
set { setElement(&array, at: index, to: newValue) }
}
}
This is really ugly, and the SR-4542 bug that forces the getter to be mutable impacts client code. It does, however, address the performance issue.
Array initialization
I have some arrays that are statically initialized, e.g. the following one to map a set of directions into a sequence of offsets, used for efficient iteration through neighbor points (goN, goW, goE, goS are constants):
let nbOffsetForSet: [Int8] = [
/* 0*/ 0, 0, 0, 0,
/* 1*/ goN, 0, 0, 0,
/* 2*/ goW, 0, 0, 0,
/* 3*/ goN, goW, 0, 0,
/* 4*/ goE, 0, 0, 0,
/* 5*/ goN, goE, 0, 0,
/* 6*/ goW, goE, 0, 0,
/* 7*/ goN, goW, goE, 0,
/* 8*/ goS, 0, 0, 0,
/* 9*/ goN, goS, 0, 0,
/10/ goW, goS, 0, 0,
/11/ goN, goW, goS, 0,
/12/ goE, goS, 0, 0,
/13/ goN, goE, goS, 0,
/14/ goW, goE, goS, 0,
/15/ goN, goW, goE, goS, 0, 0, 0, 0 ]
When declaring this constant array, Swift will initialize it once. However, before accessing it, the code needs to check whether it’s initialized. I get a significant performance boost by declaring the same array in C and using the following function:
func getNbOffsetForSet(_ index: Int) -> Int8 {
func getElement(_ pointer: UnsafeRawPointer, at index: Int) -> Int8 {
let a = pointer.bindMemory(to: Int8.self, capacity: 17*4)
return a[index]
}
return getElement(&nbOffsetForSetImportedFromC, at: index)
}
This kind of hack should just not be necessary to make Swift code perform well.
Proposal
Adding support for a fixed-size array in Swift would make it possible to write performant code without resorting to C and ugly hacks.
var a = FixedArray(repeating: 0, count: 10)
There are many possibilities for how to include this in Swift, e.g. a separate FixedArray class that behaves mostly like Array, except that the compiler knows that count and capacity are constant. But whichever way it’s done, I feel like this is the most important feature currently missing from Swift.