0

Realm 允许您按排序顺序接收查询结果。

let realm = try! Realm()
let dogs = realm.objects(Dog.self)
let dogsSorted = dogs.sorted(byKeyPath: "name", ascending: false)

我运行了这个测试来看看领域返回排序数据的速度有多快

import Foundation
import RealmSwift

class TestModel: Object {
    @Persisted(indexed: true) var value: Int = 0
}

class RealmSortTest {
    let documentCount = 1000000
    var smallestValue: TestModel = TestModel()
    
    func writeData() {
        let realm = try! Realm()
        var documents: [TestModel] = []
        for _ in 0 ... documentCount {
            let newDoc = TestModel()
            newDoc.value = Int.random(in: 0 ... Int.max)
            documents.append(newDoc)
        }
        try! realm.write {
            realm.deleteAll()
            realm.add(documents)
        }
    }
    
    func readData() {
        let realm = try! Realm()
        let sortedResults = realm.objects(TestModel.self).sorted(byKeyPath: "value")
                
        let start = Date()
        
        self.smallestValue = sortedResults[0]
        
        let end = Date()
        let delta = end.timeIntervalSinceReferenceDate - start.timeIntervalSinceReferenceDate
        print("Time Taken: \(delta)")
    }
    
    func updateSmallestValue() {
        let realm = try! Realm()
        let sortedResults = realm.objects(TestModel.self).sorted(byKeyPath: "value")

        smallestValue = sortedResults[0]
        
        print("Originally loaded smallest value: \(smallestValue.value)")
        
        let newSmallestValue = TestModel()
        newSmallestValue.value = smallestValue.value - 1
        try! realm.write {
            realm.add(newSmallestValue)
        }
        
        print("Originally loaded smallest value after write: \(smallestValue.value)")
        
        let readStart = Date()
        smallestValue = sortedResults[0]
        let readEnd = Date()
        let readDelta = readEnd.timeIntervalSinceReferenceDate - readStart.timeIntervalSinceReferenceDate
        print("Reloaded smallest value \(smallestValue.value)")
        print("Time Taken to reload the smallest value: \(readDelta)")
    }
}

使用documentCount = 100000, readData() 输出:

Time taken to load smallest value: 0.48901796340942383

和 updateData() 输出:

Originally loaded smallest value: 2075613243102
Originally loaded smallest value after write: 2075613243102
Reloaded smallest value 2075613243101
Time taken to reload the smallest value: 0.4624580144882202

使用documentCount = 1000000, readData() 输出:

Time taken to load smallest value: 4.807577967643738

和 updateData() 输出:

Originally loaded smallest value: 4004790407680
Originally loaded smallest value after write: 4004790407680
Reloaded smallest value 4004790407679
Time taken to reload the smallest value: 5.2308430671691895

从排序的结果集中检索第一个文档所花费的时间随着存储在领域中的文档数量而不是正在检索的文档数量而缩放。这向我表明,领域是在查询时而不是在编写文档时对所有文档进行排序。有没有一种方法可以索引您的数据,以便您可以快速检索少量排序的文档?

编辑:

在评论中的讨论之后,我更新了代码以仅加载排序集合中的最小值。

编辑 2

observe按照评论中的建议将代码更新为结果。

import Foundation
import RealmSwift

class TestModel: Object {
    @Persisted(indexed: true) var value: Int = 0
}

class RealmSortTest {
    let documentCount = 1000000
    var smallestValue: TestModel = TestModel()
    var storedResults: Results<TestModel> = (try! Realm()).objects(TestModel.self).sorted(byKeyPath: "value")
    var resultsToken: NotificationToken? = nil
    
    func writeData() {
        let realm = try! Realm()
        var documents: [TestModel] = []
        for _ in 0 ... documentCount {
            let newDoc = TestModel()
            newDoc.value = Int.random(in: 0 ... Int.max)
            documents.append(newDoc)
        }
        try! realm.write {
            realm.deleteAll()
            realm.add(documents)
        }
    }
    
    func observeData() {
        let realm = try! Realm()
        print("Loading Data")
        let startTime = Date()
        self.storedResults = realm.objects(TestModel.self).sorted(byKeyPath: "value")
        self.resultsToken = self.storedResults.observe { changes in
            let observationTime = Date().timeIntervalSince(startTime)
            print("Time to first observation: \(observationTime)")
            let firstTenElementsSlice = self.storedResults[0..<10]
            let elementsArray = Array(firstTenElementsSlice) //print this if you want to see the elements
            elementsArray.forEach { print($0.value) }
            let moreElapsed = Date().timeIntervalSince(startTime)
            print("Time to printed elements: \(moreElapsed)")
        }
    }
}

我得到以下输出

Loading Data
Time to first observation: 5.252112984657288
3792614823099
56006949537408
Time to printed elements: 5.253015995025635

用观察者读取数据并没有减少读取数据的时间。

4

2 回答 2

0

dogs并且dogsSorted是 Realm Results Collection 对象,它本质上包含指向底层数据的指针,而不是数据本身。

定义排序顺序不会加载所有对象,它们仍然是惰性的——只在需要时加载,这是 Realm 的巨大好处之一;可以使用巨大的数据集,而不必担心内存过载。

这也是 Realm Results 对象始终反映底层数据的数据当前状态的原因之一;该数据可以多次更改,您在应用程序结果变量(以及一般的领域集合)中看到的内容将始终显示更新的数据。

