Проблемы с производительностью в списке областей - PullRequest
0 голосов
/ 28 ноября 2018

У меня возникают проблемы с производительностью памяти при выполнении операций со списками областей.У меня есть два объекта, похожих на этот:

final class Contact: Object {
    let phones = List<Phone>()
    let emails = List<Email>()
}

Теперь я пытаюсь найти возможные сходства между двумя объектами одного типа (например, хотя бы одним общим элементом), которые потенциально могут иметь дублирующиеся электронные письма илителефоны.Для этого я использовал Set операции.

func possibleDuplicateOf(contact: Contact) {
    return !Set(emails).isDisjoint(with: Set(contact.emails)) || !Set(phones).isDisjoint(with: Set(contact.phones))
}

Это функция внутри объекта Contact.Я знаю, что при преобразовании Списка Сфер в Набор или Массив производительность падает, и я чувствую это сильно, когда у меня большое количество контактов (10 КБ или более), потребление памяти возрастает до более чем 1 ГБ.

Итак, я попытался заменить вышеупомянутую функцию на эту:

func possibleDuplicateOf(contact: Contact) {
    let emailsInCommon = emails.contains(where: contact.emails.contains)
    let phonesInCommon = phones.contains(where: contact.phones.contains)

    return emailsInCommon || phonesInCommon
}

Это имеет ту же производительность, что и использование наборов.

Метод isEqual в электронных письмах и телефонах является простымсравнение строк:

extension Email {
    static func ==(lhs: Email, rhs: Email) -> Bool {
        return (lhs.email == rhs.email)
    }

    override func isEqual(_ object: Any?) -> Bool {
        guard let object = object as? Email else { return false }

        return object == self
    }

    override var hash: Int {
        return email.hashValue
    }
}

Email.swift

final class Email: Object {

enum Attribute: String { case primary, secondary }

@objc dynamic var email: String = ""
@objc dynamic var label: String = ""

/* Cloud Properties */
@objc dynamic var attribute_raw: String = ""
var attribute: Attribute {
    get {
        guard let attributeEnum = Attribute(rawValue: attribute_raw) else { return .primary }
        return attributeEnum
    }
    set { attribute_raw = newValue.rawValue }
}

override static func ignoredProperties() -> [String] {
    return ["attribute"]
}

convenience init(email: String, label: String = "email", attribute: Attribute) {
        self.init()

        self.email = email
        self.label = label
        self.attribute = attribute
    }
}

У меня немного не хватает вариантов, я потратил целый день, пытаясь придумать другой подходк этой проблеме, но без удачи.Если у кого-то есть идея получше, я бы с удовольствием выслушал ее:)

Спасибо

Ответы [ 2 ]

0 голосов
/ 13 декабря 2018

Ваша проблема может быть решена более оптимально, если перестроить структуры данных.Получение всего в памяти и попытка преобразования в набор (создание наборов - дорогостоящая операция) далеко не оптимальны :(. Я предлагаю это решение.

Рассмотрим контакт - это объект (я добавил свойство id)Я не добавлял объекты телефонов для краткости, но точно такой же подход может быть использован для телефонов.

class Contact: Object {
    @objc dynamic var id = UUID().uuidString
    var emails = List<Email>()

    override public static func primaryKey() -> String? {
        return "id"
    }
}

И вот класс электронной почты. Отношение к контакту добавлено.

class Email: Object {
    @objc dynamic var email: String = ""
    @objc dynamic var contact: Contact?
}

Имея эти «связанные» таблицы в области, вы можете создать запрос для поиска дублированных объектов:

func hasDups(contact: Contact) -> Bool {
  let realm = try! Realm()
  let emails: [String] = contact.emails.map { $0.email }
  let sameObjects = realm.objects(Email.self)
                         .filter("email in %@ AND contact.id != %@", emails, contact.id)
  // sameObject will contain emails which has duplicates with current contact
  return !sameObjects.isEmpty
}

Это работает очень быстро, я протестировал более 100000 объектов и сразу же выполнил.

Надеюсь, это поможет!

0 голосов
/ 10 декабря 2018

Всякий раз, когда происходит что-то подобное, хорошее начало - использовать Instruments , чтобы выяснить, где используются циклы ЦП и память.Вот хороший учебник: Использование Time Profiler в инструментах

Вы пропустили код, который делает реальное сравнение, но я подозреваю, что это могут быть вложенные циклы for или что-то в этом роде.Realm не знает ваш вариант использования и не кэширует должным образом что-то подобное.

Используя инструменты, довольно легко найти узкие места.В вашем случае это должно работать:

final class Contact: Object
{
    let emails = List<Email>()

    lazy var emailsForDuplicateCheck:Set<Email> = Set(emails)

    func possibleDuplicateOf(other: Contact) -> Bool {
        return !emailsForDuplicateCheck.isDisjoint(with: other.emailsForDuplicateCheck)
    }

    override static func ignoredProperties() -> [String] {
        return ["emailsForDuplicateCheck"]
    }
}

И для сравнения:

// create an array of the contacts to be compared to cache them 
let contacts = Array(realm.objects(Contact.self))
for contact in contacts {
    for other in contacts {
        if contact.possibleDuplicateOf(other: other) {
            print("Possible duplicate found!")
        }
    }
}

Эта реализация гарантирует, что Contact объекты выбираются только один раз, а SetEmail создается только один раз для каждого Contact.

...