Swift字典即使进行了优化也很慢:执行不必要的保留/释放吗? [英] Swift Dictionary slow even with optimizations: doing uncessary retain/release?

查看:136
本文介绍了Swift字典即使进行了优化也很慢:执行不必要的保留/释放吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

The following code, which maps simple value holders to booleans, runs over 20x faster in Java than Swift 2 - XCode 7 beta3, "Fastest, Aggressive Optimizations [-Ofast]", and "Fast, Whole Module Optimizations" turned on. I can get over 280M lookups/sec in Java but only about 10M in Swift.

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

When I look at it in Instruments I see that most of the time is going into a pair of retain/release calls associated with the map lookup. Any suggestions on why this is happening or a workaround would be appreciated.

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

The structure of the code is a simplified version of my real code, which has a more complex key class and also stores other types (though Boolean is an actual case for me). Also, note that I am using a single mutable key instance for the retrieval to avoid allocating objects inside the loop and according to my tests this is faster in Swift than an immutable key.

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

I have also tried switching to NSMutableDictionary but when used with Swift objects as keys it seems to be terribly slow.

我已经尝试在objc中实现测试(它不会具有可选的展开开销),并且它比Java更快,但仍然比Java慢了一个数量级...我将以该示例为例另一个问题,看看是否有人有想法.

I have tried implementing the test in objc (which wouldn't have the Optional unwrapping overhead) and it is faster but still over an order of magnitude slower than Java... I'm going to pose that example as another question to see if anyone has ideas.

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

EDIT3 - Answer. I have posted my conclusions and my workaround in an answer below.

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代码:

and here is the corresponding Java code:

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 );
    }

推荐答案

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

After much experimentation I have come to some conclusions and found a workaround (albeit somewhat extreme).

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

First let me say that I recognize that this kind of very fine grained data structure access within a tight loop is not representative of general performance, but it does affect my application and I'm imagining others like games and heavily numeric applications. Also let me say that I know that Swift is a moving target and I'm sure it will improve - perhaps my workaround (hacks) below will not be necessary by the time you read this. But if you are trying to do something like this today and you are looking at Instruments and seeing the majority of your application time spent in retain/release and you don't want to rewrite your entire app in objc please read on.

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

What I have found is that almost anything that one does in Swift that touches an object reference incurs an ARC retain/release penalty. Additionally Optional values - even optional primitives - also incur this cost. This pretty much rules out using Dictionary or NSDictionary.

以下是一些可以快速解决的方法,您可以将其包括在解决方法中:

Here are some things that are fast that you can include in a workaround:

a)基本类型的数组.

a) Arrays of primitive types.

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

b) Arrays of final objects as long as long as the array is on the stack and not on the heap. e.g. Declare an array within the method body (but outside of your loop of course) and iteratively copy the values to it. Do not Array(array) copy it.

将其放在一起,您可以根据存储例如先将数据存储为整数,然后将数组索引存储到该数据结构中的对象.在循环中,您可以通过快速本地数组中的对象索引查找对象.在您问数据结构不能为我存储数组"之前,否,因为这将招致我上面提到的两项惩罚:(

Putting this together you can construct a data structure based on arrays that stores e.g. Ints and then store array indexes to your objects in that data structure. Within your loop you can look up the objects by their index in the fast local array. Before you ask "couldn't the data structure store the array for me" - no, because that would incur two of the penalties I mentioned above :(

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

All things considered this workaround is not too bad - If you can enumerate the entities that you want to store in the Dictionary / data structure you should be able to host them in an array as described. Using the technique above I was able to exceed the Java performance by a factor of 2x in Swift in my case.

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

If anyone is still reading and interested at this point I will consider updating my example code and posting.

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

I'd add an option: c) It is also possible to use UnsafeMutablePointer<> or Unmanaged<> in Swift to create a reference that will not be retained when passed around. I was not aware of this when I started and I would hesitate to recommend it in general because it's a hack, but I've used it in a few cases to wrap a heavily used array that was incurring a retain/release every time it was referenced.

这篇关于Swift字典即使进行了优化也很慢:执行不必要的保留/释放吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