【问题标题】:Swift's Dictionary is slow even with -Ofast即使使用 -Ofast,Swift 的字典也很慢
【发布时间】:2014-08-20 01:06:57
【问题描述】:

我在 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

虽然它提供了一个(希望如此)有趣的数据点,但在我的情况下,直接访问 Dictionary 是不可能的。我还能做些什么来让当前形式的 Swift 性能更接近这一水平?

【问题讨论】:

  • 根据您的用例,您可以使用 Int 数组而不是 dict。
  • 无论你做什么......很有可能当 Swift 发布用于生产用途时,这个问题已经解决了。我建议使任何解决方案都易于可逆,并不时检查。为了回答你的问题,我怀疑不是类慢而是函数调用。如果编译器设法内联函数,那应该可以解决问题......
  • 我的用例在沸腾过程中有些丢失。关键实际上不是一个 Int。当类变量可用时,它很容易成为一个变量,但如果没有它们,这将是一个相当复杂的变化。

标签: performance swift


【解决方案1】:

Swift 4.2 更新:我使用来自 @howard-lovatt 的 Holder 测试类获得 130 - 180ms 的速度。

速度提示 #1: 如果您使用 Playground 进行测试,请将所有代码(包括循环测试方法)放在单独的源文件中。

速度提示 #2: 在这种情况下,通过将字典预分配到适当的大小,可以实现大约 1.5 - 3 倍的速度提升: var dictionary = Dictionary&lt;Int, Int&gt;(minimumCapacity: 1_000_000)

【讨论】:

    【解决方案2】:

    不幸的是,我得到了更糟糕的结果。

    我修改了 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])")
    

    Java 用了 300 毫秒,Swift 用了 20 秒(!):(

    这是在 6.1 上。

    更新 1:

    更改为 NSMutableDictionary 提供了更好的性能。

    sunzero-ln:HolderTest lov080$ swift -sdk $(xcrun --show-sdk-path --sdk macosx) -未检查 main.swift 时间 = 647.060036659241 毫秒,持有人 0 = 可选(0),持有人 999999 = 可选(999999)

    仍然比 Java 慢 2 倍,但要好得多!

    更新 2:

    似乎即使我在 Xcode 中要求 -)unchecked 我没有得到它(可能我还需要一些其他设置:( )。从命令行使用 Swift 字典的 Swift 版本给出:

    sunzero-ln:HolderTest lov080$ swift -sdk $(xcrun --show-sdk-path --sdk macosx) -未检查 main.swift 时间 = 303.406000137329 毫秒,持有人 0 = 可选(0),持有人 999999 = 可选(999999)

    IE 和 Java 一样——万岁 :)

    【讨论】:

      【解决方案3】:

      直到 Xcode 6 接近发布并且 Apple 禁用调试代码并完成优化器,为什么不简单地将您的 var 声明为 NSMutableDictionary?

      它已经过现场验证并且速度非常快。

      class Holder {
          var dictionary = NSMutableDictionary()
          func store(#key: Int, value: Int) {
              dictionary[key] = value
          }
      }
      

      当/如果 Dictionary 提供类似或更好的性能时,您可以稍后将其更改回来。

      更新:

      我在 testPerformanceHolder() 的单元测试中尝试了上述代码

      使用 -O3 优化它平均在 0.013 秒内完成 - 比 Java 示例快约 7 倍。

      【讨论】:

        【解决方案4】:

        TL;DR 这是测试版。

        我认为现在的答案只是 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
        

        再次,我认为您在性能方面看到的差异可能取决于编译时的当前优化级别。

        【讨论】:

        • 谢谢。这更多地解决了“为什么慢?”问题,而不是我问的那个,我可以用当前形式的 Swift 做什么来加快速度。我想我可以将您的回答解释为“您无能为力。它处于测试阶段。您只需要等待”?
        • 我刚刚添加了一点关于在你的类定义中使用@final 关键字作为编译器的提示以及加快速度。使用它时,Swift 的性能与 Obj-C 相同,但你放弃了子类化。确实,尽管我认为答案只是等到我们看到添加了编译器优化以在大多数情况下获得奇偶校验。请记住,使用 -Ofast 可能非常危险,并且破坏了 Swift 构建的许多“安全性”。
        • 我很沮丧,我没想过自己尝试@final。非常感谢您的建议。这对我的实际代码(而不是我在问题中发布的使用 Ints 的示例)产生了相当大的影响:将条目添加到字典的性能提高了 165 倍(使用 mach_absolute_time() 测量)
        • 对@final 的需求对我来说似乎有点莫名其妙。编译器知道它是否在项目中被子类化,那么为什么不自动添加它呢?我假设随着编译器的成熟,这种事情会变得自动化,但我现在要提交一份关于它的雷达,以防万一……
        猜你喜欢
        • 2013-04-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-22
        相关资源
        最近更新 更多