Realm返回排序数据的速度有多快



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

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

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

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

与观察者一起读取数据并没有减少读取数据所花费的时间。

此时,Realm似乎在访问数据时而不是在写入数据时对数据进行排序,并且没有办法让Realm在写入时对数据排序。这意味着访问排序后的数据会随着数据库中文档的数量而变化,而不是随着访问的文档数量而变化。

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

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

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

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

作为一个侧节点,此时使用Swift High Level函数处理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来保存磁盘上的Results和一个通知令牌,该令牌允许我们知道何时可以向用户显示该数据。最后是一个var,用于保存加载开始的时间

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

这是一个写入大量数据的函数。objectCount变量将从第一次运行的10000个对象更改为第二次运行的1000000个对象。请注意,这是糟糕的编码,因为我正在内存中创建一百万个对象,所以不要这样做;仅供演示之用。

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

以及结果。两次运行,一次运行10000个对象,另一次运行1000000个对象

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. 当数据完成加载,以及当有其他更改时。我们是利用它在数据加载完成时通知应用程序并且可以使用-例如显示给用户。

  2. 我们正懒洋洋地加载所有的对象!我们决不去遇到内存过载问题。一旦加载了对象到结果中,然后可以免费向用户或以任何需要的方式处理。对工作非常重要在处理大型数据集时,以Realm方式处理Realm对象。一般来说,如果是10个物体,投掷没有问题它们组成一个数组,但当有100万个对象时,让Realm做这件事很懒。

  3. 该应用程序使用上述代码和技术进行保护。那里可能是10个对象或1000000个对象,对内存的影响是最小的

编辑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

相关内容

最新更新