即使使用-Ofast,Swift的字典也很慢

时间:2014-06-29 14:23:12

标签: performance swift

我在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这个性能水平吗?

4 个答案:

答案 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)