作为一个侧节点,此时使用 Swift 高级函数处理 Realm Collection 对象会导致该数据加载到内存中 - 所以不要这样做。使用 Realm 功能进行排序、过滤等,一切都保持惰性和内存友好。

索引是一种权衡;一方面,它可以提高某些查询的性能,例如相等(“name == 'Spot'”),但另一方面它会降低写入性能。此外,添加索引会占用更多空间。

一般来说,索引最适合特定用例;也许在某种情况下,您正在执行某种类型的提前自动填充,而性能至关重要。我们有几个应用程序具有非常大的数据集(Gb),并且没有任何索引,因为收到的性能优势被较慢的写入所抵消,这些写入经常进行。我建议在没有索引的情况下开始。

编辑:

将根据其他讨论更新答案。

首先,将数据从一个对象复制到另一个对象并不是衡量数据库加载性能的标准。这里的真正目标是用户体验和/或能够访问该数据 - 从用户期望看到数据的时间到显示数据的时间。因此,让我们提供一些代码来演示一般性能:

我们将首先从与 OP 使用的模型类似的模型开始

class TestModel: Object {
    @Persisted(indexed: true) var value: Int = 0
    
    convenience init(withIndex: Int) {
        self.init()
        self.value = withIndex
    }
}

然后定义几个变量来保存来自磁盘的结果和一个通知令牌,它允许我们知道何时可以向用户显示该数据。最后是一个 var 来保存加载开始的时间

var modelResults: Results<TestModel>!
var modelsToken: NotificationToken?
var startTime = Date()

这是写入大量数据的函数。objectCountvar 将从第一次运行的 10,000 个对象更改为第二次运行的 1,000,000 个对象。请注意,这是错误的编码,因为我在内存中创建了一百万个对象,所以不要这样做;仅用于演示目的。

func writeLotsOfData() {
    let realm = try! Realm()
    let objectCount = 1000000
    autoreleasepool {
        var testModelArray = [TestModel]()
        for _ in 0..<objectCount {
            let m = TestModel(withIndex: Int.random(in: 0 ... Int.max))
            testModelArray.append(m)
        }

        try! realm.write {
            realm.add(testModelArray)
        }
        
        print("data written: \(testModelArray.count) objects")
    }
}

最后是从领域加载这些对象并在数据可用于向用户显示时输出的函数。请注意,它们是根据原始问题排序的——事实上,随着数据的添加和更改,它们会保持它们的排序!很酷的东西。

func loadBigData() {
    let realm = try! Realm()
    print("Loading Data")
    
    self.startTime = Date()
    self.modelResults = realm.objects(TestModel.self).sorted(byKeyPath: "value")
    self.modelsToken = self.modelResults?.observe { changes in
        let elapsed = Date().timeIntervalSince(self.startTime)
        print("Load completed of \(self.modelResults.count) objects -  elapsed time of \(elapsed)")
    }
}

和结果。两次运行,一次包含 10,000 个对象,一次包含 1,000,000 个对象

data written: 10000 objects
Loading Data
Load completed of 10000 objects -  elapsed time of 0.0059670209884643555

data written: 1000000 objects
Loading Data
Load completed of 1000000 objects -  elapsed time of 0.6800119876861572

有三点需要注意

  1. Realm Notification 对象会在数据加载完成时触发一个事件,并且还会在发生其他更改时触发一个事件。我们正在利用它在数据完成加载并且可以使用时通知应用程序 - 例如向用户显示。

  2. 我们正在懒惰地加载所有对象!我们绝不会遇到内存过载问题。一旦对象加载到结果中,它们就可以自由地显示给用户或以任何需要的方式进行处理。在处理大型数据集时,以 Realm 方式处理 Realm 对象非常重要。一般来说,如果它有 10 个对象,将它们扔进一个数组是没有问题的,但是当有 100 万个对象时 - 让 Realm 做它是懒惰的工作。

  3. 该应用程序使用上述代码和技术进行保护。可能有 10 个对象或 1,000,000 个对象,内存影响很小。

编辑 2

(有关此编辑的更多信息,请参阅对 OP 问题的评论)

根据 OP 的请求,他们希望看到带有打印值和时间的相同练习。这是更新的代码

self.modelsToken = self.modelResults?.observe { changes in
    let elapsed = Date().timeIntervalSince(self.startTime)
    print("Load completed of \(self.modelResults.count) objects -  elapsed time of \(elapsed)")
    print("print first 10 object values")
    let firstTenElementsSlice = self.modelResults[0..<10]
    let elementsArray = Array(firstTenElementsSlice) //print this if you want to see the elements
    elementsArray.forEach { print($0.value)}
    let moreElapsed = Date().timeIntervalSince(self.startTime)
    print("Printing of 10 elements completed: \(moreElapsed)")
}

然后输出

Loading Data
Load completed of 1000000 objects -  elapsed time of 0.6730009317398071
print first 10 object values
12264243738520
17242140785413
29611477414437
31558144830373
32913160803785
45399774467128
61700529799916
63929929449365
73833938586206
81739195218861
Printing of 10 elements completed: 0.6745189428329468
于 2021-12-29T18:31:23.663 回答
0

此时看来,Realm 是在访问时而不是在写入时对数据进行排序,并且没有办法让 Realm 在写入时对数据进行排序。这意味着访问排序的数据与数据库中的文档数量而不是被访问的文档数量成比例。

访问数据所需的实际时间因用例和平台而异。

于 2022-02-21T16:35:27.177 回答