diff --git a/Benchmarks/Benchmarks/CppBenchmarks.swift b/Benchmarks/Benchmarks/CppBenchmarks.swift index 19b51d8fe..219283f06 100644 --- a/Benchmarks/Benchmarks/CppBenchmarks.swift +++ b/Benchmarks/Benchmarks/CppBenchmarks.swift @@ -96,6 +96,49 @@ internal class CppUnorderedMap { } } +internal class CppPriorityQueue { + var ptr: UnsafeMutableRawPointer? + + init(_ input: [Int]) { + self.ptr = input.withUnsafeBufferPointer { buffer in + cpp_priority_queue_create(buffer.baseAddress, buffer.count) + } + } + + convenience init() { + self.init([]) + } + + deinit { + destroy() + } + + func destroy() { + if let ptr = ptr { + cpp_priority_queue_destroy(ptr) + } + ptr = nil + } + + func push(_ value: Int) { + cpp_priority_queue_push(ptr, value) + } + + func push(_ values: [Int]) { + values.withUnsafeBufferPointer { buffer in + cpp_priority_queue_push_loop(ptr, buffer.baseAddress, buffer.count) + } + } + + func pop() -> Int { + cpp_priority_queue_pop(ptr) + } + + func popAll() { + cpp_priority_queue_pop_all(ptr) + } +} + extension Benchmark { public mutating func addCppBenchmarks() { @@ -625,5 +668,43 @@ extension Benchmark { map.destroy() } } + + //-------------------------------------------------------------------------- + + self.addSimple( + title: "std::priority_queue construct from buffer", + input: [Int].self + ) { input in + let pq = CppPriorityQueue(input) + blackHole(pq) + } + + self.add( + title: "std::priority_queue push", + input: [Int].self + ) { input in + return { timer in + let pq = CppPriorityQueue() + timer.measure { + pq.push(input) + } + blackHole(pq) + pq.destroy() + } + } + + self.add( + title: "std::priority_queue pop", + input: [Int].self + ) { input in + return { timer in + let pq = CppPriorityQueue(input) + timer.measure { + pq.popAll() + } + blackHole(pq) + pq.destroy() + } + } } } diff --git a/Benchmarks/Benchmarks/FoundationBenchmarks.swift b/Benchmarks/Benchmarks/FoundationBenchmarks.swift new file mode 100644 index 000000000..0f950c659 --- /dev/null +++ b/Benchmarks/Benchmarks/FoundationBenchmarks.swift @@ -0,0 +1,103 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import CollectionsBenchmark + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +import Foundation + +extension CFBinaryHeap { + internal static func _create(capacity: Int) -> CFBinaryHeap { + var callbacks = CFBinaryHeapCallBacks( + version: 0, + retain: nil, + release: nil, + copyDescription: { value in + let result = "\(Int(bitPattern: value))" as NSString + return Unmanaged.passRetained(result) + }, + compare: { left, right, context in + let left = Int(bitPattern: left) + let right = Int(bitPattern: right) + if left == right { return .compareEqualTo } + if left < right { return .compareLessThan } + return .compareGreaterThan + }) + return CFBinaryHeapCreate(kCFAllocatorDefault, capacity, &callbacks, nil) + } +} +#endif + +extension Benchmark { + public mutating func addFoundationBenchmarks() { + self.add( + title: "CFBinaryHeapAddValue", + input: [Int].self + ) { input in +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + return { timer in + let heap = CFBinaryHeap._create(capacity: 0) + timer.measure { + for value in input { + CFBinaryHeapAddValue(heap, UnsafeRawPointer(bitPattern: value)) + } + } + blackHole(heap) + } +#else + // CFBinaryHeap isn't available + return nil +#endif + } + + self.add( + title: "CFBinaryHeapAddValue, reserving capacity", + input: [Int].self + ) { input in +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + return { timer in + let heap = CFBinaryHeap._create(capacity: input.count) + timer.measure { + for value in input { + CFBinaryHeapAddValue(heap, UnsafeRawPointer(bitPattern: value)) + } + } + blackHole(heap) + } +#else + return nil +#endif + } + + self.add( + title: "CFBinaryHeapRemoveMinimumValue", + input: [Int].self + ) { input in +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + return { timer in + let heap = CFBinaryHeap._create(capacity: input.count) + for value in input { + CFBinaryHeapAddValue(heap, UnsafeRawPointer(bitPattern: value)) + } + timer.measure { + for _ in 0 ..< input.count { + blackHole(CFBinaryHeapGetMinimum(heap)) + CFBinaryHeapRemoveMinimumValue(heap) + } + } + blackHole(heap) + } +#else + return nil +#endif + } + } +} diff --git a/Benchmarks/Benchmarks/PriorityQueueBenchmarks.swift b/Benchmarks/Benchmarks/HeapBenchmarks.swift similarity index 72% rename from Benchmarks/Benchmarks/PriorityQueueBenchmarks.swift rename to Benchmarks/Benchmarks/HeapBenchmarks.swift index 750f7173c..e0489ebdf 100644 --- a/Benchmarks/Benchmarks/PriorityQueueBenchmarks.swift +++ b/Benchmarks/Benchmarks/HeapBenchmarks.swift @@ -22,6 +22,13 @@ extension Benchmark { blackHole(Heap(0.. init from buffer", + input: [Int].self + ) { input in + blackHole(Heap(input)) + } + self.addSimple( title: "Heap insert", input: [Int].self @@ -79,44 +86,3 @@ extension Benchmark { } } } - -// MARK: - - -extension Benchmark { - public mutating func addCFBinaryHeapBenchmarks() { - #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) - self.addSimple( - title: "CFBinaryHeap insert", - input: [Int].self - ) { input in - let heap = BinaryHeap() - for i in input { - heap.insert(i) - } - precondition(heap.count == input.count) - blackHole(heap) - } - - self.add( - title: "CFBinaryHeap removeMinimumValue", - input: [Int].self - ) { input in - return { timer in - let heap = BinaryHeap() - for i in input { - heap.insert(i) - } - - timer.measure { - while heap.count > 0 { - let min = heap.popMinimum() - blackHole(min) - } - } - precondition(heap.count == 0) - blackHole(heap) - } - } - #endif - } -} diff --git a/Benchmarks/Benchmarks/Library.json b/Benchmarks/Benchmarks/Library.json index bd4f9c8d4..5064107dd 100644 --- a/Benchmarks/Benchmarks/Library.json +++ b/Benchmarks/Benchmarks/Library.json @@ -761,47 +761,48 @@ ] }, ] - } - ] - }, - { - "kind": "group", - "title": "PriorityQueue", - "contents": [ - { - "kind": "chart", - "title": "operations", - "tasks": [ - "Heap init from range", - "Heap insert", - "Heap insert(contentsOf:)", - "Heap popMax", - "Heap popMin" - ] }, { - "kind": "chart", - "title": "initializers", - "tasks": [ - "Heap init from range" - ] - }, - { - "kind": "chart", - "title": "insert", - "tasks": [ - "Heap insert", - "Heap insert(contentsOf:)" + "kind": "group", + "title": "Heap", + "contents": [ + { + "kind": "chart", + "title": "operations", + "tasks": [ + "Heap init from range", + "Heap insert", + "Heap insert(contentsOf:)", + "Heap popMax", + "Heap popMin" + ] + }, + { + "kind": "chart", + "title": "initializers", + "tasks": [ + "Heap init from range", + "Heap init from buffer" + ] + }, + { + "kind": "chart", + "title": "insert", + "tasks": [ + "Heap insert", + "Heap insert(contentsOf:)" + ] + }, + { + "kind": "chart", + "title": "remove", + "tasks": [ + "Heap popMax", + "Heap popMin" + ] + } ] }, - { - "kind": "chart", - "title": "remove", - "tasks": [ - "Heap popMax", - "Heap popMin" - ] - } ] }, { @@ -2035,6 +2036,40 @@ }, ] }, + { + "kind": "group", + "title": "Heap vs std::priority_queue", + "directory": "Heap + priority_queue", + "contents": [ + { + "kind": "chart", + "title": "initializers", + "tasks": [ + "std::priority_queue construct from buffer", + "Heap init from buffer", + ] + }, + { + "kind": "chart", + "title": "insert", + "tasks": [ + "std::priority_queue push", + "CFBinaryHeapAddValue", + "Heap insert" + ] + }, + { + "kind": "chart", + "title": "pop", + "tasks": [ + "std::priority_queue pop", + "CFBinaryHeapRemoveMinimumValue", + "Heap popMin", + "Heap popMax" + ] + }, + ] + } ] }, ] diff --git a/Benchmarks/CppBenchmarks/include/BinaryHeap.h b/Benchmarks/CppBenchmarks/include/BinaryHeap.h deleted file mode 100644 index cb586abf1..000000000 --- a/Benchmarks/CppBenchmarks/include/BinaryHeap.h +++ /dev/null @@ -1,29 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2021 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// -//===----------------------------------------------------------------------===// - -#ifndef BinaryHeap_h -#define BinaryHeap_h - -#if __APPLE__ // CFBinaryHeap only exists on Apple platforms - -@import Foundation; - -@interface BinaryHeap: NSObject - -@property (nonatomic, readonly) NSUInteger count; - -- (void)insert:(NSInteger)value; -- (NSInteger)popMinimum; - -@end - -#endif // __APPLE__ -#endif /* BinaryHeap_h */ diff --git a/Benchmarks/CppBenchmarks/include/PriorityQueueBenchmarks.h b/Benchmarks/CppBenchmarks/include/PriorityQueueBenchmarks.h new file mode 100644 index 000000000..2550bb081 --- /dev/null +++ b/Benchmarks/CppBenchmarks/include/PriorityQueueBenchmarks.h @@ -0,0 +1,47 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#ifndef CPPBENCHMARKS_PRIORITY_QUEUE_BENCHMARKS_H +#define CPPBENCHMARKS_PRIORITY_QUEUE_BENCHMARKS_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/// Create a `std::priority_queue`, populating it with data from the +/// supplied buffer. Returns an opaque pointer to the created instance. +extern void *cpp_priority_queue_create(const intptr_t *start, size_t count); + +/// Destroys a priority queue previously returned by `cpp_priority_queue_create`. +extern void cpp_priority_queue_destroy(void *ptr); + +/// Push a value to a priority queue. +extern void cpp_priority_queue_push(void *ptr, intptr_t value); + +/// Loop through the supplied buffer, pushing each value to the queue. +extern void cpp_priority_queue_push_loop(void *ptr, const intptr_t *start, size_t count); + +/// Remove and return the top value off of a priority queue. +extern intptr_t cpp_priority_queue_pop(void *ptr); + +/// Remove and discard all values in a priority queue one by one in a loop. +extern void cpp_priority_queue_pop_all(void *ptr); + +#ifdef __cplusplus +} +#endif + + +#endif /* CPPBENCHMARKS_PRIORITY_QUEUE_BENCHMARKS_H */ diff --git a/Benchmarks/CppBenchmarks/src/utils.h b/Benchmarks/CppBenchmarks/include/Utils.h similarity index 95% rename from Benchmarks/CppBenchmarks/src/utils.h rename to Benchmarks/CppBenchmarks/include/Utils.h index 9109a0936..56ee85aa9 100644 --- a/Benchmarks/CppBenchmarks/src/utils.h +++ b/Benchmarks/CppBenchmarks/include/Utils.h @@ -14,6 +14,8 @@ #include +#ifdef __cplusplus + // FIXME: Is putting this in a separate compilation unit enough to make // sure the function call is always emitted? @@ -35,4 +37,6 @@ static inline const T *identity(const T *value) { return static_cast(_identity(value)); } + +#endif /* __cplusplus */ #endif /* BLACK_HOLE_H */ diff --git a/Benchmarks/CppBenchmarks/include/module.modulemap b/Benchmarks/CppBenchmarks/include/module.modulemap index 7f45ce88b..7fbcf9332 100644 --- a/Benchmarks/CppBenchmarks/include/module.modulemap +++ b/Benchmarks/CppBenchmarks/include/module.modulemap @@ -1,9 +1,10 @@ module CppBenchmarks { + header "Utils.h" header "Hashing.h" header "VectorBenchmarks.h" header "DequeBenchmarks.h" header "UnorderedSetBenchmarks.h" header "UnorderedMapBenchmarks.h" - header "BinaryHeap.h" + header "PriorityQueueBenchmarks.h" export * } diff --git a/Benchmarks/CppBenchmarks/src/BinaryHeap.m b/Benchmarks/CppBenchmarks/src/BinaryHeap.m deleted file mode 100644 index e27cf84c2..000000000 --- a/Benchmarks/CppBenchmarks/src/BinaryHeap.m +++ /dev/null @@ -1,76 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2021 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// -//===----------------------------------------------------------------------===// - -#if __APPLE__ // CFBinaryHeap only exists on Apple platforms - -#import - -#import "BinaryHeap.h" - -@implementation BinaryHeap -{ - CFBinaryHeapRef _storage; -} - -static CFComparisonResult HeapCompare(const void *lhs, const void *rhs, void *context) -{ - if ((NSInteger)lhs == (NSInteger)rhs) { - return kCFCompareEqualTo; - } else if ((NSInteger)lhs < (NSInteger)rhs) { - return kCFCompareLessThan; - } else { - return kCFCompareGreaterThan; - } -} - -- (instancetype)init -{ - if ((self = [super init])) - { - CFBinaryHeapCallBacks callbacks = (CFBinaryHeapCallBacks){ - .version = 0, - .retain = NULL, - .release = NULL, - .copyDescription = &CFCopyDescription, - .compare = &HeapCompare - }; - _storage = CFBinaryHeapCreate(kCFAllocatorDefault, 0, &callbacks, NULL); - } - - return self; -} - -- (void)dealloc -{ - CFRelease(_storage); -} - -- (NSUInteger)count -{ - return CFBinaryHeapGetCount(_storage); -} - -- (void)insert:(NSInteger)value -{ - CFBinaryHeapAddValue(_storage, (const void *)value); -} - -- (NSInteger)popMinimum -{ - const NSInteger val = (NSInteger)CFBinaryHeapGetMinimum(_storage); - CFBinaryHeapRemoveMinimumValue(_storage); - - return val; -} - -@end - -#endif // __APPLE__ diff --git a/Benchmarks/CppBenchmarks/src/DequeBenchmarks.cpp b/Benchmarks/CppBenchmarks/src/DequeBenchmarks.cpp index dc76accae..a239c2692 100644 --- a/Benchmarks/CppBenchmarks/src/DequeBenchmarks.cpp +++ b/Benchmarks/CppBenchmarks/src/DequeBenchmarks.cpp @@ -9,10 +9,10 @@ // //===----------------------------------------------------------------------===// -#include "DequeBenchmarks.h" #include #include -#include "utils.h" +#include "DequeBenchmarks.h" +#include "Utils.h" void * cpp_deque_create(const intptr_t *start, size_t count) diff --git a/Benchmarks/CppBenchmarks/src/Hashing.cpp b/Benchmarks/CppBenchmarks/src/Hashing.cpp index cdbac9d6b..bb3049876 100644 --- a/Benchmarks/CppBenchmarks/src/Hashing.cpp +++ b/Benchmarks/CppBenchmarks/src/Hashing.cpp @@ -11,7 +11,7 @@ #import "Hashing.h" #import "CustomHash.h" -#import "utils.h" +#import "Utils.h" cpp_hash_fn custom_hash_fn; diff --git a/Benchmarks/CppBenchmarks/src/PriorityQueueBenchmarks.cpp b/Benchmarks/CppBenchmarks/src/PriorityQueueBenchmarks.cpp new file mode 100644 index 000000000..1dee8728b --- /dev/null +++ b/Benchmarks/CppBenchmarks/src/PriorityQueueBenchmarks.cpp @@ -0,0 +1,63 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#include +#include "PriorityQueueBenchmarks.h" +#include "Utils.h" + +typedef std::priority_queue pqueue; + +void * +cpp_priority_queue_create(const intptr_t *start, size_t count) +{ + auto pq = new pqueue(start, start + count); + return pq; +} + +void +cpp_priority_queue_destroy(void *ptr) +{ + delete static_cast(ptr); +} + +void +cpp_priority_queue_push(void *ptr, intptr_t value) +{ + auto pq = static_cast(ptr); + pq->push(value); +} + +void +cpp_priority_queue_push_loop(void *ptr, const intptr_t *start, size_t count) +{ + auto pq = static_cast(ptr); + for (auto p = start; p < start + count; ++p) { + pq->push(*p); + } +} + +intptr_t cpp_priority_queue_pop(void *ptr) +{ + auto pq = static_cast(ptr); + auto result = pq->top(); + pq->pop(); + return result; +} + +void +cpp_priority_queue_pop_all(void *ptr) +{ + auto pq = static_cast(ptr); + while (!pq->empty()) { + black_hole(pq->top()); + pq->pop(); + } +} diff --git a/Benchmarks/CppBenchmarks/src/UnorderedMapBenchmarks.cpp b/Benchmarks/CppBenchmarks/src/UnorderedMapBenchmarks.cpp index 17f4f95a8..3d5bf0b83 100644 --- a/Benchmarks/CppBenchmarks/src/UnorderedMapBenchmarks.cpp +++ b/Benchmarks/CppBenchmarks/src/UnorderedMapBenchmarks.cpp @@ -9,12 +9,12 @@ // //===----------------------------------------------------------------------===// -#include "UnorderedMapBenchmarks.h" #include #include #include +#include "UnorderedMapBenchmarks.h" #include "CustomHash.h" -#include "utils.h" +#include "Utils.h" typedef std::unordered_map custom_map; diff --git a/Benchmarks/CppBenchmarks/src/UnorderedSetBenchmarks.cpp b/Benchmarks/CppBenchmarks/src/UnorderedSetBenchmarks.cpp index 625523c10..1a89efc1c 100644 --- a/Benchmarks/CppBenchmarks/src/UnorderedSetBenchmarks.cpp +++ b/Benchmarks/CppBenchmarks/src/UnorderedSetBenchmarks.cpp @@ -9,13 +9,13 @@ // //===----------------------------------------------------------------------===// -#include "UnorderedSetBenchmarks.h" #include #include #include #include +#include "UnorderedSetBenchmarks.h" #include "CustomHash.h" -#include "utils.h" +#include "Utils.h" typedef std::unordered_set custom_set; diff --git a/Benchmarks/CppBenchmarks/src/utils.cpp b/Benchmarks/CppBenchmarks/src/Utils.cpp similarity index 97% rename from Benchmarks/CppBenchmarks/src/utils.cpp rename to Benchmarks/CppBenchmarks/src/Utils.cpp index 1e2c542c2..2399339fe 100644 --- a/Benchmarks/CppBenchmarks/src/utils.cpp +++ b/Benchmarks/CppBenchmarks/src/Utils.cpp @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -#include "utils.h" +#include "Utils.h" void black_hole(intptr_t value) diff --git a/Benchmarks/CppBenchmarks/src/VectorBenchmarks.cpp b/Benchmarks/CppBenchmarks/src/VectorBenchmarks.cpp index cd93965b6..b7e1f6bb1 100644 --- a/Benchmarks/CppBenchmarks/src/VectorBenchmarks.cpp +++ b/Benchmarks/CppBenchmarks/src/VectorBenchmarks.cpp @@ -9,10 +9,10 @@ // //===----------------------------------------------------------------------===// -#include "VectorBenchmarks.h" #include #include -#include "utils.h" +#include "VectorBenchmarks.h" +#include "Utils.h" void * cpp_vector_create(const intptr_t *start, size_t count) diff --git a/Benchmarks/Libraries/Heap.json b/Benchmarks/Libraries/Heap.json new file mode 100644 index 000000000..3e4e2e049 --- /dev/null +++ b/Benchmarks/Libraries/Heap.json @@ -0,0 +1,83 @@ +{ + "kind": "group", + "title": "Heap Benchmarks", + "directory": "Results", + "contents": [ + { + "kind": "group", + "title": "Heap Operations", + "contents": [ + { + "kind": "chart", + "title": "operations", + "tasks": [ + "Heap init from range", + "Heap insert", + "Heap insert(contentsOf:)", + "Heap popMax", + "Heap popMin" + ] + }, + { + "kind": "chart", + "title": "initializers", + "tasks": [ + "Heap init from range", + "Heap init from buffer" + ] + }, + { + "kind": "chart", + "title": "insert", + "tasks": [ + "Heap insert", + "Heap insert(contentsOf:)" + ] + }, + { + "kind": "chart", + "title": "remove", + "tasks": [ + "Heap popMax", + "Heap popMin" + ] + } + ] + }, + { + "kind": "group", + "title": "Heap vs std::priority_queue", + "directory": "Heap + priority_queue", + "contents": [ + { + "kind": "chart", + "title": "initializers", + "tasks": [ + "std::priority_queue construct from buffer", + "Heap init from buffer", + ] + }, + { + "kind": "chart", + "title": "insert", + "tasks": [ + "std::priority_queue push", + "CFBinaryHeapAddValue", + "CFBinaryHeapAddValue, reserving capacity", + "Heap insert" + ] + }, + { + "kind": "chart", + "title": "pop", + "tasks": [ + "std::priority_queue pop", + "CFBinaryHeapRemoveMinimumValue", + "Heap popMin", + "Heap popMax" + ] + }, + ] + } + ] +} diff --git a/Benchmarks/swift-collections-benchmark/main.swift b/Benchmarks/swift-collections-benchmark/main.swift index 0227f77d2..d2096d384 100644 --- a/Benchmarks/swift-collections-benchmark/main.swift +++ b/Benchmarks/swift-collections-benchmark/main.swift @@ -20,8 +20,10 @@ benchmark.addDequeBenchmarks() benchmark.addOrderedSetBenchmarks() benchmark.addOrderedDictionaryBenchmarks() benchmark.addHeapBenchmarks() -benchmark.addCFBinaryHeapBenchmarks() benchmark.addCppBenchmarks() +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +benchmark.addFoundationBenchmarks() +#endif benchmark.chartLibrary = try benchmark.loadReferenceLibrary() diff --git a/Package.swift b/Package.swift index 538f2d8a8..ace16942c 100644 --- a/Package.swift +++ b/Package.swift @@ -93,8 +93,8 @@ let package = Package( name: "Benchmarks", dependencies: [ .product(name: "CollectionsBenchmark", package: "swift-collections-benchmark"), - "CppBenchmarks", "Collections", + "CppBenchmarks", ], path: "Benchmarks/Benchmarks", resources: [