我在Swift中使用Dictionary
实现了本质上的缓存。表现远远低于我的预期。我已经阅读了其他一些问题,例如this one about array sorting似乎表明-Ofast
就是答案(如果您已准备好接受它带来的更改)。但是,即使编译-Ofast
,性能也会与其他语言相比较差。我使用的是Swift 1.0版(swift-600.0.34.4.8)。
以下是一个简洁的例子,说明了问题:
import Foundation
class Holder {
var dictionary = Dictionary<Int, Int>()
func store(#key: Int, value: Int) {
dictionary[key] = value
}
}
let holder = Holder()
let items = 5000
for (var i: Int = 0; i < 5000; i++) {
holder.store(key: i, value: i)
}
使用-O3
编译,运行时间超过两秒:
xcrun swift -sdk $(xcrun --show-sdk-path --sdk macosx) -O3 Test.swift && time ./Test
real 0m2.295s
user 0m2.176s
sys 0m0.117s
使用-Ofast
进行编译可以提高3-4倍:
xcrun swift -sdk $(xcrun --show-sdk-path --sdk macosx) -Ofast Test.swift && time ./Test
real 0m0.602s
user 0m0.484s
sys 0m0.117s
相比之下,这个Java实现:
import java.util.Map;
import java.util.HashMap;
public class Test {
public static void main(String[] args) {
Holder holder = new Holder();
int items = 5000;
for (int i = 0; i < items; i++) {
holder.store(i, i);
}
}
}
class Holder {
private final Map<Integer, Integer> map = new HashMap<Integer, Integer>();
public void store(Integer key, Integer value) {
map.put(key, value);
}
}
再次快6倍:
javac Test.java && time java Test
real 0m0.096s
user 0m0.088s
sys 0m0.021s
只是复制Dictionary
的成本是因为它在Holder
实例中发生变异并存储导致Swift如此糟糕?删除Holder
并直接访问Dictionary
会表明它是。
此代码:
import Foundation
var dictionary = Dictionary<Int, Int>()
let items = 5000
for (var i: Int = 0; i < 5000; i++) {
dictionary[i] = i
}
显着更快:
$ xcrun swift -sdk $(xcrun --show-sdk-path --sdk macosx) -O3 NoHolder.swift && time ./NoHolder
real 0m0.011s
user 0m0.009s
sys 0m0.002s
$ xcrun swift -sdk $(xcrun --show-sdk-path --sdk macosx) -Ofast NoHolder.swift && time ./NoHolder
real 0m0.011s
user 0m0.007s
sys 0m0.003s
虽然它提供了(希望)有趣的数据点,但在我的情况下直接访问词典是不可能的。我还能做些什么来接近目前形式的Swift这个性能水平吗?
答案 0 :(得分:3)
TL; DR它是Beta。
我认为现在的答案只是Swift处于测试阶段,工具处于测试阶段,还有很多优化尚未完成。复制Obj-C中的“Holder”类示例表明,即使它在相同的-Ofast
级别上也要快得多。
@import Foundation;
@interface Holder : NSObject
@property NSMutableDictionary *dictionary;
- (void)storeValue:(NSInteger)value forKey:(NSString *)key;
@end
@implementation Holder
- (instancetype)init {
self = [self initWithDict];
return self;
}
- (instancetype)initWithDict {
if (!self) {
self = [super init];
_dictionary = [NSMutableDictionary dictionary];
}
return self;
}
- (void)storeValue:(NSInteger)value forKey:(NSString *)key {
[self.dictionary setValue:@(value) forKey:key];
}
@end
int main(int argc, const char * argv[]) {
Holder *holder = [Holder new];
for (NSInteger i = 0; i < 5000; i++) {
[holder storeValue:i forKey:[NSString stringWithFormat:@"%ld", i]];
}
}
Obj-C快速走出大门。
time ./loop
real 0m0.013s
user 0m0.006s
sys 0m0.003s
您给出的NoHolder示例的时间相似性很好地表明Obj-C编译器正在进行多少优化。
在Swift中查看-O3
和-Ofast
级别的程序集显示,安全检查的数量存在很大差异。观察Obj-C组件表明,执行它的次数要少得多。由于快速制作程序的关键是不需要做太多的事情......
OS-X-Dos-Equis:~ joshwisenbaker$ wc -l objc.txt
159 objc.txt
OS-X-Dos-Equis:~ joshwisenbaker$ wc -l oFast.txt
3749 oFast.txt
(编辑:使用结束Holder课程的结果进行更新。)
另一个有趣的问题是在类定义中使用@final
装饰。如果您知道您的类永远不会被子类化,那么请尝试添加如下关键字:@final class Holder
正如您所看到的,当以相同的方式编译时,它也会使性能正常化。
OS-X-Dos-Equis:~ joshwisenbaker$ swift -sdk $(xcrun --show-sdk-path --sdk macosx) -Ofast bench.swift && time ./bench
real 0m0.013s
user 0m0.007s
sys 0m0.003s
即使只使用-O3
,@final
也可以使用魔法。
OS-X-Dos-Equis:~ joshwisenbaker$ swift -sdk $(xcrun --show-sdk-path --sdk macosx) -O3 bench.swift && time ./bench
real 0m0.015s
user 0m0.009s
sys 0m0.003s
同样,我认为您在性能方面看到的差异可能是编译时的当前优化级别。
答案 1 :(得分:0)
在Xcode 6接近发布并且Apple禁用调试代码并完成优化器之前,为什么不简单地将var声明为NSMutableDictionary?
它经过现场验证并且非常快。
class Holder {
var dictionary = NSMutableDictionary()
func store(#key: Int, value: Int) {
dictionary[key] = value
}
}
如果/如果词典提供相似或更好的效果,您可以稍后更改它。
<强>更新强>
我在testPerformanceHolder()
的单元测试中尝试了上述代码使用-O3进行优化它平均在.013秒内完成 - 比Java示例快约7倍。
答案 2 :(得分:0)
不幸的是,我得到了更糟糕的结果。
我修改了Java代码以避免启动时间,增加循环次数以获得更多可重复的时序,并检查结果以防止JVM优化循环:
import java.util.Map;
import java.util.HashMap;
public class HolderTest {
private static final int items = 1_000_000;
public static void main(String[] args) {
final long start = System.nanoTime();
final Holder holder = new Holder();
for (int i = 0; i < items; i++) {
holder.store(i, i);
}
final long finish = System.nanoTime();
System.out.println("time = " + (finish - start) / 1_000_000.0 + " ms, holder 0 = " + holder.map.get(0) + ", holder " + (items - 1) + " = " + holder.map.get(items - 1));
}
}
class Holder {
final Map<Integer, Integer> map = new HashMap<>();
public void store(Integer key, Integer value) {
map.put(key, value);
}
}
类似于Swift代码:
import Foundation
class Holder {
var dictionary = Dictionary<Int, Int>()
func store(#key: Int, value: Int) {
dictionary[key] = value
}
}
let start = CFAbsoluteTimeGetCurrent()
let holder = Holder()
let items = 1_000_000
for i in 0 ..< items {
holder.store(key: i, value: i)
}
let finish = CFAbsoluteTimeGetCurrent()
println("time = \((finish - start) * 1000.0) ms, holder 0 = \(holder.dictionary[0]), holder \(items - 1) = \(holder.dictionary[items - 1])")
我获得了300毫秒的Java和20秒(!)的Swift :(
这是6.1。
更新1:
更改为NSMutableDictionary提供了更好的性能。
sunzero-ln:HolderTest lov080 $ swift -sdk $(xcrun --show-sdk-path --sdk macosx)-Ounchecked main.swift 时间= 647.060036659241毫秒,持有人0 =可选(0),持有人999999 =可选(999999)
比Java慢2倍,但好多了!
更新2:
似乎即使我要求 - )在Xcode中未经检查我也没有得到它(可能还需要其他一些设置:()。)从命令行使用Swift词典的Swift版本给出:
sunzero-ln:HolderTest lov080 $ swift -sdk $(xcrun --show-sdk-path --sdk macosx)-Ounchecked main.swift 时间= 303.406000137329毫秒,持有人0 =可选(0),持有人999999 =可选(999999)
IE与Java相同 - 欢呼:)
答案 3 :(得分:0)
Swift 4.2的更新:使用@ howard-lovatt的Holder测试类,我获得了130-180ms的速度。
速度提示1: 如果要使用游乐场进行测试,请将所有代码(包括循环测试方法)放在单独的源文件中。
速度提示2:
在这种情况下,通过将字典预分配给适当的大小,可以提高大约1.5-3倍的速度:
var dictionary = Dictionary<Int, Int>(minimumCapacity: 1_000_000)