【问题标题】:Realm delay at end of write function写函数结束时的领域延迟
【发布时间】:2020-12-20 23:16:10
【问题描述】:

我在 Xcode/Swift 中使用 Realm 作为基础构建了一个体育游戏。它正在运行和工作,但随着时间的推移,领域交易变得越来越慢,以至于它比我希望的要慢得多。这在本例中尤为明显。

每周有许多比赛,每个球员都有为每场比赛创建的 GameStat 对象(每周大约创建 2,000 个新的 GameStat)。

class GameStat: Object {         
  @objc dynamic var iden = UUID().uuidString
  @objc dynamic var playerIden = "" // Links to iden of a Player Object
  @objc dynamic var statOne = 0
  @objc dynamic var statTwo = 0
  @objc dynamic var statThree = 0
  etc...

  // Primary Key
  override static func primaryKey() -> String? {
    return "iden"
  }
}

为了防止在每个游戏中多次写入领域,我在领域之外创建 GameStats(存储在 thisGameGameStats 数组中),然后在游戏完成后将这些 GameStats 批量写入领域。正是这批写入 Realm 非常慢。

try! realm.write {
  for gmStat in thisGameGameStats {
    realm.add(gmStat)
  }
}

我测量了所用的时间,我总是看到“for”循环在 0.05 秒后完成。在前几个循环中,realm.write 函数完成没有延迟,但是在几个季节(数据库中的 c70k 记录)之后,realm.write 函数又需要 0.50 秒才能完成。

这是意料之中的,还是有机会在这里优化?

编辑:通过删除primaryKey,写入变得更快,并且似乎不会随着数据库的增长而减慢。因此,我假设在提交时会按 primaryKey 进行某种形式的排序。

【问题讨论】:

  • 确实没有足够的信息或代码来回答。这是存储在单个设备上的单用户游戏吗?另外,我没有看到您使用自动释放池 - 请参阅 Threading。请注意,Realm 在通过在单个事务中批量处理多个突变来写入大量数据时非常有效请参阅示例here
  • 感谢杰伊的回复。是的,这是一个单用户离线游戏。添加到 autoreleasepool 中,它提高了内存使用率(直到现在 for 循环结束才不断上升),但仍然看到“提交”需要 0.5 秒。我对 Realm 比较陌生,但认为我上面的方法被归类为批处理事务,如 forloop in write。有没有更好的批处理方式?

标签: ios swift xcode realm


【解决方案1】:

这并不完全是一个答案,但可能会提供一些验证并说明一个有趣的观点。

TL;DR

随着时间的推移写入对象块不会对写入时间产生重大影响 - 根据此用例中的现有数据量,您应该不会看到写入时间显着增加。

带有设置和测试的长版本:

写入完成后,realm 将生成通知,因此我们使用该通知来“计时”写入。

我们从一个类 var 开始存储每次写入的开始时间和一个通知来跟踪它何时完成

var startTime = Date()
var gameStatNotificationToken: NotificationToken?

然后我们向领域添加一个观察者,该观察者将在写入完成时执行,确定开始时间和现在之间的经过时间

func addObserver {
    if let realm = gGetRealm() { //this just gets a realm
        self.gameStatNotificationToken = realm.observe { notification, realm in
            let elapsed = Date().timeIntervalSince(self.startTime)
            print("\(elapsed)")
        }
    }
}

然后是实际写入数据的代码

func writeLotsOfData() {
    autoreleasepool {

        var  gameStatArray = [GameStat]()

        for index in 0..<10000 {
            let stat = GameStat()
            stat.playerIden = "\(index)"
            gameStatArray.append(stat)
        }

    
        if let realm = gGetRealm() {
            self.startTime = Date()
            try! realm.write {
                realm.add(gameStatArray)
            }
        }
    }
}

然后是一个循环调用上述代码 10 次;每个循环写入 10k,总共 100k 个对象。

func looper() {
    for i in 0..<10 {
        self.writeLotsOfData()
    }
}

所以最终结果显示写作没有随着时间的推移而增加

0.19487500190734863
0.1404169797897339
0.14565002918243408
0.15493690967559814
0.14426898956298828
0.15933406352996826
0.15379595756530762
0.16700804233551025
0.1598280668258667
0.15421009063720703

编辑

这是我正在使用的 GameStat 对象 - 它与问题中的对象相同

class GameStat: Object {
  @objc dynamic var iden = UUID().uuidString
  @objc dynamic var playerIden = "" // Links to iden of a Player Object
  @objc dynamic var statOne = 0
  @objc dynamic var statTwo = 0
  @objc dynamic var statThree = 0

  // Primary Key
  override static func primaryKey() -> String? {
    return "iden"
  }
}

【讨论】:

  • 再次感谢杰。如果可以的话,还有几件事要跟进。当我将计时器移到写循环之外时,那是我看到延迟的时候。即在写入函数中添加的动作很快,但是退出写入循环是需要时间的。添加 1,000 个 GameStats 所需的时间 = 0.1110778(在 realm.write 内测量) Stats Ran - 2.655491947
  • 由于某种原因,我无法编辑上面的评论。最后应该说... 添加 1,000 个统计数据所需的时间 = 0.110778,但在最终添加后退出写入函数所需的时间 = 2.655491947
  • 今天再做一些调查。通过删除primaryKey,写入变得明显更快,并且似乎不会随着数据库的增长而减慢。因此,我假设在提交时会按 primaryKey 进行某种形式的排序。
  • @hyperpringle 我正在考虑这个问题,我的第一个答案对写入做出了一些不正确的假设。我删除了它并使用更准确的经过时间测量重新进行了测试。根据更新测试,您应该不会看到基于数据量的写入时间有任何显着增加——我认为我的测试证实了这一点。看看我的重做答案。此外,领域对象本质上是无序的,除了 List 的情况,因此没有继续进行排序。顺便说一句,超级好问题,所以+1。
  • 再次感谢您花时间考虑这个问题。我说您使用 Int as String 作为 primaryKey 是否正确?如果您仍然有代码,您可以尝试将其设置为 UUID().uuidString 吗?这就是我正在使用的,我在其他地方读到它可能是罪魁祸首(因此我在上一条评论中删除了它,它似乎改善了一些事情)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-01-15
  • 1970-01-01
  • 2014-01-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多