当前位置: 首页 > 面试题库 >

Swift字典即使进行了优化也很慢:执行不必要的保留/释放?

宁浩博
2023-03-14
问题内容

下面的代码将简单的值持有者映射为布尔值,在Java中的运行速度比Swift 2快20倍-XCode 7
beta3,“最快,积极的优化[-Ofast]”和“最快,完整的模块优化”处于打开状态。在Java中,每秒可以进行2.8亿次以上的查询,但是在Swift中,只能达到1000万次。

当我在Instruments中查看它时,我看到大多数时间都在进行与映射查找相关的一对保留/释放调用。关于为什么发生这种情况或解决方法的任何建议将不胜感激。

该代码的结构是我的实际代码的简化版本,它具有更复杂的键类并存储其他类型(尽管布尔值对我来说是实际情况)。另外,请注意,我使用单个可变键实例进行检索,以避免在循环内分配对象,根据我的测试,在Swift中,这比不可变键要快。

编辑:我也尝试过切换到NSMutableDictionary,但是当与Swift对象作为键一起使用时,它似乎非常慢。

EDIT2:我已经尝试在objc中实现测试(它不会具有可选的展开开销),它比Java更快,但仍然比Java慢了一个数量级…我将把这个例子作为另一个问题看看是否有人有想法。

EDIT3-回答。我在下面的答案中发布了我的结论和解决方法。

public final class MyKey : Hashable {
    var xi : Int = 0
    init( _ xi : Int ) { set( xi ) }  
    final func set( xi : Int) { self.xi = xi }
    public final var hashValue: Int { return xi }
}
public func == (lhs: MyKey, rhs: MyKey) -> Bool {
    if ( lhs === rhs ) { return true }
    return lhs.xi==rhs.xi
}

...
var map = Dictionary<MyKey,Bool>()
let range = 2500
for x in 0...range { map[ MyKey(x) ] = true }
let runs = 10
for _ in 0...runs
{
    let time = Time()
    let reps = 10000
    let key = MyKey(0)
    for _ in 0...reps {
        for x in 0...range {
            key.set(x)
            if ( map[ key ] == nil ) { XCTAssertTrue(false) }
        }
    }
    print("rate=\(time.rate( reps*range )) lookups/s")
}

这是相应的Java代码:

public class MyKey  {
    public int xi;
    public MyKey( int xi ) { set( xi ); }
    public void set( int xi) { this.xi = xi; }

    @Override public int hashCode() { return xi; }

    @Override
    public boolean equals( Object o ) {
        if ( o == this ) { return true; }
        MyKey mk = (MyKey)o;
        return mk.xi == this.xi;
    }
}
...
    Map<MyKey,Boolean> map = new HashMap<>();
    int range = 2500;    
    for(int x=0; x<range; x++) { map.put( new MyKey(x), true ); }

    int runs = 10;
    for(int run=0; run<runs; run++)
    {
        Time time = new Time();
        int reps = 10000;
        MyKey buffer = new MyKey( 0 );
        for (int it = 0; it < reps; it++) {
            for (int x = 0; x < range; x++) {
                buffer.set( x );
                if ( map.get( buffer ) == null ) { Assert.assertTrue( false ); }
            }
        }
        float rate = reps*range/time.s();
        System.out.println( "rate = " + rate );
    }

问题答案:

经过大量的实验,我得出了一些结论并找到了一种解决方法(尽管有些极端)。

首先让我说,我认识到这种紧密循环内的非常细粒度的数据结构访问不能代表一般性能,但确实会影响我的应用程序,并且我在想象其他游戏,例如数字应用程序。另外,我想说我知道Swift是一个不断发展的目标,并且我相信它会有所改进-
也许到您读这篇文章时,下面的解决方法(hack)将不再必要。但是,如果您今天尝试做这样的事情,并且正在查看Instruments,并且看到大部分应用程序时间都在保留/释放上,并且您不想在objc中重写整个应用程序,请继续阅读。

我发现,在Swift中所做的 几乎 所有与对象引用相关的操作都会导致ARC保留/释放。此外,可选值-甚至可选基元-
也会产生此费用。这几乎排除了使用Dictionary或NSDictionary的可能性。

