Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonasuncion committed Sep 24, 2020
0 parents commit 4b64e07
Show file tree
Hide file tree
Showing 11 changed files with 825 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
*.xcworkspacedata
21 changes: 21 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2020 Brandon Asuncion

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
29 changes: 29 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// swift-tools-version:5.2

import PackageDescription

let package = Package(
name: "SwissTable",
platforms: [
.macOS(.v10_15)
],
products: [
.library(
name: "SwissTable",
targets: ["SwissTable"]),
],
dependencies: [
],
targets: [
.target(
name: "SwissTable",
dependencies: [],
swiftSettings: [
.unsafeFlags(["-Xcc", "-mavx2"]),
]
),
.testTarget(
name: "SwissTableTests",
dependencies: ["SwissTable"]),
]
)
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Swiss Table

A [SIMD-accelerated](https://en.wikipedia.org/wiki/SIMD) hash table implementation in Swift based off of [Google's Abseil](https://abseil.io/about/design/swisstables) and [Rust's Hashbrown](https://github.com/rust-lang/hashbrown/) that utilizes the AVX2 instruction set to perform fast read/write performance.

Work in progress.

## License

[MIT License](https://choosealicense.com/licenses/mit/)
62 changes: 62 additions & 0 deletions Sources/SwissTable/Extensions/FixedWidthInteger.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
extension FixedWidthInteger {
@inlinable
var isPowerOf2: Bool {
assert(self >= 0)
return self & (self &- 1) == 0
}

@inlinable
func nextPowerOf2() -> Self {
assert(self >= 0)
return 1 &<< (Self.bitWidth &- (self &- 1).leadingZeroBitCount)
}

@inlinable
func prevPowerOf2() -> Self {
assert(self >= 0)
return nextPowerOf2() &>> 1
}
}

extension FixedWidthInteger {
@inlinable
func reduce(_ N: Self) -> Self {
self.multipliedFullWidth(by: N).high
}
}

extension FixedWidthInteger where Self: SignedInteger {
@_transparent
@inlinable
func alignedUp(to alignment: Self) -> Self {
assert(alignment > 0 && alignment.isPowerOf2)
return (self &+ alignment &- 1) & -alignment
}

@_transparent
@inlinable
func alignedUp<T>(to: T.Type) -> Self {
return self.alignedUp(to: Self(MemoryLayout<T>.alignment))
}
}

extension Int {
@inlinable
func murmur64() -> Int {
var h = UInt(bitPattern: self)
h ^= h &>> 47
h &*= 0xc6a4a7935bd1e995
h ^= h &>> 47
return Int(bitPattern: h)
}

@inlinable
func fnv1a() -> Int {
var h: UInt64 = 0xcbf29ce484222325
for shift in stride(from: 0, to: 64, by: 8) {
h ^= UInt64(truncatingIfNeeded: (self &>> shift) & 0xff)
h &*= 0x100000001b3
}
return Int(Int64(bitPattern: h))
}
}
82 changes: 82 additions & 0 deletions Sources/SwissTable/SwissTable+Protocols.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
extension SwissTable: Sequence {
public typealias Element = (key: Key, value: Value)

public struct Iterator: IteratorProtocol {

@inlinable
internal init(table: SwissTable<Key, Value>) {
self.table = table
self.iterator = table.control.enumerated().makeIterator()
}

@usableFromInline
internal let table: SwissTable

@usableFromInline
internal var iterator: EnumeratedSequence<UnsafeMutableBufferPointer<Int8>>.Iterator

@inlinable
public mutating func next() -> (key: Key, value: Value)? {

// TODO: Manually vectorize this loop
while let (index, control) = iterator.next() {
if control != -1 {
let entry = table.entries[index]
return (key: entry.key, value: entry.value)
}
}
return nil
}

}

@inlinable
public func makeIterator() -> Iterator {
Iterator(table: self)
}
}



extension SwissTable: Equatable where Value: Equatable {
public static func == (lhs: SwissTable<Key, Value>, rhs: SwissTable<Key, Value>) -> Bool {
// TODO: Implement
false
}
}

extension SwissTable: Hashable where Value: Hashable {

// same as Swift's stdlib
public func hash(into hasher: inout Hasher) {
var result = 0
for (k, v) in self {
var elementHasher = hasher
elementHasher.combine(k)
elementHasher.combine(v)
result ^= elementHasher.finalize()
}

hasher.combine(result)
}
}



extension SwissTable: ExpressibleByDictionaryLiteral {
public init(dictionaryLiteral elements: (Key, Value)...) {
self.init(minimumCapacity: elements.count &* 2)
for (key, value) in elements {
self[key] = value
}
}
}

extension SwissTable {
public init<S>(uniqueKeysWithValues keysAndValues: S) where S : Sequence, S.Element == (Key, Value) {
self.init(minimumCapacity: keysAndValues.underestimatedCount &* 2)
for (key, value) in keysAndValues {
self[key] = value
}
}
}
150 changes: 150 additions & 0 deletions Sources/SwissTable/SwissTable+Storage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import struct SwiftShims.HeapObject

extension SwissTable {

/// A reference-counted buffer for storing control bytes and table entries in a singlular heap allocation
@usableFromInline
@_fixed_layout
@_objc_non_lazy_realization
final class Storage: Swift.ManagedBuffer<Storage.Header, Int8> {

@inlinable
@inline(__always)
var size: Int {
header.size
}

@inlinable
deinit {
controlBytes.baseAddress!.deinitialize(count: size)
entries.baseAddress!.deinitialize(count: size)
}
}

@inlinable
static func allocateUninitializedStorage(size: Int) -> Storage {
var bufferSize = size.alignedUp(to: Storage.Entry.self)
bufferSize += MemoryLayout<Storage.Entry>.stride &* size

return Storage.create(minimumCapacity: bufferSize) { buffer in
Storage.Header(size: size)
} as! Storage
}

@inlinable
static func allocateStorage(size: Int) -> Storage {
let buffer = allocateUninitializedStorage(size: size)
buffer.rawBody.initializeMemory(as: Int8.self, repeating: -1)
return buffer
}

@inlinable
static func copyStorage(source: Storage, newSize size: Int) -> Storage {
assert(size >= source.size)
let buffer = allocateUninitializedStorage(size: size)

let sourcePointer = source.rawBody.baseAddress!.assumingMemoryBound(to: Int8.self)
let destPointer = buffer.rawBody.baseAddress!

destPointer.moveInitializeMemory(
as: Int8.self,
from: sourcePointer,
count: source.bodyByteSize
)

let growth = size - source.size
let uninitialized = destPointer.assumingMemoryBound(to: Int8.self) + source.bodyByteSize
uninitialized.initialize(repeating: -1, count: growth)

return buffer
}

}


extension SwissTable.Storage {

@frozen
@usableFromInline
struct Header {

@usableFromInline
let size: Int

@inlinable
init(size: Int) {
self.size = size
}
}

@frozen
@usableFromInline
struct Entry {

@usableFromInline let hash: Int
@usableFromInline let key: Key
@usableFromInline let value: Value

@inlinable
init(hash: Int, key: Key, value: Value) {
self.hash = hash
self.key = key
self.value = value
}
}
}

extension SwissTable.Storage {

// Using a manually allocated buffer would be cleaner,
// but a ManagedBuffer is used for copy-on-write.
// https://www.cocoawithlove.com/blog/2016/09/22/deque.html

@inlinable
@inline(__always)
var bodyByteSize: Int {
size.alignedUp(to: MemoryLayout<Entry>.alignment)
+ MemoryLayout<Entry>.stride &* size
}

@inlinable
@inline(__always)
var rawBody: UnsafeMutableRawBufferPointer {
self.withUnsafeMutablePointerToElements { (elements: UnsafeMutablePointer<Int8>) -> UnsafeMutableRawBufferPointer in
UnsafeMutableRawBufferPointer(start: elements, count: bodyByteSize)
}
}

@inlinable
@inline(__always)
var controlBytes: UnsafeMutableBufferPointer<Int8> {
var value = unsafeBitCast(self, to: Int.self)
value &+= MemoryLayout<HeapObject>.size

value = value.alignedUp(to: Header.self)
value &+= MemoryLayout<Header>.stride

let base = UnsafeMutablePointer<Int8>(bitPattern: value)
return .init(start: base, count: self.size)
}

@inlinable
@inline(__always)
var entries: UnsafeMutableBufferPointer<Entry> {
var value = unsafeBitCast(self, to: Int.self)
value &+= MemoryLayout<HeapObject>.size

value = value.alignedUp(to: Header.self)
value &+= MemoryLayout<Header>.stride

// size of control bytes
value &+= self.size

// align up to Entry alignment
value = value.alignedUp(to: Entry.self)

let base = UnsafeMutablePointer<Entry>(bitPattern: value)
return .init(start: base, count: self.size)
}

}
Loading

0 comments on commit 4b64e07

Please sign in to comment.