您可以在解决方法中包括以下一些快速操作:

a)基本类型数组。

b)最终对象 的数组,只要该数组在堆栈上而不在堆上即可
。例如,在方法主体中声明一个数组(但当然要在循环之外),然后将值迭代复制到该数组中。不要Array(array)复制它。

放在一起,您可以基于存储例如Ints的数组构建数据结构,然后将数组索引存储到该数据结构中的对象。在循环中,您可以通过快速本地数组中的对象索引来查找对象。在您问“数据结构无法为我存储数组”之前,否,因为这将招致我在上面提到的两项惩罚:(

所有被视为该解决方法的方法都还不错-如果您可以枚举要存储在Dictionary
/数据结构中的实体,则应该能够按照所述将它们托管在一个数组中。使用上面的技术,在我的案例中,我能够将Java性能超出Swift的2倍。

如果此时仍然有人在阅读并且有兴趣,我将考虑更新示例代码并发布。

编辑:我要添加一个选项:c)也可以在Swift中使用UnsafeMutablePointer <>或Unmanaged
<>来创建一个引用,该引用在传递时将不会保留。我刚开始时并没有意识到这一点,因此我通常会犹豫地推荐它,因为它是一个hack,但是在某些情况下,我已经使用它来包装一个频繁使用的数组,该数组每次发生保留/释放参考。



 类似资料:
  • 下面的代码将简单的值持有者映射到布尔值,它在Java中的运行速度比Swift2-xCode7Beta3快20倍以上,“最快的、积极的优化[-ofast]”和“快速的、整个模块的优化”已经开启。我在Java中每秒可以得到超过2.8亿次查找,但在Swift中只能得到大约1000万次查找。 当我在Instruments中查看它时,我看到大部分时间都是进入一对与映射查找相关联的retrain/releas

  • 我正在使用Swift中的实现实质上是一个缓存。演出远未达到我的预期。我读过一些其他问题,例如关于数组排序的问题,它似乎暗示是答案(如果您准备接受它带来的更改)。但是,即使在编译时,性能也比其他语言差。我使用的是Swift 1.0版(Swift-600.0.34.4.8)。 下面是一个简单的例子来说明这个问题: 使用编译时,运行时间超过两秒: 相比之下,这个Java实现: 又快了6倍: 难道仅仅是复

  • 我正在尝试在Swift中构建一个数据结构,将一个整数映射到一个对象数组(一个以int为键、数组为值的字典)。这些对象非常小,它们只包装了一个UIColor和一个Int。我有两个实现,一个使用Swift数组作为字典的值类型,而另一个使用NSMutableArray作为值类型。我的Objective-C代码运行速度非常快,但我的Swift代码运行速度非常慢。理想情况下,我不想使用NSMutableAr

  • 问题内容: 我试图弄清楚如何在MySQL中优化一个非常慢的查询(我没有设计这个): 比较一下: 说明语句对我没有帮助: 好的,它仍然认为它需要大约400万个条目才能计数,但是我可以计算文件中的行数比这还要快!我不明白为什么MySQL要花这么长时间。 这是表的定义: 版: 有什么明显的我想念的东西吗?(是的,我已经尝试过“ SELECT COUNT(change_event_id)”,但是没有性能差

  • 我大致了解了TensorFlow图在评估它包含的之一时是如何评估的:该张量的或的执行将触发图中所需的所有级联计算计算该张量的值,因此,图中“导致它”的任何张量也将被计算,并且连接它们的任何操作都将被运行。 因此,如果我有一个包含张量out\u a的图,其计算涉及(可能在许多其他事情中)使用int\u b的操作,这反过来(最终)需要执行本身(最终)使用in的操作 将只评估、和一次:和的计算都使用的相

  • 问题内容: 我正在尝试在Swift中构建一个数据结构,该数据结构将一个Integer映射到一个对象数组(一个以int为键,而array为值的字典)。这些对象非常小,它们只包装了一个UIColor和一个Int。我有两种实现,一种使用Swift数组作为Dictionary的值类型,而另一种使用NSMutableArray作为值类型。我的Objective- C代码执行得非常快,但是我的Swift代码却